This is an automated email from the ASF dual-hosted git repository.

yzheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new 87841359c Python code format (#1954)
87841359c is described below

commit 87841359c489f35f808e6a0ab9f232fd08f9fbd5
Author: Yong Zheng <[email protected]>
AuthorDate: Fri Jun 27 16:13:57 2025 -0500

    Python code format (#1954)
---
 client/python/cli/command/__init__.py        |  63 ++-
 client/python/cli/command/catalog_roles.py   |  52 +-
 client/python/cli/command/catalogs.py        | 134 +++--
 client/python/cli/command/namespaces.py      |  43 +-
 client/python/cli/command/principal_roles.py |  42 +-
 client/python/cli/command/principals.py      |  97 ++--
 client/python/cli/command/privileges.py      |  78 +--
 client/python/cli/command/profiles.py        |  47 +-
 client/python/cli/constants.py               | 274 ++++++-----
 client/python/cli/options/option_tree.py     | 712 ++++++++++++++++++++-------
 client/python/cli/options/parser.py          | 128 +++--
 client/python/cli/polaris_cli.py             | 117 +++--
 12 files changed, 1212 insertions(+), 575 deletions(-)

diff --git a/client/python/cli/command/__init__.py 
b/client/python/cli/command/__init__.py
index 414ce43f5..897cbaaa5 100644
--- a/client/python/cli/command/__init__.py
+++ b/client/python/cli/command/__init__.py
@@ -32,8 +32,7 @@ class Command(ABC):
     """
 
     @staticmethod
-    def from_options(options: argparse.Namespace) -> 'Command':
-
+    def from_options(options: argparse.Namespace) -> "Command":
         def options_get(key, f=lambda x: x):
             return f(getattr(options, key)) if hasattr(options, key) else None
 
@@ -44,8 +43,9 @@ class Command(ABC):
         command = None
         if options.command == Commands.CATALOGS:
             from cli.command.catalogs import CatalogsCommand
+
             command = CatalogsCommand(
-                options_get(f'{Commands.CATALOGS}_subcommand'),
+                options_get(f"{Commands.CATALOGS}_subcommand"),
                 catalog_type=options_get(Arguments.TYPE),
                 
default_base_location=options_get(Arguments.DEFAULT_BASE_LOCATION),
                 storage_type=options_get(Arguments.STORAGE_TYPE),
@@ -61,81 +61,100 @@ class Command(ABC):
                 catalog_name=options_get(Arguments.CATALOG),
                 properties={} if properties is None else properties,
                 set_properties={} if set_properties is None else 
set_properties,
-                remove_properties=[] if remove_properties is None else 
remove_properties
+                remove_properties=[]
+                if remove_properties is None
+                else remove_properties,
             )
         elif options.command == Commands.PRINCIPALS:
             from cli.command.principals import PrincipalsCommand
+
             command = PrincipalsCommand(
-                options_get(f'{Commands.PRINCIPALS}_subcommand'),
+                options_get(f"{Commands.PRINCIPALS}_subcommand"),
                 type=options_get(Arguments.TYPE),
                 principal_name=options_get(Arguments.PRINCIPAL),
                 client_id=options_get(Arguments.CLIENT_ID),
                 principal_role=options_get(Arguments.PRINCIPAL_ROLE),
                 properties={} if properties is None else properties,
                 set_properties={} if set_properties is None else 
set_properties,
-                remove_properties=[] if remove_properties is None else 
remove_properties
+                remove_properties=[]
+                if remove_properties is None
+                else remove_properties,
             )
         elif options.command == Commands.PRINCIPAL_ROLES:
             from cli.command.principal_roles import PrincipalRolesCommand
+
             command = PrincipalRolesCommand(
-                options_get(f'{Commands.PRINCIPAL_ROLES}_subcommand'),
+                options_get(f"{Commands.PRINCIPAL_ROLES}_subcommand"),
                 principal_role_name=options_get(Arguments.PRINCIPAL_ROLE),
                 principal_name=options_get(Arguments.PRINCIPAL),
                 catalog_name=options_get(Arguments.CATALOG),
                 catalog_role_name=options_get(Arguments.CATALOG_ROLE),
                 properties={} if properties is None else properties,
                 set_properties={} if set_properties is None else 
set_properties,
-                remove_properties=[] if remove_properties is None else 
remove_properties
+                remove_properties=[]
+                if remove_properties is None
+                else remove_properties,
             )
         elif options.command == Commands.CATALOG_ROLES:
             from cli.command.catalog_roles import CatalogRolesCommand
+
             command = CatalogRolesCommand(
-                options_get(f'{Commands.CATALOG_ROLES}_subcommand'),
+                options_get(f"{Commands.CATALOG_ROLES}_subcommand"),
                 catalog_name=options_get(Arguments.CATALOG),
                 catalog_role_name=options_get(Arguments.CATALOG_ROLE),
                 principal_role_name=options_get(Arguments.PRINCIPAL_ROLE),
                 properties={} if properties is None else properties,
                 set_properties={} if set_properties is None else 
set_properties,
-                remove_properties=[] if remove_properties is None else 
remove_properties
+                remove_properties=[]
+                if remove_properties is None
+                else remove_properties,
             )
         elif options.command == Commands.PRIVILEGES:
             from cli.command.privileges import PrivilegesCommand
-            subcommand = options_get(f'{Commands.PRIVILEGES}_subcommand')
+
+            subcommand = options_get(f"{Commands.PRIVILEGES}_subcommand")
             command = PrivilegesCommand(
                 subcommand,
-                action=options_get(f'{subcommand}_subcommand'),
+                action=options_get(f"{subcommand}_subcommand"),
                 catalog_name=options_get(Arguments.CATALOG),
                 catalog_role_name=options_get(Arguments.CATALOG_ROLE),
-                namespace=options_get(Arguments.NAMESPACE, lambda s: 
s.split('.') if s else None),
+                namespace=options_get(
+                    Arguments.NAMESPACE, lambda s: s.split(".") if s else None
+                ),
                 view=options_get(Arguments.VIEW),
                 table=options_get(Arguments.TABLE),
                 privilege=options_get(Arguments.PRIVILEGE),
-                cascade=options_get(Arguments.CASCADE)
+                cascade=options_get(Arguments.CASCADE),
             )
         elif options.command == Commands.NAMESPACES:
             from cli.command.namespaces import NamespacesCommand
-            subcommand = options_get(f'{Commands.NAMESPACES}_subcommand')
+
+            subcommand = options_get(f"{Commands.NAMESPACES}_subcommand")
             command = NamespacesCommand(
                 subcommand,
                 catalog=options_get(Arguments.CATALOG),
-                namespace=options_get(Arguments.NAMESPACE, lambda s: 
s.split('.')),
-                parent=options_get(Arguments.PARENT, lambda s: s.split('.') if 
s else None),
+                namespace=options_get(Arguments.NAMESPACE, lambda s: 
s.split(".")),
+                parent=options_get(
+                    Arguments.PARENT, lambda s: s.split(".") if s else None
+                ),
                 location=options_get(Arguments.LOCATION),
-                properties=properties
+                properties=properties,
             )
         elif options.command == Commands.PROFILES:
             from cli.command.profiles import ProfilesCommand
-            subcommand = options_get(f'{Commands.PROFILES}_subcommand')
+
+            subcommand = options_get(f"{Commands.PROFILES}_subcommand")
             command = ProfilesCommand(
-                subcommand,
-                profile_name=options_get(Arguments.PROFILE)
+                subcommand, profile_name=options_get(Arguments.PROFILE)
             )
 
         if command is not None:
             command.validate()
             return command
         else:
-            raise Exception("Please specify a command or run ./polaris --help 
to view the available commands")
+            raise Exception(
+                "Please specify a command or run ./polaris --help to view the 
available commands"
+            )
 
     def execute(self, api: PolarisDefaultApi) -> None:
         """
diff --git a/client/python/cli/command/catalog_roles.py 
b/client/python/cli/command/catalog_roles.py
index 1c7d4df9a..3fa7cebb5 100644
--- a/client/python/cli/command/catalog_roles.py
+++ b/client/python/cli/command/catalog_roles.py
@@ -24,8 +24,13 @@ from pydantic import StrictStr
 from cli.command import Command
 from cli.constants import Subcommands, Arguments
 from cli.options.option_tree import Argument
-from polaris.management import PolarisDefaultApi, CreateCatalogRoleRequest, 
CatalogRole, UpdateCatalogRoleRequest, \
-    GrantCatalogRoleRequest
+from polaris.management import (
+    PolarisDefaultApi,
+    CreateCatalogRoleRequest,
+    CatalogRole,
+    UpdateCatalogRoleRequest,
+    GrantCatalogRoleRequest,
+)
 
 
 @dataclass
@@ -51,34 +56,44 @@ class CatalogRolesCommand(Command):
 
     def validate(self):
         if not self.catalog_name:
-            raise Exception(f'Missing required argument: 
{Argument.to_flag_name(Arguments.CATALOG)}')
+            raise Exception(
+                f"Missing required argument: 
{Argument.to_flag_name(Arguments.CATALOG)}"
+            )
         if self.catalog_roles_subcommand in {Subcommands.GRANT, 
Subcommands.REVOKE}:
             if not self.principal_role_name:
-                raise Exception(f'Missing required argument: 
{Argument.to_flag_name(Arguments.PRINCIPAL_ROLE)}')
+                raise Exception(
+                    f"Missing required argument: 
{Argument.to_flag_name(Arguments.PRINCIPAL_ROLE)}"
+                )
 
     def execute(self, api: PolarisDefaultApi) -> None:
         if self.catalog_roles_subcommand == Subcommands.CREATE:
             request = CreateCatalogRoleRequest(
                 catalog_role=CatalogRole(
-                    name=self.catalog_role_name,
-                    properties=self.properties
+                    name=self.catalog_role_name, properties=self.properties
                 )
             )
             api.create_catalog_role(self.catalog_name, request)
         elif self.catalog_roles_subcommand == Subcommands.DELETE:
             api.delete_catalog_role(self.catalog_name, self.catalog_role_name)
         elif self.catalog_roles_subcommand == Subcommands.GET:
-            print(api.get_catalog_role(self.catalog_name, 
self.catalog_role_name).to_json())
+            print(
+                api.get_catalog_role(
+                    self.catalog_name, self.catalog_role_name
+                ).to_json()
+            )
         elif self.catalog_roles_subcommand == Subcommands.LIST:
             if self.principal_role_name:
                 for catalog_role in api.list_catalog_roles_for_principal_role(
-                        self.principal_role_name, self.catalog_name).roles:
+                    self.principal_role_name, self.catalog_name
+                ).roles:
                     print(catalog_role.to_json())
             else:
                 for catalog_role in 
api.list_catalog_roles(self.catalog_name).roles:
                     print(catalog_role.to_json())
         elif self.catalog_roles_subcommand == Subcommands.UPDATE:
-            catalog_role = api.get_catalog_role(self.catalog_name, 
self.catalog_role_name)
+            catalog_role = api.get_catalog_role(
+                self.catalog_name, self.catalog_role_name
+            )
             new_properties = catalog_role.properties or {}
 
             # Add or update all entries specified in set_properties
@@ -92,19 +107,22 @@ class CatalogRolesCommand(Command):
 
             request = UpdateCatalogRoleRequest(
                 current_entity_version=catalog_role.entity_version,
-                properties=new_properties
+                properties=new_properties,
             )
             api.update_catalog_role(self.catalog_name, self.catalog_role_name, 
request)
         elif self.catalog_roles_subcommand == Subcommands.GRANT:
             request = GrantCatalogRoleRequest(
-                catalog_role=CatalogRole(
-                    name=self.catalog_role_name
-                ),
-                properties=self.properties
+                catalog_role=CatalogRole(name=self.catalog_role_name),
+                properties=self.properties,
+            )
+            api.assign_catalog_role_to_principal_role(
+                self.principal_role_name, self.catalog_name, request
             )
-            
api.assign_catalog_role_to_principal_role(self.principal_role_name, 
self.catalog_name, request)
         elif self.catalog_roles_subcommand == Subcommands.REVOKE:
             api.revoke_catalog_role_from_principal_role(
-                self.principal_role_name, self.catalog_name, 
self.catalog_role_name)
+                self.principal_role_name, self.catalog_name, 
self.catalog_role_name
+            )
         else:
-            raise Exception(f'{self.catalog_roles_subcommand} is not supported 
in the CLI')
+            raise Exception(
+                f"{self.catalog_roles_subcommand} is not supported in the CLI"
+            )
diff --git a/client/python/cli/command/catalogs.py 
b/client/python/cli/command/catalogs.py
index 718e6d11a..0baf2cffa 100644
--- a/client/python/cli/command/catalogs.py
+++ b/client/python/cli/command/catalogs.py
@@ -24,9 +24,18 @@ from pydantic import StrictStr
 from cli.command import Command
 from cli.constants import StorageType, CatalogType, Subcommands, Arguments
 from cli.options.option_tree import Argument
-from polaris.management import PolarisDefaultApi, CreateCatalogRequest, 
UpdateCatalogRequest, \
-    StorageConfigInfo, ExternalCatalog, AwsStorageConfigInfo, 
AzureStorageConfigInfo, GcpStorageConfigInfo, \
-    PolarisCatalog, CatalogProperties
+from polaris.management import (
+    PolarisDefaultApi,
+    CreateCatalogRequest,
+    UpdateCatalogRequest,
+    StorageConfigInfo,
+    ExternalCatalog,
+    AwsStorageConfigInfo,
+    AzureStorageConfigInfo,
+    GcpStorageConfigInfo,
+    PolarisCatalog,
+    CatalogProperties,
+)
 
 
 @dataclass
@@ -63,38 +72,58 @@ class CatalogsCommand(Command):
     def validate(self):
         if self.catalogs_subcommand == Subcommands.CREATE:
             if not self.storage_type:
-                raise Exception(f'Missing required argument:'
-                                f' 
{Argument.to_flag_name(Arguments.STORAGE_TYPE)}')
+                raise Exception(
+                    f"Missing required argument:"
+                    f" {Argument.to_flag_name(Arguments.STORAGE_TYPE)}"
+                )
             if not self.default_base_location:
-                raise Exception(f'Missing required argument:'
-                                f' 
{Argument.to_flag_name(Arguments.DEFAULT_BASE_LOCATION)}')
+                raise Exception(
+                    f"Missing required argument:"
+                    f" 
{Argument.to_flag_name(Arguments.DEFAULT_BASE_LOCATION)}"
+                )
 
         if self.storage_type == StorageType.S3.value:
             if not self.role_arn:
-                raise Exception(f"Missing required argument for storage type 
's3':"
-                                f" 
{Argument.to_flag_name(Arguments.ROLE_ARN)}")
+                raise Exception(
+                    f"Missing required argument for storage type 's3':"
+                    f" {Argument.to_flag_name(Arguments.ROLE_ARN)}"
+                )
             if self._has_azure_storage_info() or self._has_gcs_storage_info():
-                raise Exception(f"Storage type 's3' supports the storage 
credentials"
-                                f" 
{Argument.to_flag_name(Arguments.ROLE_ARN)},"
-                                f" {Argument.to_flag_name(Arguments.REGION)},"
-                                f" 
{Argument.to_flag_name(Arguments.EXTERNAL_ID)}, and"
-                                f" 
{Argument.to_flag_name(Arguments.USER_ARN)}")
+                raise Exception(
+                    f"Storage type 's3' supports the storage credentials"
+                    f" {Argument.to_flag_name(Arguments.ROLE_ARN)},"
+                    f" {Argument.to_flag_name(Arguments.REGION)},"
+                    f" {Argument.to_flag_name(Arguments.EXTERNAL_ID)}, and"
+                    f" {Argument.to_flag_name(Arguments.USER_ARN)}"
+                )
         elif self.storage_type == StorageType.AZURE.value:
             if not self.tenant_id:
-                raise Exception("Missing required argument for storage type 
'azure': "
-                                f" 
{Argument.to_flag_name(Arguments.TENANT_ID)}")
+                raise Exception(
+                    "Missing required argument for storage type 'azure': "
+                    f" {Argument.to_flag_name(Arguments.TENANT_ID)}"
+                )
             if self._has_aws_storage_info() or self._has_gcs_storage_info():
-                raise Exception("Storage type 'azure' supports the storage 
credentials"
-                                f" 
{Argument.to_flag_name(Arguments.TENANT_ID)},"
-                                f" 
{Argument.to_flag_name(Arguments.MULTI_TENANT_APP_NAME)}, and"
-                                f" 
{Argument.to_flag_name(Arguments.CONSENT_URL)}")
+                raise Exception(
+                    "Storage type 'azure' supports the storage credentials"
+                    f" {Argument.to_flag_name(Arguments.TENANT_ID)},"
+                    f" 
{Argument.to_flag_name(Arguments.MULTI_TENANT_APP_NAME)}, and"
+                    f" {Argument.to_flag_name(Arguments.CONSENT_URL)}"
+                )
         elif self.storage_type == StorageType.GCS.value:
             if self._has_aws_storage_info() or self._has_azure_storage_info():
-                raise Exception("Storage type 'gcs' supports the storage 
credential"
-                                f" 
{Argument.to_flag_name(Arguments.SERVICE_ACCOUNT)}")
+                raise Exception(
+                    "Storage type 'gcs' supports the storage credential"
+                    f" {Argument.to_flag_name(Arguments.SERVICE_ACCOUNT)}"
+                )
         elif self.storage_type == StorageType.FILE.value:
-            if self._has_aws_storage_info() or self._has_azure_storage_info() 
or self._has_gcs_storage_info():
-                raise Exception("Storage type 'file' does not support any 
storage credentials")
+            if (
+                self._has_aws_storage_info()
+                or self._has_azure_storage_info()
+                or self._has_gcs_storage_info()
+            ):
+                raise Exception(
+                    "Storage type 'file' does not support any storage 
credentials"
+                )
 
     def _has_aws_storage_info(self):
         return self.role_arn or self.external_id or self.user_arn or 
self.region
@@ -114,7 +143,7 @@ class CatalogsCommand(Command):
                 role_arn=self.role_arn,
                 external_id=self.external_id,
                 user_arn=self.user_arn,
-                region=self.region
+                region=self.region,
             )
         elif self.storage_type == StorageType.AZURE.value:
             config = AzureStorageConfigInfo(
@@ -128,12 +157,12 @@ class CatalogsCommand(Command):
             config = GcpStorageConfigInfo(
                 storage_type=self.storage_type.upper(),
                 allowed_locations=self.allowed_locations,
-                gcs_service_account=self.service_account
+                gcs_service_account=self.service_account,
             )
         elif self.storage_type == StorageType.FILE.value:
             config = StorageConfigInfo(
                 storage_type=self.storage_type.upper(),
-                allowed_locations=self.allowed_locations
+                allowed_locations=self.allowed_locations,
             )
         return config
 
@@ -148,8 +177,8 @@ class CatalogsCommand(Command):
                         storage_config_info=config,
                         properties=CatalogProperties(
                             default_base_location=self.default_base_location,
-                            additional_properties=self.properties
-                        )
+                            additional_properties=self.properties,
+                        ),
                     )
                 )
             else:
@@ -160,8 +189,8 @@ class CatalogsCommand(Command):
                         storage_config_info=config,
                         properties=CatalogProperties(
                             default_base_location=self.default_base_location,
-                            additional_properties=self.properties
-                        )
+                            additional_properties=self.properties,
+                        ),
                     )
                 )
             api.create_catalog(request)
@@ -175,13 +204,25 @@ class CatalogsCommand(Command):
         elif self.catalogs_subcommand == Subcommands.UPDATE:
             catalog = api.get_catalog(self.catalog_name)
 
-            if self.default_base_location or self.set_properties or 
self.remove_properties:
-                new_default_base_location = self.default_base_location or 
catalog.properties.default_base_location
-                new_additional_properties = 
catalog.properties.additional_properties or {}
+            if (
+                self.default_base_location
+                or self.set_properties
+                or self.remove_properties
+            ):
+                new_default_base_location = (
+                    self.default_base_location
+                    or catalog.properties.default_base_location
+                )
+                new_additional_properties = (
+                    catalog.properties.additional_properties or {}
+                )
 
                 # Add or update all entries specified in set_properties
                 if self.set_properties:
-                    new_additional_properties = {**new_additional_properties, 
**self.set_properties}
+                    new_additional_properties = {
+                        **new_additional_properties,
+                        **self.set_properties,
+                    }
 
                 # Remove all keys specified in remove_properties
                 if self.remove_properties:
@@ -190,11 +231,15 @@ class CatalogsCommand(Command):
 
                 catalog.properties = CatalogProperties(
                     default_base_location=new_default_base_location,
-                    additional_properties=new_additional_properties
+                    additional_properties=new_additional_properties,
                 )
 
-            if (self._has_aws_storage_info() or self._has_azure_storage_info() 
or
-                self._has_gcs_storage_info() or self.allowed_locations):
+            if (
+                self._has_aws_storage_info()
+                or self._has_azure_storage_info()
+                or self._has_gcs_storage_info()
+                or self.allowed_locations
+            ):
                 # We must first reconstitute local storage-config related 
settings from the existing
                 # catalog to properly construct the complete updated 
storage-config
                 updated_storage_info = catalog.storage_config_info
@@ -204,7 +249,9 @@ class CatalogsCommand(Command):
                 # in option_tree.py should be applied individually against the 
existing
                 # storage_config_info here.
                 if self.allowed_locations:
-                    
updated_storage_info.allowed_locations.extend(self.allowed_locations)
+                    updated_storage_info.allowed_locations.extend(
+                        self.allowed_locations
+                    )
 
                 if self.region:
                     # Note: We have to lowercase the returned value because 
the server enum
@@ -212,20 +259,21 @@ class CatalogsCommand(Command):
                     storage_type = updated_storage_info.storage_type
                     if storage_type.lower() != StorageType.S3.value:
                         raise Exception(
-                            f'--region requires S3 storage_type, got: 
{storage_type}')
+                            f"--region requires S3 storage_type, got: 
{storage_type}"
+                        )
                     updated_storage_info.region = self.region
 
                 request = UpdateCatalogRequest(
                     current_entity_version=catalog.entity_version,
                     properties=catalog.properties.to_dict(),
-                    storage_config_info=updated_storage_info
+                    storage_config_info=updated_storage_info,
                 )
             else:
                 request = UpdateCatalogRequest(
                     current_entity_version=catalog.entity_version,
-                    properties=catalog.properties.to_dict()
+                    properties=catalog.properties.to_dict(),
                 )
 
             api.update_catalog(self.catalog_name, request)
         else:
-            raise Exception(f'{self.catalogs_subcommand} is not supported in 
the CLI')
+            raise Exception(f"{self.catalogs_subcommand} is not supported in 
the CLI")
diff --git a/client/python/cli/command/namespaces.py 
b/client/python/cli/command/namespaces.py
index 65687226a..763138473 100644
--- a/client/python/cli/command/namespaces.py
+++ b/client/python/cli/command/namespaces.py
@@ -26,7 +26,12 @@ from pydantic import StrictStr
 from cli.command import Command
 from cli.constants import Subcommands, Arguments, UNIT_SEPARATOR
 from cli.options.option_tree import Argument
-from polaris.catalog import IcebergCatalogAPI, CreateNamespaceRequest, 
ApiClient, Configuration
+from polaris.catalog import (
+    IcebergCatalogAPI,
+    CreateNamespaceRequest,
+    ApiClient,
+    Configuration,
+)
 from polaris.management import PolarisDefaultApi
 
 
@@ -51,16 +56,19 @@ class NamespacesCommand(Command):
 
     def validate(self):
         if not self.catalog:
-            raise Exception(f'Missing required argument:'
-                            f' {Argument.to_flag_name(Arguments.CATALOG)}')
+            raise Exception(
+                f"Missing required argument: 
{Argument.to_flag_name(Arguments.CATALOG)}"
+            )
 
     def _get_catalog_api(self, api: PolarisDefaultApi):
         """
         Convert a management API to a catalog API
         """
-        catalog_host = re.match(r'(https?://.+)/api/management', 
api.api_client.configuration.host).group(1)
+        catalog_host = re.match(
+            r"(https?://.+)/api/management", api.api_client.configuration.host
+        ).group(1)
         configuration = Configuration(
-            host=f'{catalog_host}/api/catalog',
+            host=f"{catalog_host}/api/catalog",
             username=api.api_client.configuration.username,
             password=api.api_client.configuration.password,
             access_token=api.api_client.configuration.access_token,
@@ -74,26 +82,29 @@ class NamespacesCommand(Command):
             if self.location:
                 req_properties = {**req_properties, Arguments.LOCATION: 
self.location}
             request = CreateNamespaceRequest(
-                namespace=self.namespace,
-                properties=req_properties
+                namespace=self.namespace, properties=req_properties
             )
             catalog_api.create_namespace(
-                prefix=self.catalog,
-                create_namespace_request=request)
+                prefix=self.catalog, create_namespace_request=request
+            )
         elif self.namespaces_subcommand == Subcommands.LIST:
             if self.parent is not None:
                 result = catalog_api.list_namespaces(
-                    prefix=self.catalog,
-                    parent=UNIT_SEPARATOR.join(self.parent))
+                    prefix=self.catalog, 
parent=UNIT_SEPARATOR.join(self.parent)
+                )
             else:
                 result = catalog_api.list_namespaces(prefix=self.catalog)
             for namespace in result.namespaces:
-                print(json.dumps({"namespace": '.'.join(namespace)}))
+                print(json.dumps({"namespace": ".".join(namespace)}))
         elif self.namespaces_subcommand == Subcommands.DELETE:
-            catalog_api.drop_namespace(prefix=self.catalog, 
namespace=UNIT_SEPARATOR.join(self.namespace))
+            catalog_api.drop_namespace(
+                prefix=self.catalog, 
namespace=UNIT_SEPARATOR.join(self.namespace)
+            )
         elif self.namespaces_subcommand == Subcommands.GET:
-            print(catalog_api.load_namespace_metadata(
-                prefix=self.catalog,
-                namespace=UNIT_SEPARATOR.join(self.namespace)).to_json())
+            print(
+                catalog_api.load_namespace_metadata(
+                    prefix=self.catalog, 
namespace=UNIT_SEPARATOR.join(self.namespace)
+                ).to_json()
+            )
         else:
             raise Exception(f"{self.namespaces_subcommand} is not supported in 
the CLI")
diff --git a/client/python/cli/command/principal_roles.py 
b/client/python/cli/command/principal_roles.py
index 911f741f5..8dd48bb74 100644
--- a/client/python/cli/command/principal_roles.py
+++ b/client/python/cli/command/principal_roles.py
@@ -24,8 +24,13 @@ from pydantic import StrictStr
 from cli.command import Command
 from cli.constants import Subcommands, Arguments
 from cli.options.option_tree import Argument
-from polaris.management import PolarisDefaultApi, CreatePrincipalRoleRequest, 
PrincipalRole, UpdatePrincipalRoleRequest, \
-    GrantPrincipalRoleRequest
+from polaris.management import (
+    PolarisDefaultApi,
+    CreatePrincipalRoleRequest,
+    PrincipalRole,
+    UpdatePrincipalRoleRequest,
+    GrantPrincipalRoleRequest,
+)
 
 
 @dataclass
@@ -52,19 +57,22 @@ class PrincipalRolesCommand(Command):
     def validate(self):
         if self.principal_roles_subcommand == Subcommands.LIST:
             if self.principal_name and self.catalog_role_name:
-                raise Exception(f'You may provide either 
{Argument.to_flag_name(Arguments.PRINCIPAL)} or'
-                                f' 
{Argument.to_flag_name(Arguments.CATALOG_ROLE)}, but not both')
+                raise Exception(
+                    f"You may provide either 
{Argument.to_flag_name(Arguments.PRINCIPAL)} or"
+                    f" {Argument.to_flag_name(Arguments.CATALOG_ROLE)}, but 
not both"
+                )
         if self.principal_roles_subcommand in {Subcommands.GRANT, 
Subcommands.REVOKE}:
             if not self.principal_name:
-                raise Exception(f'Missing required argument for 
{self.principal_roles_subcommand}:'
-                                f' 
{Argument.to_flag_name(Arguments.PRINCIPAL)}')
+                raise Exception(
+                    f"Missing required argument for 
{self.principal_roles_subcommand}:"
+                    f" {Argument.to_flag_name(Arguments.PRINCIPAL)}"
+                )
 
     def execute(self, api: PolarisDefaultApi) -> None:
         if self.principal_roles_subcommand == Subcommands.CREATE:
             request = CreatePrincipalRoleRequest(
                 principal_role=PrincipalRole(
-                    name=self.principal_role_name,
-                    properties=self.properties
+                    name=self.principal_role_name, properties=self.properties
                 )
             )
             api.create_principal_role(request)
@@ -74,10 +82,14 @@ class PrincipalRolesCommand(Command):
             print(api.get_principal_role(self.principal_role_name).to_json())
         elif self.principal_roles_subcommand == Subcommands.LIST:
             if self.catalog_role_name:
-                for principal_role in 
api.list_principal_roles(self.catalog_role_name).roles:
+                for principal_role in api.list_principal_roles(
+                    self.catalog_role_name
+                ).roles:
                     print(principal_role.to_json())
             elif self.principal_name:
-                for principal_role in 
api.list_principal_roles_assigned(self.principal_name).roles:
+                for principal_role in api.list_principal_roles_assigned(
+                    self.principal_name
+                ).roles:
                     print(principal_role.to_json())
             else:
                 for principal_role in api.list_principal_roles().roles:
@@ -97,17 +109,17 @@ class PrincipalRolesCommand(Command):
 
             request = UpdatePrincipalRoleRequest(
                 current_entity_version=principal_role.entity_version,
-                properties=new_properties
+                properties=new_properties,
             )
             api.update_principal_role(self.principal_role_name, request)
         elif self.principal_roles_subcommand == Subcommands.GRANT:
             request = GrantPrincipalRoleRequest(
-                principal_role=PrincipalRole(
-                    name=self.principal_role_name
-                ),
+                principal_role=PrincipalRole(name=self.principal_role_name),
             )
             api.assign_principal_role(self.principal_name, request)
         elif self.principal_roles_subcommand == Subcommands.REVOKE:
             api.revoke_principal_role(self.principal_name, 
self.principal_role_name)
         else:
-            raise Exception(f"{self.principal_roles_subcommand} is not 
supported in the CLI")
+            raise Exception(
+                f"{self.principal_roles_subcommand} is not supported in the 
CLI"
+            )
diff --git a/client/python/cli/command/principals.py 
b/client/python/cli/command/principals.py
index a25c2a0c1..58779f77d 100644
--- a/client/python/cli/command/principals.py
+++ b/client/python/cli/command/principals.py
@@ -24,7 +24,13 @@ from pydantic import StrictStr
 
 from cli.command import Command
 from cli.constants import Subcommands
-from polaris.management import PolarisDefaultApi, CreatePrincipalRequest, 
Principal, PrincipalWithCredentials, UpdatePrincipalRequest
+from polaris.management import (
+    PolarisDefaultApi,
+    CreatePrincipalRequest,
+    Principal,
+    PrincipalWithCredentials,
+    UpdatePrincipalRequest,
+)
 
 
 @dataclass
@@ -52,23 +58,40 @@ class PrincipalsCommand(Command):
 
     def _get_catalogs(self, api: PolarisDefaultApi):
         for catalog in api.list_catalogs().catalogs:
-            yield catalog.to_dict()['name']
-    
+            yield catalog.to_dict()["name"]
+
     def _get_principal_roles(self, api: PolarisDefaultApi):
-        for principal_role in 
api.list_principal_roles_assigned(self.principal_name).roles:
-            yield principal_role.to_dict()['name']
-    
-    def _get_catalog_roles(self, api: PolarisDefaultApi, principal_role_name: 
str, catalog_name: str):
-        for catalog_role in 
api.list_catalog_roles_for_principal_role(principal_role_name, 
catalog_name).roles:
-            yield catalog_role.to_dict()['name']
-    
-    def _get_privileges(self, api: PolarisDefaultApi, catalog_name: str, 
catalog_role_name: str):
-        for grant in api.list_grants_for_catalog_role(catalog_name, 
catalog_role_name).grants:
+        for principal_role in api.list_principal_roles_assigned(
+            self.principal_name
+        ).roles:
+            yield principal_role.to_dict()["name"]
+
+    def _get_catalog_roles(
+        self, api: PolarisDefaultApi, principal_role_name: str, catalog_name: 
str
+    ):
+        for catalog_role in api.list_catalog_roles_for_principal_role(
+            principal_role_name, catalog_name
+        ).roles:
+            yield catalog_role.to_dict()["name"]
+
+    def _get_privileges(
+        self, api: PolarisDefaultApi, catalog_name: str, catalog_role_name: str
+    ):
+        for grant in api.list_grants_for_catalog_role(
+            catalog_name, catalog_role_name
+        ).grants:
             yield grant.to_dict()
 
-    def build_credential_json(self, principal_with_credentials: 
PrincipalWithCredentials):
+    def build_credential_json(
+        self, principal_with_credentials: PrincipalWithCredentials
+    ):
         credentials = principal_with_credentials.credentials
-        return json.dumps({"clientId": credentials.client_id, "clientSecret": 
credentials.client_secret.get_secret_value()})
+        return json.dumps(
+            {
+                "clientId": credentials.client_id,
+                "clientSecret": credentials.client_secret.get_secret_value(),
+            }
+        )
 
     def validate(self):
         pass
@@ -80,7 +103,7 @@ class PrincipalsCommand(Command):
                     type=self.type.upper(),
                     name=self.principal_name,
                     client_id=self.client_id,
-                    properties=self.properties
+                    properties=self.properties,
                 )
             )
             print(self.build_credential_json(api.create_principal(request)))
@@ -90,13 +113,17 @@ class PrincipalsCommand(Command):
             print(api.get_principal(self.principal_name).to_json())
         elif self.principals_subcommand == Subcommands.LIST:
             if self.principal_role:
-                for principal in 
api.list_assignee_principals_for_principal_role(self.principal_role).principals:
+                for principal in 
api.list_assignee_principals_for_principal_role(
+                    self.principal_role
+                ).principals:
                     print(principal.to_json())
             else:
                 for principal in api.list_principals().principals:
                     print(principal.to_json())
         elif self.principals_subcommand == Subcommands.ROTATE_CREDENTIALS:
-            
print(self.build_credential_json(api.rotate_credentials(self.principal_name)))
+            print(
+                
self.build_credential_json(api.rotate_credentials(self.principal_name))
+            )
         elif self.principals_subcommand == Subcommands.UPDATE:
             principal = api.get_principal(self.principal_name)
             new_properties = principal.properties or {}
@@ -112,37 +139,37 @@ class PrincipalsCommand(Command):
 
             request = UpdatePrincipalRequest(
                 current_entity_version=principal.entity_version,
-                properties=new_properties
+                properties=new_properties,
             )
             api.update_principal(self.principal_name, request)
         elif self.principals_subcommand == Subcommands.ACCESS:
-            principal = 
api.get_principal(self.principal_name).to_dict()['name']
+            principal = 
api.get_principal(self.principal_name).to_dict()["name"]
             principal_roles = self._get_principal_roles(api)
 
             # Initialize the result structure
-            result = {
-                'principal': principal,
-                'principal_roles': []
-            }
-            
+            result = {"principal": principal, "principal_roles": []}
+
             # Construct the result structure for each principal role
             for principal_role in principal_roles:
-                role_data = {
-                    'name': principal_role,
-                    'catalog_roles': []
-                }
+                role_data = {"name": principal_role, "catalog_roles": []}
                 # For each catalog role, get associated privileges
                 for catalog in self._get_catalogs(api):
-                    catalog_roles = self._get_catalog_roles(api, 
principal_role, catalog)
+                    catalog_roles = self._get_catalog_roles(
+                        api, principal_role, catalog
+                    )
                     for catalog_role in catalog_roles:
                         catalog_data = {
-                            'name': catalog_role,
-                            'catalog': catalog,
-                            'privileges': []
+                            "name": catalog_role,
+                            "catalog": catalog,
+                            "privileges": [],
                         }
-                        catalog_data['privileges'] = 
list(self._get_privileges(api, catalog_data['catalog'], catalog_role))
-                        role_data['catalog_roles'].append(catalog_data)
-                result['principal_roles'].append(role_data)
+                        catalog_data["privileges"] = list(
+                            self._get_privileges(
+                                api, catalog_data["catalog"], catalog_role
+                            )
+                        )
+                        role_data["catalog_roles"].append(catalog_data)
+                result["principal_roles"].append(role_data)
             print(json.dumps(result))
         else:
             raise Exception(f"{self.principals_subcommand} is not supported in 
the CLI")
diff --git a/client/python/cli/command/privileges.py 
b/client/python/cli/command/privileges.py
index c7db21555..6cd19913f 100644
--- a/client/python/cli/command/privileges.py
+++ b/client/python/cli/command/privileges.py
@@ -24,9 +24,19 @@ from pydantic import StrictStr
 from cli.command import Command
 from cli.constants import Subcommands, Actions, Arguments
 from cli.options.option_tree import Argument
-from polaris.management import PolarisDefaultApi, AddGrantRequest, 
NamespaceGrant, \
-    RevokeGrantRequest, CatalogGrant, TableGrant, ViewGrant, CatalogPrivilege, 
NamespacePrivilege, TablePrivilege, \
-    ViewPrivilege
+from polaris.management import (
+    PolarisDefaultApi,
+    AddGrantRequest,
+    NamespaceGrant,
+    RevokeGrantRequest,
+    CatalogGrant,
+    TableGrant,
+    ViewGrant,
+    CatalogPrivilege,
+    NamespacePrivilege,
+    TablePrivilege,
+    ViewPrivilege,
+)
 
 
 @dataclass
@@ -54,75 +64,87 @@ class PrivilegesCommand(Command):
 
     def validate(self):
         if not self.catalog_name:
-            raise Exception(f'Missing required argument: 
{Argument.to_flag_name(Arguments.CATALOG)}')
+            raise Exception(
+                f"Missing required argument: 
{Argument.to_flag_name(Arguments.CATALOG)}"
+            )
         if not self.catalog_role_name:
-            raise Exception(f'Missing required argument: 
{Argument.to_flag_name(Arguments.CATALOG_ROLE)}')
+            raise Exception(
+                f"Missing required argument: 
{Argument.to_flag_name(Arguments.CATALOG_ROLE)}"
+            )
 
         if not self.privileges_subcommand:
-            raise Exception('A subcommand must be provided')
-        if (self.privileges_subcommand in {Subcommands.NAMESPACE, 
Subcommands.TABLE, Subcommands.VIEW}
-                and not self.namespace):
-            raise Exception(f'Missing required argument: 
{Argument.to_flag_name(Arguments.NAMESPACE)}')
+            raise Exception("A subcommand must be provided")
+        if (
+            self.privileges_subcommand
+            in {Subcommands.NAMESPACE, Subcommands.TABLE, Subcommands.VIEW}
+            and not self.namespace
+        ):
+            raise Exception(
+                f"Missing required argument: 
{Argument.to_flag_name(Arguments.NAMESPACE)}"
+            )
 
         if self.action == Actions.GRANT and self.cascade:
-            raise Exception('Unrecognized argument for GRANT: --cascade')
+            raise Exception("Unrecognized argument for GRANT: --cascade")
 
         if self.privileges_subcommand == Subcommands.CATALOG:
             if self.privilege not in {i.value for i in CatalogPrivilege}:
-                raise Exception(f'Invalid catalog privilege: {self.privilege}')
+                raise Exception(f"Invalid catalog privilege: {self.privilege}")
         if self.privileges_subcommand == Subcommands.NAMESPACE:
             if self.privilege not in {i.value for i in NamespacePrivilege}:
-                raise Exception(f'Invalid namespace privilege: 
{self.privilege}')
+                raise Exception(f"Invalid namespace privilege: 
{self.privilege}")
         if self.privileges_subcommand == Subcommands.TABLE:
             if self.privilege not in {i.value for i in TablePrivilege}:
-                raise Exception(f'Invalid table privilege: {self.privilege}')
+                raise Exception(f"Invalid table privilege: {self.privilege}")
         if self.privileges_subcommand == Subcommands.VIEW:
             if self.privilege not in {i.value for i in ViewPrivilege}:
-                raise Exception(f'Invalid view privilege: {self.privilege}')
+                raise Exception(f"Invalid view privilege: {self.privilege}")
 
     def execute(self, api: PolarisDefaultApi) -> None:
         if self.privileges_subcommand == Subcommands.LIST:
-            for grant in api.list_grants_for_catalog_role(self.catalog_name, 
self.catalog_role_name).grants:
+            for grant in api.list_grants_for_catalog_role(
+                self.catalog_name, self.catalog_role_name
+            ).grants:
                 print(grant.to_json())
         else:
             grant = None
             if self.privileges_subcommand == Subcommands.CATALOG:
                 grant = CatalogGrant(
-                    type=Subcommands.CATALOG,
-                    privilege=CatalogPrivilege(self.privilege)
+                    type=Subcommands.CATALOG, 
privilege=CatalogPrivilege(self.privilege)
                 )
             elif self.privileges_subcommand == Subcommands.NAMESPACE:
                 grant = NamespaceGrant(
                     type=Subcommands.NAMESPACE,
                     namespace=self.namespace,
-                    privilege=NamespacePrivilege(self.privilege)
+                    privilege=NamespacePrivilege(self.privilege),
                 )
             elif self.privileges_subcommand == Subcommands.TABLE:
                 grant = TableGrant(
                     type=Subcommands.TABLE,
                     namespace=self.namespace,
                     table_name=self.table,
-                    privilege=TablePrivilege(self.privilege)
+                    privilege=TablePrivilege(self.privilege),
                 )
             elif self.privileges_subcommand == Subcommands.VIEW:
                 grant = ViewGrant(
                     type=Subcommands.VIEW,
                     namespace=self.namespace,
                     view_name=self.view,
-                    privilege=ViewPrivilege(self.privilege)
+                    privilege=ViewPrivilege(self.privilege),
                 )
 
             if not grant:
-                raise Exception(f'{self.privileges_subcommand} is not 
supported in the CLI')
+                raise Exception(
+                    f"{self.privileges_subcommand} is not supported in the CLI"
+                )
             elif self.action == Actions.GRANT:
-                request = AddGrantRequest(
-                    grant=grant
+                request = AddGrantRequest(grant=grant)
+                api.add_grant_to_catalog_role(
+                    self.catalog_name, self.catalog_role_name, request
                 )
-                api.add_grant_to_catalog_role(self.catalog_name, 
self.catalog_role_name, request)
             elif self.action == Actions.REVOKE:
-                request = RevokeGrantRequest(
-                    grant=grant
+                request = RevokeGrantRequest(grant=grant)
+                api.revoke_grant_from_catalog_role(
+                    self.catalog_name, self.catalog_role_name, self.cascade, 
request
                 )
-                api.revoke_grant_from_catalog_role(self.catalog_name, 
self.catalog_role_name, self.cascade, request)
             else:
-                raise Exception(f'{self.action} is not supported in the CLI')
+                raise Exception(f"{self.action} is not supported in the CLI")
diff --git a/client/python/cli/command/profiles.py 
b/client/python/cli/command/profiles.py
index 8679907b0..fc689ae2b 100644
--- a/client/python/cli/command/profiles.py
+++ b/client/python/cli/command/profiles.py
@@ -24,7 +24,13 @@ from typing import Dict, Optional, List
 
 
 from cli.command import Command
-from cli.constants import Subcommands, DEFAULT_HOSTNAME, DEFAULT_PORT, 
CONFIG_DIR, CONFIG_FILE
+from cli.constants import (
+    Subcommands,
+    DEFAULT_HOSTNAME,
+    DEFAULT_PORT,
+    CONFIG_DIR,
+    CONFIG_FILE,
+)
 from polaris.management import PolarisDefaultApi
 
 
@@ -61,20 +67,20 @@ class ProfilesCommand(Command):
     def _create_profile(self, name: str) -> None:
         profiles = self._load_profiles()
         if name not in profiles:
-          client_id = input("Polaris Client ID: ")
-          client_secret = input("Polaris Client Secret: ")
-          host = input(f"Polaris Host [{DEFAULT_HOSTNAME}]: ") or 
DEFAULT_HOSTNAME
-          port = input(f"Polaris Port [{DEFAULT_PORT}]: ") or DEFAULT_PORT
-          profiles[name] = {
-              "client_id": client_id,
-              "client_secret": client_secret,
-              "host": host,
-              "port": port
-          }
-          self._save_profiles(profiles)
+            client_id = input("Polaris Client ID: ")
+            client_secret = input("Polaris Client Secret: ")
+            host = input(f"Polaris Host [{DEFAULT_HOSTNAME}]: ") or 
DEFAULT_HOSTNAME
+            port = input(f"Polaris Port [{DEFAULT_PORT}]: ") or DEFAULT_PORT
+            profiles[name] = {
+                "client_id": client_id,
+                "client_secret": client_secret,
+                "host": host,
+                "port": port,
+            }
+            self._save_profiles(profiles)
         else:
-          print(f"Profile {name} already exists.")
-          sys.exit(1)
+            print(f"Profile {name} already exists.")
+            sys.exit(1)
 
     def _get_profile(self, name: str) -> Optional[Dict[str, str]]:
         profiles = self._load_profiles()
@@ -98,15 +104,20 @@ class ProfilesCommand(Command):
             current_host = profiles[name].get("host")
             current_port = profiles[name].get("port")
 
-            client_id = input(f"Polaris Client ID [{current_client_id}]: ") or 
current_client_id
-            client_secret = input(f"Polaris Client Secret 
[{current_client_secret}]: ") or current_client_secret
+            client_id = (
+                input(f"Polaris Client ID [{current_client_id}]: ") or 
current_client_id
+            )
+            client_secret = (
+                input(f"Polaris Client Secret [{current_client_secret}]: ")
+                or current_client_secret
+            )
             host = input(f"Polaris Client ID [{current_host}]: ") or 
current_host
             port = input(f"Polaris Client Secret [{current_port}]: ") or 
current_port
             profiles[name] = {
                 "client_id": client_id,
                 "client_secret": client_secret,
                 "host": host,
-                "port": port
+                "port": port,
             }
             self._save_profiles(profiles)
         else:
@@ -125,7 +136,7 @@ class ProfilesCommand(Command):
             print(f"Polaris profile {self.profile_name} deleted successfully.")
         elif self.profiles_subcommand == Subcommands.UPDATE:
             self._update_profile(self.profile_name)
-            print(f"Polaris profile {self.profile_name} updated 
successfully.")            
+            print(f"Polaris profile {self.profile_name} updated successfully.")
         elif self.profiles_subcommand == Subcommands.GET:
             profile = self._get_profile(self.profile_name)
             if profile:
diff --git a/client/python/cli/constants.py b/client/python/cli/constants.py
index c9f28ba3c..91fff0dc0 100644
--- a/client/python/cli/constants.py
+++ b/client/python/cli/constants.py
@@ -25,10 +25,10 @@ class StorageType(Enum):
     Represents a Storage Type within the Polaris API -- `s3`, `azure`, `gcs`, 
or `file`.
     """
 
-    S3 = 's3'
-    AZURE = 'azure'
-    GCS = 'gcs'
-    FILE = 'file'
+    S3 = "s3"
+    AZURE = "azure"
+    GCS = "gcs"
+    FILE = "file"
 
 
 class CatalogType(Enum):
@@ -36,8 +36,8 @@ class CatalogType(Enum):
     Represents a Catalog Type within the Polaris API -- `internal` or 
`external`
     """
 
-    INTERNAL = 'internal'
-    EXTERNAL = 'external'
+    INTERNAL = "internal"
+    EXTERNAL = "external"
 
 
 class PrincipalType(Enum):
@@ -45,7 +45,7 @@ class PrincipalType(Enum):
     Represents a Principal Type within the Polaris API -- currently only 
`service`
     """
 
-    SERVICE = 'service'
+    SERVICE = "service"
 
 
 class Commands:
@@ -53,13 +53,13 @@ class Commands:
     Represents the various commands available in the CLI
     """
 
-    CATALOGS = 'catalogs'
-    PRINCIPALS = 'principals'
-    PRINCIPAL_ROLES = 'principal-roles'
-    CATALOG_ROLES = 'catalog-roles'
-    PRIVILEGES = 'privileges'
-    NAMESPACES = 'namespaces'
-    PROFILES = 'profiles'
+    CATALOGS = "catalogs"
+    PRINCIPALS = "principals"
+    PRINCIPAL_ROLES = "principal-roles"
+    CATALOG_ROLES = "catalog-roles"
+    PRIVILEGES = "privileges"
+    NAMESPACES = "namespaces"
+    PROFILES = "profiles"
 
 
 class Subcommands:
@@ -68,19 +68,19 @@ class Subcommands:
     all these subcommands.
     """
 
-    CREATE = 'create'
-    DELETE = 'delete'
-    GET = 'get'
-    LIST = 'list'
-    UPDATE = 'update'
-    ROTATE_CREDENTIALS = 'rotate-credentials'
-    CATALOG = 'catalog'
-    NAMESPACE = 'namespace'
-    TABLE = 'table'
-    VIEW = 'view'
-    GRANT = 'grant'
-    REVOKE = 'revoke'
-    ACCESS = 'access'
+    CREATE = "create"
+    DELETE = "delete"
+    GET = "get"
+    LIST = "list"
+    UPDATE = "update"
+    ROTATE_CREDENTIALS = "rotate-credentials"
+    CATALOG = "catalog"
+    NAMESPACE = "namespace"
+    TABLE = "table"
+    VIEW = "view"
+    GRANT = "grant"
+    REVOKE = "revoke"
+    ACCESS = "access"
 
 
 class Actions:
@@ -89,8 +89,8 @@ class Actions:
     `privileges` command support actions.
     """
 
-    GRANT = 'grant'
-    REVOKE = 'revoke'
+    GRANT = "grant"
+    REVOKE = "revoke"
 
 
 class Arguments:
@@ -102,40 +102,40 @@ class Arguments:
     These values should be snake_case, but they will get mapped to kebab-case 
in `Parser.parse`
     """
 
-    TYPE = 'type'
-    DEFAULT_BASE_LOCATION = 'default_base_location'
-    STORAGE_TYPE = 'storage_type'
-    ALLOWED_LOCATION = 'allowed_location'
-    ROLE_ARN = 'role_arn'
-    EXTERNAL_ID = 'external_id'
-    USER_ARN = 'user_arn'
-    TENANT_ID = 'tenant_id'
-    MULTI_TENANT_APP_NAME = 'multi_tenant_app_name'
-    CONSENT_URL = 'consent_url'
-    SERVICE_ACCOUNT = 'service_account'
-    CATALOG_ROLE = 'catalog_role'
-    CATALOG = 'catalog'
-    PRINCIPAL = 'principal'
-    CLIENT_ID = 'client_id'
-    PRINCIPAL_ROLE = 'principal_role'
-    PROPERTY = 'property'
-    SET_PROPERTY = 'set_property'
-    REMOVE_PROPERTY = 'remove_property'
-    PRIVILEGE = 'privilege'
-    NAMESPACE = 'namespace'
-    TABLE = 'table'
-    VIEW = 'view'
-    CASCADE = 'cascade'
-    CLIENT_SECRET = 'client_secret'
-    ACCESS_TOKEN = 'access_token'
-    HOST = 'host'
-    PORT = 'port'
-    BASE_URL = 'base_url'
-    PARENT = 'parent'
-    LOCATION = 'location'
-    REGION = 'region'
-    PROFILE = 'profile'
-    PROXY = 'proxy'
+    TYPE = "type"
+    DEFAULT_BASE_LOCATION = "default_base_location"
+    STORAGE_TYPE = "storage_type"
+    ALLOWED_LOCATION = "allowed_location"
+    ROLE_ARN = "role_arn"
+    EXTERNAL_ID = "external_id"
+    USER_ARN = "user_arn"
+    TENANT_ID = "tenant_id"
+    MULTI_TENANT_APP_NAME = "multi_tenant_app_name"
+    CONSENT_URL = "consent_url"
+    SERVICE_ACCOUNT = "service_account"
+    CATALOG_ROLE = "catalog_role"
+    CATALOG = "catalog"
+    PRINCIPAL = "principal"
+    CLIENT_ID = "client_id"
+    PRINCIPAL_ROLE = "principal_role"
+    PROPERTY = "property"
+    SET_PROPERTY = "set_property"
+    REMOVE_PROPERTY = "remove_property"
+    PRIVILEGE = "privilege"
+    NAMESPACE = "namespace"
+    TABLE = "table"
+    VIEW = "view"
+    CASCADE = "cascade"
+    CLIENT_SECRET = "client_secret"
+    ACCESS_TOKEN = "access_token"
+    HOST = "host"
+    PORT = "port"
+    BASE_URL = "base_url"
+    PARENT = "parent"
+    LOCATION = "location"
+    REGION = "region"
+    PROFILE = "profile"
+    PROXY = "proxy"
 
 
 class Hints:
@@ -145,97 +145,119 @@ class Hints:
     parameter used by `catalog-roles create` and `catalog-roles delete` may be 
the same.
     """
 
-    PROPERTY = ('A key/value pair such as: tag=value. Multiple can be provided 
by specifying this option'
-                ' more than once')
-    SET_PROPERTY = ('A key/value pair such as: tag=value. Merges the specified 
key/value into an existing'
-                    ' properties map by updating the value if the key already 
exists or creating a new'
-                    ' entry if not. Multiple can be provided by specifying 
this option more than once')
-    REMOVE_PROPERTY = ('A key to remove from a properties map. If the key 
already does not exist then'
-                       ' no action is takn for the specified key. If 
properties are also being set in'
-                       ' the same update command then the list of removals is 
applied last. Multiple'
-                       ' can be provided by specifying this option more than 
once')
+    PROPERTY = (
+        "A key/value pair such as: tag=value. Multiple can be provided by 
specifying this option"
+        " more than once"
+    )
+    SET_PROPERTY = (
+        "A key/value pair such as: tag=value. Merges the specified key/value 
into an existing"
+        " properties map by updating the value if the key already exists or 
creating a new"
+        " entry if not. Multiple can be provided by specifying this option 
more than once"
+    )
+    REMOVE_PROPERTY = (
+        "A key to remove from a properties map. If the key already does not 
exist then"
+        " no action is takn for the specified key. If properties are also 
being set in"
+        " the same update command then the list of removals is applied last. 
Multiple"
+        " can be provided by specifying this option more than once"
+    )
 
     class Catalogs:
-        GRANT = 'Grant a catalog role to a catalog'
-        REVOKE = 'Revoke a catalog role from a catalog'
+        GRANT = "Grant a catalog role to a catalog"
+        REVOKE = "Revoke a catalog role from a catalog"
 
         class Create:
-            TYPE = 'The type of catalog to create in [INTERNAL, EXTERNAL]. 
INTERNAL by default.'
-            DEFAULT_BASE_LOCATION = '(Required) Default base location of the 
catalog'
-            STORAGE_TYPE = '(Required) The type of storage to use for the 
catalog'
-            ALLOWED_LOCATION = ('An allowed location for files tracked by the 
catalog. '
-                                'Multiple locations can be provided by 
specifying this option more than once.')
-
-            ROLE_ARN = '(Required for S3) A role ARN to use when connecting to 
S3'
-            EXTERNAL_ID = '(Only for S3) The external ID to use when 
connecting to S3'
-            REGION = '(Only for S3) The region to use when connecting to S3'
-            USER_ARN = '(Only for S3) A user ARN to use when connecting to S3'
-
-            TENANT_ID = '(Required for Azure) A tenant ID to use when 
connecting to Azure Storage'
-            MULTI_TENANT_APP_NAME = '(Only for Azure) The app name to use when 
connecting to Azure Storage'
-            CONSENT_URL = '(Only for Azure) A consent URL granting permissions 
for the Azure Storage location'
-
-            SERVICE_ACCOUNT = '(Only for GCS) The service account to use when 
connecting to GCS'
+            TYPE = "The type of catalog to create in [INTERNAL, EXTERNAL]. 
INTERNAL by default."
+            DEFAULT_BASE_LOCATION = "(Required) Default base location of the 
catalog"
+            STORAGE_TYPE = "(Required) The type of storage to use for the 
catalog"
+            ALLOWED_LOCATION = (
+                "An allowed location for files tracked by the catalog. "
+                "Multiple locations can be provided by specifying this option 
more than once."
+            )
+
+            ROLE_ARN = "(Required for S3) A role ARN to use when connecting to 
S3"
+            EXTERNAL_ID = "(Only for S3) The external ID to use when 
connecting to S3"
+            REGION = "(Only for S3) The region to use when connecting to S3"
+            USER_ARN = "(Only for S3) A user ARN to use when connecting to S3"
+
+            TENANT_ID = "(Required for Azure) A tenant ID to use when 
connecting to Azure Storage"
+            MULTI_TENANT_APP_NAME = (
+                "(Only for Azure) The app name to use when connecting to Azure 
Storage"
+            )
+            CONSENT_URL = "(Only for Azure) A consent URL granting permissions 
for the Azure Storage location"
+
+            SERVICE_ACCOUNT = (
+                "(Only for GCS) The service account to use when connecting to 
GCS"
+            )
 
         class Update:
-            DEFAULT_BASE_LOCATION = 'A new default base location for the 
catalog'
+            DEFAULT_BASE_LOCATION = "A new default base location for the 
catalog"
 
     class Principals:
         class Create:
-            TYPE = 'The type of principal to create in [SERVICE]'
-            NAME = 'The principal name'
-            CLIENT_ID = 'The output-only OAuth clientId associated with this 
principal if applicable'
+            TYPE = "The type of principal to create in [SERVICE]"
+            NAME = "The principal name"
+            CLIENT_ID = "The output-only OAuth clientId associated with this 
principal if applicable"
 
         class Revoke:
-            PRINCIPAL_ROLE = 'A principal role to revoke from this principal'
+            PRINCIPAL_ROLE = "A principal role to revoke from this principal"
 
     class PrincipalRoles:
-        PRINCIPAL_ROLE = 'The name of a principal role'
-        LIST = 'List principal roles, optionally limited to those held a given 
principal'
+        PRINCIPAL_ROLE = "The name of a principal role"
+        LIST = (
+            "List principal roles, optionally limited to those held a given 
principal"
+        )
 
-        GRANT = 'Grant a principal role to a principal'
-        REVOKE = 'Revoke a principal role from a principal'
+        GRANT = "Grant a principal role to a principal"
+        REVOKE = "Revoke a principal role from a principal"
 
         class Grant:
-            PRINCIPAL = 'A principal to grant this principal role to'
+            PRINCIPAL = "A principal to grant this principal role to"
 
         class Revoke:
-            PRINCIPAL = 'A principal to revoke this principal role from'
+            PRINCIPAL = "A principal to revoke this principal role from"
 
         class List:
-            CATALOG_ROLE = ('The name of a catalog role. If provided, show 
only principal roles assigned to this'
-                            ' catalog role.')
-            PRINCIPAL_NAME = ('The name of a principal. If provided, show only 
principal roles assigned to this'
-                              ' principal.')
+            CATALOG_ROLE = (
+                "The name of a catalog role. If provided, show only principal 
roles assigned to this"
+                " catalog role."
+            )
+            PRINCIPAL_NAME = (
+                "The name of a principal. If provided, show only principal 
roles assigned to this"
+                " principal."
+            )
 
     class CatalogRoles:
-        CATALOG_NAME = 'The name of an existing catalog'
-        CATALOG_ROLE = 'The name of a catalog role'
-        LIST = 'List catalog roles within a catalog. Optionally, specify a 
principal role.'
-        REVOKE_CATALOG_ROLE = 'Revoke a catalog role from a principal role'
-        GRANT_CATALOG_ROLE = 'Grant a catalog role to a principal role'
+        CATALOG_NAME = "The name of an existing catalog"
+        CATALOG_ROLE = "The name of a catalog role"
+        LIST = (
+            "List catalog roles within a catalog. Optionally, specify a 
principal role."
+        )
+        REVOKE_CATALOG_ROLE = "Revoke a catalog role from a principal role"
+        GRANT_CATALOG_ROLE = "Grant a catalog role to a principal role"
 
     class Grant:
-        CATALOG_NAME = 'The name of a catalog'
-        CATALOG_ROLE = 'The name of a catalog role'
-        PRIVILEGE = 'The privilege to grant or revoke'
-        NAMESPACE = 'A period-delimited namespace'
-        TABLE = 'The name of a table'
-        VIEW = 'The name of a view'
-        CASCADE = 'When revoking privileges, additionally revoke privileges 
that depend on the specified privilege'
+        CATALOG_NAME = "The name of a catalog"
+        CATALOG_ROLE = "The name of a catalog role"
+        PRIVILEGE = "The privilege to grant or revoke"
+        NAMESPACE = "A period-delimited namespace"
+        TABLE = "The name of a table"
+        VIEW = "The name of a view"
+        CASCADE = "When revoking privileges, additionally revoke privileges 
that depend on the specified privilege"
 
     class Namespaces:
-        LOCATION = 'If specified, the location at which to store the namespace 
and entities inside it'
-        PARENT = 'If specified, list namespaces inside this parent namespace'
+        LOCATION = "If specified, the location at which to store the namespace 
and entities inside it"
+        PARENT = "If specified, list namespaces inside this parent namespace"
 
 
 UNIT_SEPARATOR = chr(0x1F)
-CLIENT_ID_ENV = 'CLIENT_ID'
-CLIENT_SECRET_ENV = 'CLIENT_SECRET'
-CLIENT_PROFILE_ENV = 'CLIENT_PROFILE'
-DEFAULT_HOSTNAME = 'localhost'
+CLIENT_ID_ENV = "CLIENT_ID"
+CLIENT_SECRET_ENV = "CLIENT_SECRET"
+CLIENT_PROFILE_ENV = "CLIENT_PROFILE"
+DEFAULT_HOSTNAME = "localhost"
 DEFAULT_PORT = 8181
-CONFIG_DIR = os.environ.get('SCRIPT_DIR')
+CONFIG_DIR = os.environ.get("SCRIPT_DIR")
 if CONFIG_DIR is None:
-    raise Exception("The SCRIPT_DIR environment variable is not set. Please 
set it to the Polaris's script directory.")
-CONFIG_FILE = os.path.join(CONFIG_DIR, '.polaris.json')
+    raise Exception(
+        "The SCRIPT_DIR environment variable is not set. Please set it to the 
Polaris's script directory."
+    )
+CONFIG_FILE = os.path.join(CONFIG_DIR, ".polaris.json")
diff --git a/client/python/cli/options/option_tree.py 
b/client/python/cli/options/option_tree.py
index f926230e1..aaec9476d 100644
--- a/client/python/cli/options/option_tree.py
+++ b/client/python/cli/options/option_tree.py
@@ -19,7 +19,16 @@
 from dataclasses import dataclass, field
 from typing import List
 
-from cli.constants import StorageType, CatalogType, PrincipalType, Hints, 
Commands, Arguments, Subcommands, Actions
+from cli.constants import (
+    StorageType,
+    CatalogType,
+    PrincipalType,
+    Hints,
+    Commands,
+    Arguments,
+    Subcommands,
+    Actions,
+)
 
 
 @dataclass
@@ -37,18 +46,19 @@ class Argument:
     default: object = None
 
     def __post_init__(self):
-        if self.name.startswith('--'):
-            raise Exception(f'Argument name {self.name} starts with `--`: 
should this be a flag_name?')
+        if self.name.startswith("--"):
+            raise Exception(
+                f"Argument name {self.name} starts with `--`: should this be a 
flag_name?"
+            )
 
     @staticmethod
     def to_flag_name(argument_name):
-        return '--' + argument_name.replace('_', '-')
+        return "--" + argument_name.replace("_", "-")
 
     def get_flag_name(self):
         return Argument.to_flag_name(self.name)
 
 
-
 @dataclass
 class Option:
     """
@@ -60,7 +70,7 @@ class Option:
     hint: str = None
     input_name: str = None
     args: List[Argument] = field(default_factory=list)
-    children: List['Option'] = field(default_factory=list)
+    children: List["Option"] = field(default_factory=list)
 
 
 class OptionTree:
@@ -71,171 +81,537 @@ class OptionTree:
 
     _CATALOG_ROLE_AND_CATALOG = [
         Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
-        Argument(Arguments.CATALOG_ROLE, str, Hints.CatalogRoles.CATALOG_ROLE)
+        Argument(Arguments.CATALOG_ROLE, str, Hints.CatalogRoles.CATALOG_ROLE),
     ]
 
     @staticmethod
     def get_tree() -> List[Option]:
         return [
-            Option(Commands.CATALOGS, 'manage catalogs', children=[
-                Option(Subcommands.CREATE, args=[
-                    Argument(Arguments.TYPE, str, Hints.Catalogs.Create.TYPE, 
lower=True,
-                             choices=[ct.value for ct in CatalogType], 
default=CatalogType.INTERNAL.value),
-                    Argument(Arguments.STORAGE_TYPE, str, 
Hints.Catalogs.Create.STORAGE_TYPE, lower=True,
-                             choices=[st.value for st in StorageType]),
-                    Argument(Arguments.DEFAULT_BASE_LOCATION, str, 
Hints.Catalogs.Create.DEFAULT_BASE_LOCATION),
-                    Argument(Arguments.ALLOWED_LOCATION, str, 
Hints.Catalogs.Create.ALLOWED_LOCATION,
-                             allow_repeats=True),
-                    Argument(Arguments.ROLE_ARN, str, 
Hints.Catalogs.Create.ROLE_ARN),
-                    Argument(Arguments.REGION, str, 
Hints.Catalogs.Create.REGION),
-                    Argument(Arguments.EXTERNAL_ID, str, 
Hints.Catalogs.Create.EXTERNAL_ID),
-                    Argument(Arguments.TENANT_ID, str, 
Hints.Catalogs.Create.TENANT_ID),
-                    Argument(Arguments.MULTI_TENANT_APP_NAME, str, 
Hints.Catalogs.Create.MULTI_TENANT_APP_NAME),
-                    Argument(Arguments.CONSENT_URL, str, 
Hints.Catalogs.Create.CONSENT_URL),
-                    Argument(Arguments.SERVICE_ACCOUNT, str, 
Hints.Catalogs.Create.SERVICE_ACCOUNT),
-                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, 
allow_repeats=True),
-                ], input_name=Arguments.CATALOG),
-                Option(Subcommands.DELETE, input_name=Arguments.CATALOG),
-                Option(Subcommands.GET, input_name=Arguments.CATALOG),
-                Option(Subcommands.LIST, args=[
-                    Argument(Arguments.PRINCIPAL_ROLE, str, 
Hints.PrincipalRoles.PRINCIPAL_ROLE)
-                ]),
-                Option(Subcommands.UPDATE, args=[
-                    Argument(Arguments.DEFAULT_BASE_LOCATION, str, 
Hints.Catalogs.Update.DEFAULT_BASE_LOCATION),
-                    Argument(Arguments.ALLOWED_LOCATION, str, 
Hints.Catalogs.Create.ALLOWED_LOCATION,
-                             allow_repeats=True),
-                    Argument(Arguments.REGION, str, 
Hints.Catalogs.Create.REGION),
-                    Argument(Arguments.SET_PROPERTY, str, Hints.SET_PROPERTY, 
allow_repeats=True),
-                    Argument(Arguments.REMOVE_PROPERTY, str, 
Hints.REMOVE_PROPERTY, allow_repeats=True),
-                ], input_name=Arguments.CATALOG)
-            ]),
-            Option(Commands.PRINCIPALS, 'manage principals', children=[
-                Option(Subcommands.CREATE, args=[
-                    Argument(Arguments.TYPE, str, 
Hints.Principals.Create.TYPE, lower=True,
-                             choices=[pt.value for pt in PrincipalType], 
default=PrincipalType.SERVICE.value),
-                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, 
allow_repeats=True)
-                ], input_name=Arguments.PRINCIPAL),
-                Option(Subcommands.DELETE, input_name=Arguments.PRINCIPAL),
-                Option(Subcommands.GET, input_name=Arguments.PRINCIPAL),
-                Option(Subcommands.LIST),
-                Option(Subcommands.ROTATE_CREDENTIALS, 
input_name=Arguments.PRINCIPAL),
-                Option(Subcommands.UPDATE, args=[
-                    Argument(Arguments.SET_PROPERTY, str, Hints.SET_PROPERTY, 
allow_repeats=True),
-                    Argument(Arguments.REMOVE_PROPERTY, str, 
Hints.REMOVE_PROPERTY, allow_repeats=True),
-                ], input_name=Arguments.PRINCIPAL),
-                Option(Subcommands.ACCESS, input_name=Arguments.PRINCIPAL),
-            ]),
-            Option(Commands.PRINCIPAL_ROLES, 'manage principal roles', 
children=[
-                Option(Subcommands.CREATE, args=[
-                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, 
allow_repeats=True)
-                ], input_name=Arguments.PRINCIPAL_ROLE),
-                Option(Subcommands.DELETE, 
input_name=Arguments.PRINCIPAL_ROLE),
-                Option(Subcommands.GET, input_name=Arguments.PRINCIPAL_ROLE),
-                Option(Subcommands.LIST, hint=Hints.PrincipalRoles.LIST, args=[
-                    Argument(Arguments.CATALOG_ROLE, str, 
Hints.PrincipalRoles.List.CATALOG_ROLE),
-                    Argument(Arguments.PRINCIPAL, str, 
Hints.PrincipalRoles.List.PRINCIPAL_NAME)
-                ]),
-                Option(Subcommands.UPDATE, args=[
-                    Argument(Arguments.SET_PROPERTY, str, Hints.SET_PROPERTY, 
allow_repeats=True),
-                    Argument(Arguments.REMOVE_PROPERTY, str, 
Hints.REMOVE_PROPERTY, allow_repeats=True),
-                ], input_name=Arguments.PRINCIPAL_ROLE),
-                Option(Subcommands.GRANT, hint=Hints.PrincipalRoles.GRANT, 
args=[
-                    Argument(Arguments.PRINCIPAL, str, 
Hints.PrincipalRoles.Grant.PRINCIPAL)
-                ], input_name=Arguments.PRINCIPAL_ROLE),
-                Option(Subcommands.REVOKE, hint=Hints.PrincipalRoles.REVOKE, 
args=[
-                    Argument(Arguments.PRINCIPAL, str, 
Hints.PrincipalRoles.Revoke.PRINCIPAL)
-                ], input_name=Arguments.PRINCIPAL_ROLE)
-            ]),
-            Option(Commands.CATALOG_ROLES, 'manage catalog roles', children=[
-                Option(Subcommands.CREATE, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME),
-                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, 
allow_repeats=True)
-                ], input_name=Arguments.CATALOG_ROLE),
-                Option(Subcommands.DELETE, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME),
-                ], input_name=Arguments.CATALOG_ROLE),
-                Option(Subcommands.GET, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME),
-                ], input_name=Arguments.CATALOG_ROLE),
-                Option(Subcommands.LIST, hint=Hints.CatalogRoles.LIST, args=[
-                    Argument(Arguments.PRINCIPAL_ROLE, str, 
Hints.PrincipalRoles.PRINCIPAL_ROLE)
-                ], input_name=Arguments.CATALOG),
-                Option(Subcommands.UPDATE, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME),
-                    Argument(Arguments.SET_PROPERTY, str, Hints.SET_PROPERTY, 
allow_repeats=True),
-                    Argument(Arguments.REMOVE_PROPERTY, str, 
Hints.REMOVE_PROPERTY, allow_repeats=True),
-                ], input_name=Arguments.CATALOG_ROLE),
-                Option(Subcommands.GRANT, 
hint=Hints.CatalogRoles.GRANT_CATALOG_ROLE, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME),
-                    Argument(Arguments.PRINCIPAL_ROLE, str, 
Hints.CatalogRoles.CATALOG_ROLE)
-                ], input_name=Arguments.CATALOG_ROLE),
-                Option(Subcommands.REVOKE, 
hint=Hints.CatalogRoles.GRANT_CATALOG_ROLE, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME),
-                    Argument(Arguments.PRINCIPAL_ROLE, str, 
Hints.CatalogRoles.CATALOG_ROLE)
-                ], input_name=Arguments.CATALOG_ROLE)
-            ]),
-            Option(Commands.PRIVILEGES, 'manage privileges for a catalog 
role', children=[
-                Option(Subcommands.LIST, 
args=OptionTree._CATALOG_ROLE_AND_CATALOG),
-                Option(Subcommands.CATALOG, children=[
-                    Option(Actions.GRANT, 
args=OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
-                    Option(Actions.REVOKE, args=[
-                        Argument(Arguments.CASCADE, bool, Hints.Grant.CASCADE)
-                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, 
input_name=Arguments.PRIVILEGE),
-                ]),
-                Option(Subcommands.NAMESPACE, children=[
-                    Option(Actions.GRANT, args=[
-                        Argument(Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE)
-                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, 
input_name=Arguments.PRIVILEGE),
-                    Option(Actions.REVOKE, args=[
-                        Argument(Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE),
-                        Argument(Arguments.CASCADE, bool, Hints.Grant.CASCADE)
-                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, 
input_name=Arguments.PRIVILEGE),
-                ]),
-                Option(Subcommands.TABLE, children=[
-                    Option(Actions.GRANT, args=[
-                        Argument(Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE),
-                        Argument(Arguments.TABLE, str, Hints.Grant.TABLE)
-                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, 
input_name=Arguments.PRIVILEGE),
-                    Option(Actions.REVOKE, args=[
-                        Argument(Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE),
-                        Argument(Arguments.TABLE, str, Hints.Grant.TABLE),
-                        Argument(Arguments.CASCADE, bool, Hints.Grant.CASCADE)
-                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, 
input_name=Arguments.PRIVILEGE),
-                ]),
-                Option(Subcommands.VIEW, children=[
-                    Option(Actions.GRANT, args=[
-                        Argument(Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE),
-                        Argument(Arguments.VIEW, str, Hints.Grant.VIEW)
-                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, 
input_name=Arguments.PRIVILEGE),
-                    Option(Actions.REVOKE, args=[
-                        Argument(Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE),
-                        Argument(Arguments.VIEW, str, Hints.Grant.VIEW),
-                        Argument(Arguments.CASCADE, bool, Hints.Grant.CASCADE)
-                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, 
input_name=Arguments.PRIVILEGE),
-                ])
-            ]),
-            Option(Commands.NAMESPACES, 'manage namespaces', children=[
-                Option(Subcommands.CREATE, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME),
-                    Argument(Arguments.LOCATION, str, 
Hints.Namespaces.LOCATION),
-                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, 
allow_repeats=True)
-                ], input_name=Arguments.NAMESPACE),
-                Option(Subcommands.LIST, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME),
-                    Argument(Arguments.PARENT, str, Hints.Namespaces.PARENT)
-                ]),
-                Option(Subcommands.DELETE, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME)
-                ], input_name=Arguments.NAMESPACE),
-                Option(Subcommands.GET, args=[
-                    Argument(Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME)
-                ], input_name=Arguments.NAMESPACE),
-            ]),
-            Option(Commands.PROFILES, 'manage profiles', children=[
-                Option(Subcommands.CREATE, input_name=Arguments.PROFILE),
-                Option(Subcommands.DELETE, input_name=Arguments.PROFILE),
-                Option(Subcommands.UPDATE, input_name=Arguments.PROFILE),
-                Option(Subcommands.GET, input_name=Arguments.PROFILE),
-                Option(Subcommands.LIST),
-            ])
+            Option(
+                Commands.CATALOGS,
+                "manage catalogs",
+                children=[
+                    Option(
+                        Subcommands.CREATE,
+                        args=[
+                            Argument(
+                                Arguments.TYPE,
+                                str,
+                                Hints.Catalogs.Create.TYPE,
+                                lower=True,
+                                choices=[ct.value for ct in CatalogType],
+                                default=CatalogType.INTERNAL.value,
+                            ),
+                            Argument(
+                                Arguments.STORAGE_TYPE,
+                                str,
+                                Hints.Catalogs.Create.STORAGE_TYPE,
+                                lower=True,
+                                choices=[st.value for st in StorageType],
+                            ),
+                            Argument(
+                                Arguments.DEFAULT_BASE_LOCATION,
+                                str,
+                                Hints.Catalogs.Create.DEFAULT_BASE_LOCATION,
+                            ),
+                            Argument(
+                                Arguments.ALLOWED_LOCATION,
+                                str,
+                                Hints.Catalogs.Create.ALLOWED_LOCATION,
+                                allow_repeats=True,
+                            ),
+                            Argument(
+                                Arguments.ROLE_ARN, str, 
Hints.Catalogs.Create.ROLE_ARN
+                            ),
+                            Argument(
+                                Arguments.REGION, str, 
Hints.Catalogs.Create.REGION
+                            ),
+                            Argument(
+                                Arguments.EXTERNAL_ID,
+                                str,
+                                Hints.Catalogs.Create.EXTERNAL_ID,
+                            ),
+                            Argument(
+                                Arguments.TENANT_ID,
+                                str,
+                                Hints.Catalogs.Create.TENANT_ID,
+                            ),
+                            Argument(
+                                Arguments.MULTI_TENANT_APP_NAME,
+                                str,
+                                Hints.Catalogs.Create.MULTI_TENANT_APP_NAME,
+                            ),
+                            Argument(
+                                Arguments.CONSENT_URL,
+                                str,
+                                Hints.Catalogs.Create.CONSENT_URL,
+                            ),
+                            Argument(
+                                Arguments.SERVICE_ACCOUNT,
+                                str,
+                                Hints.Catalogs.Create.SERVICE_ACCOUNT,
+                            ),
+                            Argument(
+                                Arguments.PROPERTY,
+                                str,
+                                Hints.PROPERTY,
+                                allow_repeats=True,
+                            ),
+                        ],
+                        input_name=Arguments.CATALOG,
+                    ),
+                    Option(Subcommands.DELETE, input_name=Arguments.CATALOG),
+                    Option(Subcommands.GET, input_name=Arguments.CATALOG),
+                    Option(
+                        Subcommands.LIST,
+                        args=[
+                            Argument(
+                                Arguments.PRINCIPAL_ROLE,
+                                str,
+                                Hints.PrincipalRoles.PRINCIPAL_ROLE,
+                            )
+                        ],
+                    ),
+                    Option(
+                        Subcommands.UPDATE,
+                        args=[
+                            Argument(
+                                Arguments.DEFAULT_BASE_LOCATION,
+                                str,
+                                Hints.Catalogs.Update.DEFAULT_BASE_LOCATION,
+                            ),
+                            Argument(
+                                Arguments.ALLOWED_LOCATION,
+                                str,
+                                Hints.Catalogs.Create.ALLOWED_LOCATION,
+                                allow_repeats=True,
+                            ),
+                            Argument(
+                                Arguments.REGION, str, 
Hints.Catalogs.Create.REGION
+                            ),
+                            Argument(
+                                Arguments.SET_PROPERTY,
+                                str,
+                                Hints.SET_PROPERTY,
+                                allow_repeats=True,
+                            ),
+                            Argument(
+                                Arguments.REMOVE_PROPERTY,
+                                str,
+                                Hints.REMOVE_PROPERTY,
+                                allow_repeats=True,
+                            ),
+                        ],
+                        input_name=Arguments.CATALOG,
+                    ),
+                ],
+            ),
+            Option(
+                Commands.PRINCIPALS,
+                "manage principals",
+                children=[
+                    Option(
+                        Subcommands.CREATE,
+                        args=[
+                            Argument(
+                                Arguments.TYPE,
+                                str,
+                                Hints.Principals.Create.TYPE,
+                                lower=True,
+                                choices=[pt.value for pt in PrincipalType],
+                                default=PrincipalType.SERVICE.value,
+                            ),
+                            Argument(
+                                Arguments.PROPERTY,
+                                str,
+                                Hints.PROPERTY,
+                                allow_repeats=True,
+                            ),
+                        ],
+                        input_name=Arguments.PRINCIPAL,
+                    ),
+                    Option(Subcommands.DELETE, input_name=Arguments.PRINCIPAL),
+                    Option(Subcommands.GET, input_name=Arguments.PRINCIPAL),
+                    Option(Subcommands.LIST),
+                    Option(
+                        Subcommands.ROTATE_CREDENTIALS, 
input_name=Arguments.PRINCIPAL
+                    ),
+                    Option(
+                        Subcommands.UPDATE,
+                        args=[
+                            Argument(
+                                Arguments.SET_PROPERTY,
+                                str,
+                                Hints.SET_PROPERTY,
+                                allow_repeats=True,
+                            ),
+                            Argument(
+                                Arguments.REMOVE_PROPERTY,
+                                str,
+                                Hints.REMOVE_PROPERTY,
+                                allow_repeats=True,
+                            ),
+                        ],
+                        input_name=Arguments.PRINCIPAL,
+                    ),
+                    Option(Subcommands.ACCESS, input_name=Arguments.PRINCIPAL),
+                ],
+            ),
+            Option(
+                Commands.PRINCIPAL_ROLES,
+                "manage principal roles",
+                children=[
+                    Option(
+                        Subcommands.CREATE,
+                        args=[
+                            Argument(
+                                Arguments.PROPERTY,
+                                str,
+                                Hints.PROPERTY,
+                                allow_repeats=True,
+                            )
+                        ],
+                        input_name=Arguments.PRINCIPAL_ROLE,
+                    ),
+                    Option(Subcommands.DELETE, 
input_name=Arguments.PRINCIPAL_ROLE),
+                    Option(Subcommands.GET, 
input_name=Arguments.PRINCIPAL_ROLE),
+                    Option(
+                        Subcommands.LIST,
+                        hint=Hints.PrincipalRoles.LIST,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG_ROLE,
+                                str,
+                                Hints.PrincipalRoles.List.CATALOG_ROLE,
+                            ),
+                            Argument(
+                                Arguments.PRINCIPAL,
+                                str,
+                                Hints.PrincipalRoles.List.PRINCIPAL_NAME,
+                            ),
+                        ],
+                    ),
+                    Option(
+                        Subcommands.UPDATE,
+                        args=[
+                            Argument(
+                                Arguments.SET_PROPERTY,
+                                str,
+                                Hints.SET_PROPERTY,
+                                allow_repeats=True,
+                            ),
+                            Argument(
+                                Arguments.REMOVE_PROPERTY,
+                                str,
+                                Hints.REMOVE_PROPERTY,
+                                allow_repeats=True,
+                            ),
+                        ],
+                        input_name=Arguments.PRINCIPAL_ROLE,
+                    ),
+                    Option(
+                        Subcommands.GRANT,
+                        hint=Hints.PrincipalRoles.GRANT,
+                        args=[
+                            Argument(
+                                Arguments.PRINCIPAL,
+                                str,
+                                Hints.PrincipalRoles.Grant.PRINCIPAL,
+                            )
+                        ],
+                        input_name=Arguments.PRINCIPAL_ROLE,
+                    ),
+                    Option(
+                        Subcommands.REVOKE,
+                        hint=Hints.PrincipalRoles.REVOKE,
+                        args=[
+                            Argument(
+                                Arguments.PRINCIPAL,
+                                str,
+                                Hints.PrincipalRoles.Revoke.PRINCIPAL,
+                            )
+                        ],
+                        input_name=Arguments.PRINCIPAL_ROLE,
+                    ),
+                ],
+            ),
+            Option(
+                Commands.CATALOG_ROLES,
+                "manage catalog roles",
+                children=[
+                    Option(
+                        Subcommands.CREATE,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            ),
+                            Argument(
+                                Arguments.PROPERTY,
+                                str,
+                                Hints.PROPERTY,
+                                allow_repeats=True,
+                            ),
+                        ],
+                        input_name=Arguments.CATALOG_ROLE,
+                    ),
+                    Option(
+                        Subcommands.DELETE,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            ),
+                        ],
+                        input_name=Arguments.CATALOG_ROLE,
+                    ),
+                    Option(
+                        Subcommands.GET,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            ),
+                        ],
+                        input_name=Arguments.CATALOG_ROLE,
+                    ),
+                    Option(
+                        Subcommands.LIST,
+                        hint=Hints.CatalogRoles.LIST,
+                        args=[
+                            Argument(
+                                Arguments.PRINCIPAL_ROLE,
+                                str,
+                                Hints.PrincipalRoles.PRINCIPAL_ROLE,
+                            )
+                        ],
+                        input_name=Arguments.CATALOG,
+                    ),
+                    Option(
+                        Subcommands.UPDATE,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            ),
+                            Argument(
+                                Arguments.SET_PROPERTY,
+                                str,
+                                Hints.SET_PROPERTY,
+                                allow_repeats=True,
+                            ),
+                            Argument(
+                                Arguments.REMOVE_PROPERTY,
+                                str,
+                                Hints.REMOVE_PROPERTY,
+                                allow_repeats=True,
+                            ),
+                        ],
+                        input_name=Arguments.CATALOG_ROLE,
+                    ),
+                    Option(
+                        Subcommands.GRANT,
+                        hint=Hints.CatalogRoles.GRANT_CATALOG_ROLE,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            ),
+                            Argument(
+                                Arguments.PRINCIPAL_ROLE,
+                                str,
+                                Hints.CatalogRoles.CATALOG_ROLE,
+                            ),
+                        ],
+                        input_name=Arguments.CATALOG_ROLE,
+                    ),
+                    Option(
+                        Subcommands.REVOKE,
+                        hint=Hints.CatalogRoles.GRANT_CATALOG_ROLE,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            ),
+                            Argument(
+                                Arguments.PRINCIPAL_ROLE,
+                                str,
+                                Hints.CatalogRoles.CATALOG_ROLE,
+                            ),
+                        ],
+                        input_name=Arguments.CATALOG_ROLE,
+                    ),
+                ],
+            ),
+            Option(
+                Commands.PRIVILEGES,
+                "manage privileges for a catalog role",
+                children=[
+                    Option(Subcommands.LIST, 
args=OptionTree._CATALOG_ROLE_AND_CATALOG),
+                    Option(
+                        Subcommands.CATALOG,
+                        children=[
+                            Option(
+                                Actions.GRANT,
+                                args=OptionTree._CATALOG_ROLE_AND_CATALOG,
+                                input_name=Arguments.PRIVILEGE,
+                            ),
+                            Option(
+                                Actions.REVOKE,
+                                args=[
+                                    Argument(
+                                        Arguments.CASCADE, bool, 
Hints.Grant.CASCADE
+                                    )
+                                ]
+                                + OptionTree._CATALOG_ROLE_AND_CATALOG,
+                                input_name=Arguments.PRIVILEGE,
+                            ),
+                        ],
+                    ),
+                    Option(
+                        Subcommands.NAMESPACE,
+                        children=[
+                            Option(
+                                Actions.GRANT,
+                                args=[
+                                    Argument(
+                                        Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE
+                                    )
+                                ]
+                                + OptionTree._CATALOG_ROLE_AND_CATALOG,
+                                input_name=Arguments.PRIVILEGE,
+                            ),
+                            Option(
+                                Actions.REVOKE,
+                                args=[
+                                    Argument(
+                                        Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE
+                                    ),
+                                    Argument(
+                                        Arguments.CASCADE, bool, 
Hints.Grant.CASCADE
+                                    ),
+                                ]
+                                + OptionTree._CATALOG_ROLE_AND_CATALOG,
+                                input_name=Arguments.PRIVILEGE,
+                            ),
+                        ],
+                    ),
+                    Option(
+                        Subcommands.TABLE,
+                        children=[
+                            Option(
+                                Actions.GRANT,
+                                args=[
+                                    Argument(
+                                        Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE
+                                    ),
+                                    Argument(Arguments.TABLE, str, 
Hints.Grant.TABLE),
+                                ]
+                                + OptionTree._CATALOG_ROLE_AND_CATALOG,
+                                input_name=Arguments.PRIVILEGE,
+                            ),
+                            Option(
+                                Actions.REVOKE,
+                                args=[
+                                    Argument(
+                                        Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE
+                                    ),
+                                    Argument(Arguments.TABLE, str, 
Hints.Grant.TABLE),
+                                    Argument(
+                                        Arguments.CASCADE, bool, 
Hints.Grant.CASCADE
+                                    ),
+                                ]
+                                + OptionTree._CATALOG_ROLE_AND_CATALOG,
+                                input_name=Arguments.PRIVILEGE,
+                            ),
+                        ],
+                    ),
+                    Option(
+                        Subcommands.VIEW,
+                        children=[
+                            Option(
+                                Actions.GRANT,
+                                args=[
+                                    Argument(
+                                        Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE
+                                    ),
+                                    Argument(Arguments.VIEW, str, 
Hints.Grant.VIEW),
+                                ]
+                                + OptionTree._CATALOG_ROLE_AND_CATALOG,
+                                input_name=Arguments.PRIVILEGE,
+                            ),
+                            Option(
+                                Actions.REVOKE,
+                                args=[
+                                    Argument(
+                                        Arguments.NAMESPACE, str, 
Hints.Grant.NAMESPACE
+                                    ),
+                                    Argument(Arguments.VIEW, str, 
Hints.Grant.VIEW),
+                                    Argument(
+                                        Arguments.CASCADE, bool, 
Hints.Grant.CASCADE
+                                    ),
+                                ]
+                                + OptionTree._CATALOG_ROLE_AND_CATALOG,
+                                input_name=Arguments.PRIVILEGE,
+                            ),
+                        ],
+                    ),
+                ],
+            ),
+            Option(
+                Commands.NAMESPACES,
+                "manage namespaces",
+                children=[
+                    Option(
+                        Subcommands.CREATE,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            ),
+                            Argument(
+                                Arguments.LOCATION, str, 
Hints.Namespaces.LOCATION
+                            ),
+                            Argument(
+                                Arguments.PROPERTY,
+                                str,
+                                Hints.PROPERTY,
+                                allow_repeats=True,
+                            ),
+                        ],
+                        input_name=Arguments.NAMESPACE,
+                    ),
+                    Option(
+                        Subcommands.LIST,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            ),
+                            Argument(Arguments.PARENT, str, 
Hints.Namespaces.PARENT),
+                        ],
+                    ),
+                    Option(
+                        Subcommands.DELETE,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            )
+                        ],
+                        input_name=Arguments.NAMESPACE,
+                    ),
+                    Option(
+                        Subcommands.GET,
+                        args=[
+                            Argument(
+                                Arguments.CATALOG, str, 
Hints.CatalogRoles.CATALOG_NAME
+                            )
+                        ],
+                        input_name=Arguments.NAMESPACE,
+                    ),
+                ],
+            ),
+            Option(
+                Commands.PROFILES,
+                "manage profiles",
+                children=[
+                    Option(Subcommands.CREATE, input_name=Arguments.PROFILE),
+                    Option(Subcommands.DELETE, input_name=Arguments.PROFILE),
+                    Option(Subcommands.UPDATE, input_name=Arguments.PROFILE),
+                    Option(Subcommands.GET, input_name=Arguments.PROFILE),
+                    Option(Subcommands.LIST),
+                ],
+            ),
         ]
diff --git a/client/python/cli/options/parser.py 
b/client/python/cli/options/parser.py
index 8613474fe..26f5c8dfc 100644
--- a/client/python/cli/options/parser.py
+++ b/client/python/cli/options/parser.py
@@ -37,57 +37,84 @@ class Parser(object):
     """
 
     _ROOT_ARGUMENTS = [
-        Argument(Arguments.HOST, str, hint='hostname'),
-        Argument(Arguments.PORT, int, hint='port'),
-        Argument(Arguments.BASE_URL, str, hint='complete base URL instead of 
hostname:port'),
-        Argument(Arguments.CLIENT_ID, str, hint='client ID for token-based 
authentication'),
-        Argument(Arguments.CLIENT_SECRET, str, hint='client secret for 
token-based authentication'),
-        Argument(Arguments.ACCESS_TOKEN, str, hint='access token for 
token-based authentication'),
-        Argument(Arguments.PROFILE, str, hint='profile for token-based 
authentication'),
-        Argument(Arguments.PROXY, str, hint='proxy URL'),
+        Argument(Arguments.HOST, str, hint="hostname"),
+        Argument(Arguments.PORT, int, hint="port"),
+        Argument(
+            Arguments.BASE_URL, str, hint="complete base URL instead of 
hostname:port"
+        ),
+        Argument(
+            Arguments.CLIENT_ID, str, hint="client ID for token-based 
authentication"
+        ),
+        Argument(
+            Arguments.CLIENT_SECRET,
+            str,
+            hint="client secret for token-based authentication",
+        ),
+        Argument(
+            Arguments.ACCESS_TOKEN,
+            str,
+            hint="access token for token-based authentication",
+        ),
+        Argument(Arguments.PROFILE, str, hint="profile for token-based 
authentication"),
+        Argument(Arguments.PROXY, str, hint="proxy URL"),
     ]
 
     @staticmethod
     def _build_parser() -> argparse.ArgumentParser:
-        parser = TreeHelpParser(description='Polaris CLI')
+        parser = TreeHelpParser(description="Polaris CLI")
 
         for arg in Parser._ROOT_ARGUMENTS:
             if arg.default is not None:
-                parser.add_argument(arg.get_flag_name(), type=arg.type, 
help=arg.hint, default=arg.default)
+                parser.add_argument(
+                    arg.get_flag_name(),
+                    type=arg.type,
+                    help=arg.hint,
+                    default=arg.default,
+                )
             else:
                 parser.add_argument(arg.get_flag_name(), type=arg.type, 
help=arg.hint)
 
         # Add everything from the option tree to the parser:
         def add_arguments(parser, args: List[Argument]):
             for arg in args:
-                kwargs = {'help': arg.hint, 'type': arg.type}
+                kwargs = {"help": arg.hint, "type": arg.type}
                 if arg.choices:
-                    kwargs['choices'] = arg.choices
+                    kwargs["choices"] = arg.choices
                 if arg.lower:
-                    kwargs['type'] = kwargs['type'].lower
+                    kwargs["type"] = kwargs["type"].lower
                 if arg.default:
-                    kwargs['default'] = arg.default
+                    kwargs["default"] = arg.default
 
                 if arg.type is bool:
-                    del kwargs['type']
-                    parser.add_argument(arg.get_flag_name(), **kwargs, 
action='store_true')
+                    del kwargs["type"]
+                    parser.add_argument(
+                        arg.get_flag_name(), **kwargs, action="store_true"
+                    )
                 elif arg.allow_repeats:
-                    parser.add_argument(arg.get_flag_name(), **kwargs, 
action='append')
+                    parser.add_argument(arg.get_flag_name(), **kwargs, 
action="append")
                 else:
                     parser.add_argument(arg.get_flag_name(), **kwargs)
 
         def recurse_options(subparser, options: List[Option]):
             for option in options:
-                option_parser = subparser.add_parser(option.name, 
help=option.hint or option.name)
+                option_parser = subparser.add_parser(
+                    option.name, help=option.hint or option.name
+                )
                 add_arguments(option_parser, option.args)
                 if option.input_name:
-                    option_parser.add_argument(option.input_name, type=str,
-                                               
help=option.input_name.replace('_', ' '), default=None)
+                    option_parser.add_argument(
+                        option.input_name,
+                        type=str,
+                        help=option.input_name.replace("_", " "),
+                        default=None,
+                    )
                 if option.children:
-                    children_subparser = 
option_parser.add_subparsers(dest=f'{option.name}_subcommand', required=False)
+                    children_subparser = option_parser.add_subparsers(
+                        dest=f"{option.name}_subcommand", required=False
+                    )
                     recurse_options(children_subparser, option.children)
 
-        subparser = parser.add_subparsers(dest='command', required=False)
+        subparser = parser.add_subparsers(dest="command", required=False)
         recurse_options(subparser, OptionTree.get_tree())
         return parser
 
@@ -102,13 +129,13 @@ class Parser(object):
             return None
         results = dict()
         for property in properties:
-            if '=' not in property:
-                raise Exception(f'Could not parse property `{property}`')
-            key, value = property.split('=', 1)
+            if "=" not in property:
+                raise Exception(f"Could not parse property `{property}`")
+            key, value = property.split("=", 1)
             if not value:
-                raise Exception(f'Could not parse property `{property}`')
+                raise Exception(f"Could not parse property `{property}`")
             if key in results:
-                raise Exception(f'Duplicate property key `{key}`')
+                raise Exception(f"Duplicate property key `{key}`")
             results[key] = value
         return results
 
@@ -118,19 +145,21 @@ class TreeHelpParser(argparse.ArgumentParser):
     Replaces the default help behavior with a more readable message.
     """
 
-    INDENT = ' ' * 2
+    INDENT = " " * 2
 
     def parse_args(self, args=None, namespace=None):
         if args is None:
             args = sys.argv[1:]
-        help_index = min([float('inf')] + [args.index(x) for x in ['-h', 
'--help'] if x in args])
-        if help_index < float('inf'):
+        help_index = min(
+            [float("inf")] + [args.index(x) for x in ["-h", "--help"] if x in 
args]
+        )
+        if help_index < float("inf"):
             tree_str = self._get_tree_str(args[:help_index])
             if tree_str:
-                print(f'input: polaris {" ".join(args)}')
-                print('options:')
+                print(f"input: polaris {' '.join(args)}")
+                print("options:")
                 print(tree_str)
-                print('\n')
+                print("\n")
                 self.print_usage()
                 super().exit()
             else:
@@ -141,11 +170,15 @@ class TreeHelpParser(argparse.ArgumentParser):
     def _get_tree_str(self, args: List[str]) -> Optional[str]:
         command_path = self._get_command_path(args, OptionTree.get_tree())
         if len(command_path) == 0:
-            result = TreeHelpParser.INDENT + 'polaris'
+            result = TreeHelpParser.INDENT + "polaris"
             for arg in Parser._ROOT_ARGUMENTS:
-                result += '\n' + (TreeHelpParser.INDENT * 2) + 
f"{arg.get_flag_name()}  {arg.hint}"
+                result += (
+                    "\n"
+                    + (TreeHelpParser.INDENT * 2)
+                    + f"{arg.get_flag_name()}  {arg.hint}"
+                )
             for option in OptionTree.get_tree():
-                result += '\n' + self._get_tree_for_option(option, indent=2)
+                result += "\n" + self._get_tree_for_option(option, indent=2)
             return result
         else:
             option_node = self._get_option_node(command_path, 
OptionTree.get_tree())
@@ -159,19 +192,25 @@ class TreeHelpParser(argparse.ArgumentParser):
         result += (TreeHelpParser.INDENT * indent) + option.name
 
         if option.args:
-            result += '\n' + (TreeHelpParser.INDENT * (indent + 1)) + "Named 
arguments:"
+            result += "\n" + (TreeHelpParser.INDENT * (indent + 1)) + "Named 
arguments:"
         for arg in option.args:
-            result += '\n' + (TreeHelpParser.INDENT * (indent + 2)) + 
f"{arg.get_flag_name()}  {arg.hint}"
+            result += (
+                "\n"
+                + (TreeHelpParser.INDENT * (indent + 2))
+                + f"{arg.get_flag_name()}  {arg.hint}"
+            )
 
         if option.input_name:
-            result += '\n' + (TreeHelpParser.INDENT * (indent + 1)) + 
"Positional arguments:"
-            result += '\n' + (TreeHelpParser.INDENT * (indent + 2)) + 
option.input_name
+            result += (
+                "\n" + (TreeHelpParser.INDENT * (indent + 1)) + "Positional 
arguments:"
+            )
+            result += "\n" + (TreeHelpParser.INDENT * (indent + 2)) + 
option.input_name
 
         if len(option.args) > 0 and len(option.children) > 0:
-            result += '\n'
+            result += "\n"
 
         for child in sorted(option.children, key=lambda o: o.name):
-            result += '\n' + self._get_tree_for_option(child, indent + 1)
+            result += "\n" + self._get_tree_for_option(child, indent + 1)
 
         return result
 
@@ -194,7 +233,9 @@ class TreeHelpParser(argparse.ArgumentParser):
                     break
         return command_path
 
-    def _get_option_node(self, command_path: List[str], nodes: List[Option]) 
-> Optional[Option]:
+    def _get_option_node(
+        self, command_path: List[str], nodes: List[Option]
+    ) -> Optional[Option]:
         if len(command_path) > 0:
             for node in nodes:
                 if node.name == command_path[0]:
@@ -203,4 +244,3 @@ class TreeHelpParser(argparse.ArgumentParser):
                     else:
                         return self._get_option_node(command_path[1:], 
node.children)
         return None
-
diff --git a/client/python/cli/polaris_cli.py b/client/python/cli/polaris_cli.py
index d60791232..83341ada4 100644
--- a/client/python/cli/polaris_cli.py
+++ b/client/python/cli/polaris_cli.py
@@ -24,12 +24,22 @@ from json import JSONDecodeError
 
 from typing import Dict
 
-from cli.constants import Arguments, Commands, CLIENT_ID_ENV, 
CLIENT_SECRET_ENV, CLIENT_PROFILE_ENV, DEFAULT_HOSTNAME, DEFAULT_PORT, 
CONFIG_FILE
+from cli.constants import (
+    Arguments,
+    Commands,
+    CLIENT_ID_ENV,
+    CLIENT_SECRET_ENV,
+    CLIENT_PROFILE_ENV,
+    DEFAULT_HOSTNAME,
+    DEFAULT_PORT,
+    CONFIG_FILE,
+)
 from cli.options.option_tree import Argument
 from cli.options.parser import Parser
 from polaris.management import ApiClient, Configuration
 from polaris.management import PolarisDefaultApi
 
+
 class PolarisCli:
     """
     Implements a basic Command-Line Interface (CLI) for interacting with a 
Polaris service. The CLI can be used to
@@ -51,6 +61,7 @@ class PolarisCli:
         options = Parser.parse(args)
         if options.command == Commands.PROFILES:
             from cli.command import Command
+
             command = Command.from_options(options)
             command.execute()
         else:
@@ -58,6 +69,7 @@ class PolarisCli:
             with client_builder() as api_client:
                 try:
                     from cli.command import Command
+
                     admin_api = PolarisDefaultApi(api_client)
                     command = Command.from_options(options)
                     command.execute(admin_api)
@@ -68,15 +80,20 @@ class PolarisCli:
     @staticmethod
     def _try_print_exception(e):
         try:
-            error = json.loads(e.body)['error']
-            sys.stderr.write(f'Exception when communicating with the Polaris 
server.'
-                             f' {error["type"]}: 
{error["message"]}{os.linesep}')
+            error = json.loads(e.body)["error"]
+            sys.stderr.write(
+                f"Exception when communicating with the Polaris server."
+                f" {error['type']}: {error['message']}{os.linesep}"
+            )
         except JSONDecodeError as _:
-            sys.stderr.write(f'Exception when communicating with the Polaris 
server.'
-                             f' {e.status}: {e.reason}{os.linesep}')
+            sys.stderr.write(
+                f"Exception when communicating with the Polaris server."
+                f" {e.status}: {e.reason}{os.linesep}"
+            )
         except Exception as _:
-            sys.stderr.write(f'Exception when communicating with the Polaris 
server.'
-                             f' {e}{os.linesep}')
+            sys.stderr.write(
+                f"Exception when communicating with the Polaris server. 
{e}{os.linesep}"
+            )
 
     @staticmethod
     def _load_profiles() -> Dict[str, Dict[str, str]]:
@@ -88,19 +105,19 @@ class PolarisCli:
     @staticmethod
     def _get_token(api_client: ApiClient, catalog_url, client_id, 
client_secret) -> str:
         response = api_client.call_api(
-            'POST',
-            f'{catalog_url}/oauth/tokens',
-            header_params={'Content-Type': 
'application/x-www-form-urlencoded'},
+            "POST",
+            f"{catalog_url}/oauth/tokens",
+            header_params={"Content-Type": 
"application/x-www-form-urlencoded"},
             post_params={
-                'grant_type': 'client_credentials',
-                'client_id': client_id,
-                'client_secret': client_secret,
-                'scope': 'PRINCIPAL_ROLE:ALL'
-            }
+                "grant_type": "client_credentials",
+                "client_id": client_id,
+                "client_secret": client_secret,
+                "scope": "PRINCIPAL_ROLE:ALL",
+            },
         ).response.data
-        if 'access_token' not in json.loads(response):
-            raise Exception('Failed to get access token')
-        return json.loads(response)['access_token']
+        if "access_token" not in json.loads(response):
+            raise Exception("Failed to get access token")
+        return json.loads(response)["access_token"]
 
     @staticmethod
     def _get_client_builder(options):
@@ -110,38 +127,50 @@ class PolarisCli:
             profiles = PolarisCli._load_profiles()
             profile = profiles.get(client_profile)
             if not profile:
-                raise Exception(f'Polaris profile {client_profile} not found')
+                raise Exception(f"Polaris profile {client_profile} not found")
         # Determine which credentials to use
-        client_id = options.client_id or os.getenv(CLIENT_ID_ENV) or 
profile.get('client_id')
-        client_secret = options.client_secret or os.getenv(CLIENT_SECRET_ENV) 
or profile.get('client_secret')
-        
+        client_id = (
+            options.client_id or os.getenv(CLIENT_ID_ENV) or 
profile.get("client_id")
+        )
+        client_secret = (
+            options.client_secret
+            or os.getenv(CLIENT_SECRET_ENV)
+            or profile.get("client_secret")
+        )
+
         # Validates
         has_access_token = options.access_token is not None
         has_client_secret = client_id is not None and client_secret is not None
         if has_access_token and (options.client_id or options.client_secret):
-            raise Exception(f'Please provide credentials via either 
{Argument.to_flag_name(Arguments.CLIENT_ID)} &'
-                            f' 
{Argument.to_flag_name(Arguments.CLIENT_SECRET)} or'
-                            f' 
{Argument.to_flag_name(Arguments.ACCESS_TOKEN)}, but not both')
+            raise Exception(
+                f"Please provide credentials via either 
{Argument.to_flag_name(Arguments.CLIENT_ID)} &"
+                f" {Argument.to_flag_name(Arguments.CLIENT_SECRET)} or"
+                f" {Argument.to_flag_name(Arguments.ACCESS_TOKEN)}, but not 
both"
+            )
         if not has_access_token and not has_client_secret:
-            raise Exception(f'Please provide credentials via either 
{Argument.to_flag_name(Arguments.CLIENT_ID)} &'
-                            f' 
{Argument.to_flag_name(Arguments.CLIENT_SECRET)} or'
-                            f' 
{Argument.to_flag_name(Arguments.ACCESS_TOKEN)}.'
-                            f' Alternatively, you may set the environment 
variables {CLIENT_ID_ENV} &'
-                            f' {CLIENT_SECRET_ENV}.')
+            raise Exception(
+                f"Please provide credentials via either 
{Argument.to_flag_name(Arguments.CLIENT_ID)} &"
+                f" {Argument.to_flag_name(Arguments.CLIENT_SECRET)} or"
+                f" {Argument.to_flag_name(Arguments.ACCESS_TOKEN)}."
+                f" Alternatively, you may set the environment variables 
{CLIENT_ID_ENV} &"
+                f" {CLIENT_SECRET_ENV}."
+            )
         # Authenticate accordingly
         if options.base_url:
             if options.host is not None or options.port is not None:
-                raise Exception(f'Please provide either 
{Argument.to_flag_name(Arguments.BASE_URL)} or'
-                                f' {Argument.to_flag_name(Arguments.HOST)} &'
-                                f' {Argument.to_flag_name(Arguments.PORT)}, 
but not both')
-
-            polaris_management_url = f'{options.base_url}/api/management/v1'
-            polaris_catalog_url = f'{options.base_url}/api/catalog/v1'
+                raise Exception(
+                    f"Please provide either 
{Argument.to_flag_name(Arguments.BASE_URL)} or"
+                    f" {Argument.to_flag_name(Arguments.HOST)} &"
+                    f" {Argument.to_flag_name(Arguments.PORT)}, but not both"
+                )
+
+            polaris_management_url = f"{options.base_url}/api/management/v1"
+            polaris_catalog_url = f"{options.base_url}/api/catalog/v1"
         else:
-            host = options.host or profile.get('host') or DEFAULT_HOSTNAME
-            port = options.port or profile.get('port') or DEFAULT_PORT
-            polaris_management_url = f'http://{host}:{port}/api/management/v1'
-            polaris_catalog_url = f'http://{host}:{port}/api/catalog/v1'
+            host = options.host or profile.get("host") or DEFAULT_HOSTNAME
+            port = options.port or profile.get("port") or DEFAULT_PORT
+            polaris_management_url = f"http://{host}:{port}/api/management/v1";
+            polaris_catalog_url = f"http://{host}:{port}/api/catalog/v1";
 
         config = Configuration(host=polaris_management_url)
         config.proxy = options.proxy
@@ -152,7 +181,9 @@ class PolarisCli:
             config.password = client_secret
 
         if not has_access_token and not 
PolarisCli.DIRECT_AUTHENTICATION_ENABLED:
-            token = PolarisCli._get_token(ApiClient(config), 
polaris_catalog_url, client_id, client_secret)
+            token = PolarisCli._get_token(
+                ApiClient(config), polaris_catalog_url, client_id, 
client_secret
+            )
             config.username = None
             config.password = None
             config.access_token = token
@@ -160,5 +191,5 @@ class PolarisCli:
         return lambda: ApiClient(config)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     PolarisCli.execute()

Reply via email to