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
This commit is contained in:
Adam Harwell
2014-09-02 14:50:28 -07:00
parent 87ca6d750a
commit 87e7be2a3c
12 changed files with 264 additions and 236 deletions

View File

@@ -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):

View File

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

View File

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

View File

@@ -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()

View File

@@ -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!"

View File

@@ -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):

View File

@@ -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'))

View File

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

View File

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

View File

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

View File

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

View File

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