From 87e7be2a3cc6587303e42e69edda06dfff1275d5 Mon Sep 17 00:00:00 2001 From: Adam Harwell Date: Tue, 2 Sep 2014 14:50:28 -0700 Subject: [PATCH] Change object __str__() to use pretty formatting Use the EntityFormatter framework to generate str() for objects, and render them using prettytable. Replaces the old manual __str__() method. Change-Id: Icf6f37f4a677595f3aaa8d305c2c8cd4676e72fa --- barbicanclient/barbican_cli/containers.py | 47 +---- barbicanclient/barbican_cli/formatter.py | 23 --- barbicanclient/barbican_cli/orders.py | 32 +-- barbicanclient/barbican_cli/secrets.py | 42 +--- barbicanclient/base.py | 7 - barbicanclient/containers.py | 191 ++++++++++++------ barbicanclient/formatter.py | 50 +++++ barbicanclient/orders.py | 40 ++-- barbicanclient/secrets.py | 46 +++-- barbicanclient/test/test_client_containers.py | 9 +- barbicanclient/test/test_client_orders.py | 11 +- barbicanclient/test/test_client_secrets.py | 2 +- 12 files changed, 264 insertions(+), 236 deletions(-) delete mode 100644 barbicanclient/barbican_cli/formatter.py create mode 100644 barbicanclient/formatter.py diff --git a/barbicanclient/barbican_cli/containers.py b/barbicanclient/barbican_cli/containers.py index 76926253..56a83e0f 100644 --- a/barbicanclient/barbican_cli/containers.py +++ b/barbicanclient/barbican_cli/containers.py @@ -16,39 +16,10 @@ Command-line interface sub-commands related to containers. from cliff import command from cliff import lister from cliff import show -import six -from barbicanclient.barbican_cli.formatter import EntityFormatter -from barbicanclient.containers import RSAContainer, CertificateContainer - - -class ContainerFormatter(EntityFormatter): - - columns = ("Container href", - "Name", - "Created", - "Status", - "Type", - "Secrets", - "Consumers", - ) - - def _get_formatted_data(self, entity): - formatted_secrets = '\n'.join( - (s.secret_ref for n, s in six.iteritems(entity.secrets)) - ) - formatted_consumers = '\n'.join( - (str(c) for c in entity.consumers) - ) - data = (entity.container_ref, - entity.name, - entity.created, - entity.status, - entity._type, - formatted_secrets, - formatted_consumers, - ) - return data +from barbicanclient.containers import CertificateContainer +from barbicanclient.containers import Container +from barbicanclient.containers import RSAContainer class DeleteContainer(command.Command): @@ -63,7 +34,7 @@ class DeleteContainer(command.Command): self.app.client.containers.delete(args.URI) -class GetContainer(show.ShowOne, ContainerFormatter): +class GetContainer(show.ShowOne): """Retrieve a container by providing its URI.""" def get_parser(self, prog_name): @@ -73,10 +44,10 @@ class GetContainer(show.ShowOne, ContainerFormatter): def take_action(self, args): entity = self.app.client.containers.get(args.URI) - return self._get_formatted_entity(entity) + return entity._get_formatted_entity() -class ListContainer(lister.Lister, ContainerFormatter): +class ListContainer(lister.Lister): """List containers.""" def get_parser(self, prog_name): @@ -101,10 +72,10 @@ class ListContainer(lister.Lister, ContainerFormatter): def take_action(self, args): obj_list = self.app.client.containers.list(args.limit, args.offset, args.name, args.type) - return self._list_objects(obj_list) + return Container._list_objects(obj_list) -class CreateContainer(show.ShowOne, ContainerFormatter): +class CreateContainer(show.ShowOne): """Store a container in Barbican.""" def get_parser(self, prog_name): @@ -155,7 +126,7 @@ class CreateContainer(show.ShowOne, ContainerFormatter): entity = container_type(api=self.app.client, name=args.name, secret_refs=secret_refs) entity.store() - return self._get_formatted_entity(entity) + return entity._get_formatted_entity() @staticmethod def _parse_secrets(secrets): diff --git a/barbicanclient/barbican_cli/formatter.py b/barbicanclient/barbican_cli/formatter.py deleted file mode 100644 index a345a302..00000000 --- a/barbicanclient/barbican_cli/formatter.py +++ /dev/null @@ -1,23 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -class EntityFormatter(object): - """ Helper class with the purpose of formatting entities for display.""" - - def _list_objects(self, obj_list): - data = (self._get_formatted_data(obj) for obj in obj_list) - return (self.columns, data) - - def _get_formatted_entity(self, entity): - return (self.columns, self._get_formatted_data(entity)) diff --git a/barbicanclient/barbican_cli/orders.py b/barbicanclient/barbican_cli/orders.py index dc302e89..51ba34bd 100644 --- a/barbicanclient/barbican_cli/orders.py +++ b/barbicanclient/barbican_cli/orders.py @@ -13,32 +13,14 @@ """ Command-line interface sub-commands related to orders. """ - from cliff import command from cliff import lister from cliff import show -from barbicanclient.barbican_cli.formatter import EntityFormatter +from barbicanclient import orders -class OrderFormatter(EntityFormatter): - - columns = ("Order href", - "Secret href", - "Created", - "Status", - ) - - def _get_formatted_data(self, entity): - data = (entity.order_ref, - entity.secret_ref, - entity.created, - entity.status, - ) - return data - - -class CreateOrder(show.ShowOne, OrderFormatter): +class CreateOrder(show.ShowOne): """Create a new order.""" def get_parser(self, prog_name): @@ -71,7 +53,7 @@ class CreateOrder(show.ShowOne, OrderFormatter): algorithm=args.algorithm, bit_length=args.bit_length, mode=args.mode, expiration=args.expiration) entity.submit() - return self._get_formatted_entity(entity) + return entity._get_formatted_entity() class DeleteOrder(command.Command): @@ -86,7 +68,7 @@ class DeleteOrder(command.Command): self.app.client.orders.delete(args.URI) -class GetOrder(show.ShowOne, OrderFormatter): +class GetOrder(show.ShowOne): """Retrieve an order by providing its URI.""" def get_parser(self, prog_name): @@ -96,10 +78,10 @@ class GetOrder(show.ShowOne, OrderFormatter): def take_action(self, args): entity = self.app.client.orders.Order(order_ref=args.URI) - return self._get_formatted_entity(entity) + return entity._get_formatted_entity() -class ListOrder(lister.Lister, OrderFormatter): +class ListOrder(lister.Lister): """List orders.""" def get_parser(self, prog_name): @@ -117,4 +99,4 @@ class ListOrder(lister.Lister, OrderFormatter): def take_action(self, args): obj_list = self.app.client.orders.list(args.limit, args.offset) - return self._list_objects(obj_list) + return orders.OrderFormatter._list_objects(obj_list) diff --git a/barbicanclient/barbican_cli/secrets.py b/barbicanclient/barbican_cli/secrets.py index eb094d8a..f8ac1476 100644 --- a/barbicanclient/barbican_cli/secrets.py +++ b/barbicanclient/barbican_cli/secrets.py @@ -13,39 +13,11 @@ """ Command-line interface sub-commands related to secrets. """ - from cliff import command from cliff import lister from cliff import show -from barbicanclient.barbican_cli.formatter import EntityFormatter - - -class SecretFormatter(EntityFormatter): - - columns = ("Secret href", - "Name", - "Created", - "Status", - "Content types", - "Algorithm", - "Bit length", - "Mode", - "Expiration", - ) - - def _get_formatted_data(self, entity): - data = (entity.secret_ref, - entity.name, - entity.created, - entity.status, - entity.content_types, - entity.algorithm, - entity.bit_length, - entity.mode, - entity.expiration, - ) - return data +from barbicanclient import secrets class DeleteSecret(command.Command): @@ -60,7 +32,7 @@ class DeleteSecret(command.Command): self.app.client.secrets.delete(args.URI) -class GetSecret(show.ShowOne, SecretFormatter): +class GetSecret(show.ShowOne): """Retrieve a secret by providing its URI.""" def get_parser(self, prog_name): @@ -86,10 +58,10 @@ class GetSecret(show.ShowOne, SecretFormatter): (entity,)) else: entity = self.app.client.secrets.Secret(secret_ref=args.URI) - return self._get_formatted_entity(entity) + return entity._get_formatted_entity() -class ListSecret(lister.Lister, SecretFormatter): +class ListSecret(lister.Lister): """List secrets.""" def get_parser(self, prog_name): @@ -123,10 +95,10 @@ class ListSecret(lister.Lister, SecretFormatter): args.name, args.mode, args.algorithm, args.bit_length) - return self._list_objects(obj_list) + return secrets.SecretFormatter._list_objects(obj_list) -class StoreSecret(show.ShowOne, SecretFormatter): +class StoreSecret(show.ShowOne): """Store a secret in Barbican.""" def get_parser(self, prog_name): @@ -168,4 +140,4 @@ class StoreSecret(show.ShowOne, SecretFormatter): algorithm=args.algorithm, bit_length=args.bit_length, mode=args.mode, expiration=args.expiration) entity.store() - return self._get_formatted_entity(entity) + return entity._get_formatted_entity() diff --git a/barbicanclient/base.py b/barbicanclient/base.py index 585ba498..4e0ef38f 100644 --- a/barbicanclient/base.py +++ b/barbicanclient/base.py @@ -34,13 +34,6 @@ def validate_ref(ref, entity): raise ValueError('{0} incorrectly specified.'.format(entity)) -def indent_object_string(string, spaces=8): - return '\n'.join( - ['{0}{1}'.format(' ' * spaces, line) - for line in str(string).split('\n') if line] - ) - - class ImmutableException(Exception): def __init__(self, attribute=None): message = "This object is immutable!" diff --git a/barbicanclient/containers.py b/barbicanclient/containers.py index e8f0ca42..2d9f98b7 100644 --- a/barbicanclient/containers.py +++ b/barbicanclient/containers.py @@ -17,6 +17,7 @@ import logging import six from barbicanclient import base +from barbicanclient import formatter from barbicanclient import secrets from barbicanclient.openstack.common.timeutils import parse_isotime @@ -33,7 +34,39 @@ def _immutable_after_save(func): return wrapper -class Container(object): +class ContainerFormatter(formatter.EntityFormatter): + + columns = ("Container href", + "Name", + "Created", + "Status", + "Type", + "Secrets", + "Consumers", + ) + + def _get_formatted_data(self): + formatted_secrets = None + formatted_consumers = None + if self.secrets: + formatted_secrets = '\n'.join(( + '='.join((name, secret_ref)) + for name, secret_ref in six.iteritems(self.secret_refs) + )) + if self.consumers: + formatted_consumers = '\n'.join((str(c) for c in self.consumers)) + data = (self.container_ref, + self.name, + self.created, + self.status, + self._type, + formatted_secrets, + formatted_consumers, + ) + return data + + +class Container(ContainerFormatter): """ Containers are used to keep track of the data stored in Barbican. """ @@ -198,31 +231,54 @@ class Container(object): def _get_named_secret(self, name): return self.secrets.get(name) - def __str__(self): - sec = ['"{0}":\n{1}'.format(s.get('name'), - base.indent_object_string(s.get('secret'), - 4)) - for s in six.iteritems(self.secrets)] - return ("Container:\n" - " href: {0}\n" - " name: {1}\n" - " created: {2}\n" - " status: {3}\n" - " type: {4}\n" - " secrets:\n" - "{5}\n" - " consumers: {6}\n" - .format(self.container_ref, self.name, self.created, - self.status, self._type, - base.indent_object_string('\n'.join(sec)), - self.consumers) - ) - def __repr__(self): return 'Container(name="{0}")'.format(self.name) -class RSAContainer(Container): +class RSAContainerFormatter(formatter.EntityFormatter): + _get_generic_data = ContainerFormatter._get_formatted_data + + def _get_generic_columns(self): + return ContainerFormatter.columns + + columns = ("Container href", + "Name", + "Created", + "Status", + "Type", + "Public Key", + "Private Key", + "PK Passphrase", + "Consumers", + ) + + def _get_formatted_data(self): + formatted_public_key = None + formatted_private_key = None + formatted_pkp = None + formatted_consumers = None + if self.public_key: + formatted_public_key = self.public_key.secret_ref + if self.private_key: + formatted_private_key = self.private_key.secret_ref + if self.private_key_passphrase: + formatted_pkp = self.private_key_passphrase.secret_ref + if self.consumers: + formatted_consumers = '\n'.join((str(c) for c in self.consumers)) + data = (self.container_ref, + self.name, + self.created, + self.status, + self._type, + formatted_public_key, + formatted_private_key, + formatted_pkp, + formatted_consumers, + ) + return data + + +class RSAContainer(RSAContainerFormatter, Container): _required_secrets = ["public_key", "private_key"] _optional_secrets = ["private_key_passphrase"] _type = 'rsa' @@ -292,29 +348,56 @@ class RSAContainer(Container): def __repr__(self): return 'RSAContainer(name="{0}")'.format(self.name) - def __str__(self): - return ("RSAContainer:\n" - " href: {0}\n" - " name: {1}\n" - " created: {2}\n" - " status: {3}\n" - " public_key:\n" - "{4}\n" - " private_key:\n" - "{5}\n" - " private_key_passphrase:\n" - "{6}\n" - " consumers: {7}\n" - .format(self.container_ref, self.name, self.created, - self.status, - base.indent_object_string(self.public_key), - base.indent_object_string(self.private_key), - base.indent_object_string(self.private_key_passphrase), - self.consumers) + +class CertificateContainerFormatter(formatter.EntityFormatter): + _get_generic_data = ContainerFormatter._get_formatted_data + + def _get_generic_columns(self): + return ContainerFormatter.columns + + columns = ("Container href", + "Name", + "Created", + "Status", + "Type", + "Certificate", + "Intermediates", + "Private Key", + "PK Passphrase", + "Consumers", + ) + + def _get_formatted_data(self): + formatted_certificate = None + formatted_private_key = None + formatted_pkp = None + formatted_intermediates = None + formatted_consumers = None + if self.certificate: + formatted_certificate = self.certificate.secret_ref + if self.intermediates: + formatted_intermediates = self.intermediates.secret_ref + if self.private_key: + formatted_private_key = self.private_key.secret_ref + if self.private_key_passphrase: + formatted_pkp = self.private_key_passphrase.secret_ref + if self.consumers: + formatted_consumers = '\n'.join((str(c) for c in self.consumers)) + data = (self.container_ref, + self.name, + self.created, + self.status, + self._type, + formatted_certificate, + formatted_intermediates, + formatted_private_key, + formatted_pkp, + formatted_consumers, ) + return data -class CertificateContainer(Container): +class CertificateContainer(CertificateContainerFormatter, Container): _required_secrets = ["certificate", "private_key"] _optional_secrets = ["private_key_passphrase", "intermediates"] _type = 'certificate' @@ -399,30 +482,6 @@ class CertificateContainer(Container): def __repr__(self): return 'CertificateContainer(name="{0}")'.format(self.name) - def __str__(self): - return ("CertificateContainer:\n" - " href: {0}\n" - " name: {1}\n" - " created: {2}\n" - " status: {3}\n" - " certificate:\n" - "{4}\n" - " private_key:\n" - "{5}\n" - " private_key_passphrase:\n" - "{6}\n" - " intermediates:\n" - "{7}\n" - " consumers: {8}\n" - .format(self.container_ref, self.name, self.created, - self.status, - base.indent_object_string(self.certificate), - base.indent_object_string(self.private_key), - base.indent_object_string(self.private_key_passphrase), - base.indent_object_string(self.intermediates), - self.consumers) - ) - class ContainerManager(base.BaseEntityManager): diff --git a/barbicanclient/formatter.py b/barbicanclient/formatter.py new file mode 100644 index 00000000..5cc35f05 --- /dev/null +++ b/barbicanclient/formatter.py @@ -0,0 +1,50 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import prettytable + + +class EntityFormatter(object): + """Base Mixin class providing functions that format entities for display. + + Must be used in conjunction with a Formatter mixin that provides + the function _get_formatted_data(). + """ + + @staticmethod + def _list_objects(obj_list): + columns = [] + data = (obj._get_generic_data() for obj in obj_list) + if obj_list: + columns = obj_list[0]._get_generic_columns() + return columns, data + + def _get_generic_data(self): + return self._get_formatted_data() + + def _get_generic_columns(self): + return self.columns + + def _get_formatted_entity(self): + return self.columns, self._get_formatted_data() + + def __str__(self): + """Provides a common prettytable based format for object strings.""" + data = self._get_formatted_data() + table = prettytable.PrettyTable(field_names=('Field', 'Value'), + print_empty=False) + table.padding_width = 1 + table.align['Field'] = 'l' + table.align['Value'] = 'l' + for name, value in zip(self.columns, data): + table.add_row((name, value)) + return table.get_string(fields=('Field', 'Value')) diff --git a/barbicanclient/orders.py b/barbicanclient/orders.py index 20658f1d..2a4057ed 100644 --- a/barbicanclient/orders.py +++ b/barbicanclient/orders.py @@ -16,6 +16,7 @@ import functools import logging from barbicanclient import base +from barbicanclient import formatter from barbicanclient.openstack.common.timeutils import parse_isotime @@ -31,7 +32,28 @@ def immutable_after_save(func): return wrapper -class Order(object): +class OrderFormatter(formatter.EntityFormatter): + + columns = ("Order href", + "Secret href", + "Created", + "Status", + "Error code", + "Error message" + ) + + def _get_formatted_data(self): + data = (self.order_ref, + self.secret_ref, + self.created, + self.status, + self.error_status_code, + self.error_reason + ) + return data + + +class Order(OrderFormatter): """ Orders are used to request the generation of a Secret in Barbican. """ @@ -196,22 +218,6 @@ class Order(object): else: raise LookupError("Order is not yet stored.") - def __str__(self): - str_rep = ("Order:\n" - " order href: {0}\n" - " secret href: {1}\n" - " created: {2}\n" - " status: {3}\n" - ).format(self.order_ref, self.secret_ref, - self.created, self.status) - - if self.error_status_code: - str_rep = ''.join([str_rep, (" error_status_code: {0}\n" - " error_reason: {1}\n" - ).format(self.error_status_code, - self.error_reason)]) - return str_rep - def __repr__(self): return 'Order(order_ref={0})'.format(self.order_ref) diff --git a/barbicanclient/secrets.py b/barbicanclient/secrets.py index ba78c657..e65e0b84 100644 --- a/barbicanclient/secrets.py +++ b/barbicanclient/secrets.py @@ -17,6 +17,7 @@ import logging import six from barbicanclient import base +from barbicanclient import formatter from barbicanclient.openstack.common.timeutils import parse_isotime @@ -32,7 +33,34 @@ def immutable_after_save(func): return wrapper -class Secret(object): +class SecretFormatter(formatter.EntityFormatter): + + columns = ("Secret href", + "Name", + "Created", + "Status", + "Content types", + "Algorithm", + "Bit length", + "Mode", + "Expiration", + ) + + def _get_formatted_data(self): + data = (self.secret_ref, + self.name, + self.created, + self.status, + self.content_types, + self.algorithm, + self.bit_length, + self.mode, + self.expiration, + ) + return data + + +class Secret(SecretFormatter): """ Secrets are used to keep track of the data stored in Barbican. """ @@ -211,22 +239,6 @@ class Secret(object): else: raise LookupError("Secret is not yet stored.") - def __str__(self): - return ("Secret:\n" - " href: {0}\n" - " name: {1}\n" - " created: {2}\n" - " status: {3}\n" - " content types: {4}\n" - " algorithm: {5}\n" - " bit length: {6}\n" - " mode: {7}\n" - " expiration: {8}\n" - .format(self.secret_ref, self.name, self.created, - self.status, self.content_types, self.algorithm, - self.bit_length, self.mode, self.expiration) - ) - def __repr__(self): return 'Secret(name="{0}")'.format(self.name) diff --git a/barbicanclient/test/test_client_containers.py b/barbicanclient/test/test_client_containers.py index 37c0de26..c6a89b6f 100644 --- a/barbicanclient/test/test_client_containers.py +++ b/barbicanclient/test/test_client_containers.py @@ -100,16 +100,19 @@ class WhenTestingContainers(test_client.BaseEntityResource): def test_should_generic_container_str(self): container_obj = self.manager.create(name=self.container.name) - self.assertIn('name: ' + self.container.name, str(container_obj)) + self.assertIn(self.container.name, str(container_obj)) + self.assertIn(' generic ', str(container_obj)) def test_should_certificate_container_str(self): container_obj = self.manager.create_certificate( name=self.container.name) - self.assertIn('name: ' + self.container.name, str(container_obj)) + self.assertIn(self.container.name, str(container_obj)) + self.assertIn(' certificate ', str(container_obj)) def test_should_rsa_container_str(self): container_obj = self.manager.create_rsa(name=self.container.name) - self.assertIn('name: ' + self.container.name, str(container_obj)) + self.assertIn(self.container.name, str(container_obj)) + self.assertIn(' rsa ', str(container_obj)) def test_should_generic_container_repr(self): container_obj = self.manager.create(name=self.container.name) diff --git a/barbicanclient/test/test_client_orders.py b/barbicanclient/test/test_client_orders.py index 7cd8bf0f..35746c34 100644 --- a/barbicanclient/test/test_client_orders.py +++ b/barbicanclient/test/test_client_orders.py @@ -48,10 +48,13 @@ class WhenTestingOrders(test_client.BaseEntityResource): def test_should_entity_str(self): order = self.order.get_dict(self.entity_href) - order_obj = orders.Order(api=None, error_status_code='500', - error_reason='Something is broken', **order) - self.assertIn('status: ' + self.order.status, str(order_obj)) - self.assertIn('error_status_code: 500', str(order_obj)) + error_code = 500 + error_reason = 'Something is broken' + order_obj = orders.Order(api=None, error_status_code=error_code, + error_reason=error_reason, **order) + self.assertIn(self.order.status, str(order_obj)) + self.assertIn(str(error_code), str(order_obj)) + self.assertIn(error_reason, str(order_obj)) def test_should_entity_repr(self): order = self.order.get_dict(self.entity_href) diff --git a/barbicanclient/test/test_client_secrets.py b/barbicanclient/test/test_client_secrets.py index 95538052..33c5abb7 100644 --- a/barbicanclient/test/test_client_secrets.py +++ b/barbicanclient/test/test_client_secrets.py @@ -52,7 +52,7 @@ class WhenTestingSecrets(test_client.BaseEntityResource): def test_should_entity_str(self): secret_obj = self.manager.Secret(name=self.secret.name) - self.assertIn('name: ' + self.secret.name, str(secret_obj)) + self.assertIn(self.secret.name, str(secret_obj)) def test_should_entity_repr(self): secret_obj = self.manager.Secret(name=self.secret.name)