Skip to content

Commit

Permalink
Merge branch 'feature/api-improvements' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
glennmatthews committed Feb 24, 2017
2 parents 95150cf + 9bbf584 commit 8d52454
Show file tree
Hide file tree
Showing 68 changed files with 1,693 additions and 1,535 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ accept-no-return-type-doc=yes

# default: max-module-lines: 1000
# current worst offender: COT/ovf/ovf.py
max-module-lines=3000
max-module-lines=2900

[LOGGING]

Expand Down
53 changes: 53 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,58 @@ Change Log
All notable changes to the COT project will be documented in this file.
This project adheres to `Semantic Versioning`_.

`Unreleased`_
-------------

**Changed**

- With ``cot edit-hardware``, the platform hardware validation is no longer
a hard limit. Instead, if a value appears to be invalid, the user will be
warned about the validation failure and given the option to continue or
abort (`#61`_).
``cot --force ...``, as usual, can be used to auto-continue without prompting.
- Lots of API changes:

- Moved the ``to_string`` function from ``COT.data_validation`` to
``COT.ui_shared``.
- Function ``COT.deploy_esxi.get_object_from_connection`` is now method
``PyVmomiVMReconfigSpec.lookup_object``.
- Function ``COT.cli.formatter`` is now class ``COT.logging_.COTFormatter``.

- COT.disks module:

- Function ``create_disk`` is now split into class methods
``DiskRepresentation.for_new_file`` (creates the disk file and returns a
corresponding ``DiskRepresentation`` instance) and
``DiskRepresentation.create_file`` (creates disk file only).
- Function ``convert_disk`` is now class method
``DiskRepresentation.convert_to``
- Function ``disk_representation_from_file`` is now
class method ``DiskRepresentation.from_file``
- The ``DiskRepresentation`` constructor now only takes the path to a file
as input - if you want to create a new file, use
``DiskRepresentation.for_new_file`` instead of calling the
constructor directly.

- COT.ovf module:

- ``COT.ovf.ovf.byte_string`` has been moved and renamed to
``COT.ui_shared.pretty_bytes``.
- ``COT.ovf.ovf.byte_count`` has been moved and renamed to
``COT.ovf.utilities.programmatic_bytes_to_int``.
- ``COT.ovf.ovf.factor_bytes`` has been moved and renamed to
``COT.ovf.utilities.int_bytes_to_programmatic_units``.

- COT.platforms module:

- Class ``GenericPlatform`` is now ``Platform``.
- Function ``platform_from_product_class`` is now class method
``Platform.for_product_string`` and returns an instance
of a ``Platform`` class rather than the class object itself.
- Most ``Platform`` APIs are now instance methods instead of
class methods.
- Function ``is_known_product_class`` has been removed.

`1.9.1`_ - 2017-02-21
---------------------

Expand Down Expand Up @@ -619,6 +671,7 @@ Initial public release.
.. _#58: https://github.com/glennmatthews/cot/issues/58
.. _#59: https://github.com/glennmatthews/cot/issues/59
.. _#60: https://github.com/glennmatthews/cot/issues/60
.. _#61: https://github.com/glennmatthews/cot/issues/61

.. _Semantic Versioning: http://semver.org/
.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
Expand Down
4 changes: 3 additions & 1 deletion COT/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
COT.data_validation
COT.file_reference
COT.platforms
COT.logging_
User interface modules
----------------------
Expand All @@ -64,8 +64,10 @@
.. autosummary::
:toctree:
COT.disks
COT.helpers
COT.ovf
COT.platforms
"""

from ._version import get_versions
Expand Down
14 changes: 7 additions & 7 deletions COT/add_disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
import logging
import os.path

from COT.disks import disk_representation_from_file
from COT.disks import DiskRepresentation
from COT.data_validation import (
InvalidInputError, ValueUnsupportedError,
check_for_conflict, device_address, match_or_die,
Expand Down Expand Up @@ -144,7 +144,7 @@ def disk_image(self):

@disk_image.setter
def disk_image(self, value):
self._disk_image = disk_representation_from_file(value)
self._disk_image = DiskRepresentation.from_file(value)

@property
def address(self):
Expand Down Expand Up @@ -393,7 +393,7 @@ def guess_controller_type(platform, ctrl_item, drive_type):
"""If a controller type wasn't specified, try to guess from context.
Args:
platform (GenericPlatform): Platform class to guess controller for
platform (Platform): Platform instance to guess controller for
ctrl_item (object): Any known controller object, or None
drive_type (str): "cdrom" or "harddisk"
Returns:
Expand All @@ -404,8 +404,8 @@ def guess_controller_type(platform, ctrl_item, drive_type):
Examples:
::
>>> from COT.platforms import GenericPlatform
>>> guess_controller_type(GenericPlatform, None, 'harddisk')
>>> from COT.platforms import Platform
>>> guess_controller_type(Platform(), None, 'harddisk')
'ide'
"""
if ctrl_item is None:
Expand All @@ -416,7 +416,7 @@ def guess_controller_type(platform, ctrl_item, drive_type):
ctrl_type = platform.controller_type_for_device(drive_type)
logger.warning("Guessing controller type should be %s "
"based on disk drive type %s and platform %s",
ctrl_type, drive_type, platform.__name__)
ctrl_type, drive_type, platform)
else:
ctrl_type = ctrl_item.hardware_type
if ctrl_type != 'ide' and ctrl_type != 'scsi':
Expand Down Expand Up @@ -634,6 +634,6 @@ def add_disk_worker(vm,
description, disk, file_obj, ctrl_item, disk_item)


if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
import doctest
doctest.testmod()
52 changes: 5 additions & 47 deletions COT/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# cli.py - CLI handling for the Common OVF Tool suite
#
# August 2013, Glenn F. Matthews
# Copyright (c) 2013-2016 the COT project developers.
# Copyright (c) 2013-2017 the COT project developers.
# See the COPYRIGHT.txt file at the top-level directory of this distribution
# and at https://github.com/glennmatthews/cot/blob/master/COPYRIGHT.txt.
#
Expand All @@ -18,13 +18,6 @@

"""CLI entry point for the Common OVF Tool (COT) suite.
**Functions**
.. autosummary::
:nosignatures:
formatter
**Classes**
.. autosummary::
Expand Down Expand Up @@ -58,45 +51,11 @@
from COT import __version_long__
from COT.data_validation import InvalidInputError, ValueMismatchError
from COT.ui_shared import UI
from COT.logging_ import COTFormatter

logger = logging.getLogger(__name__)


def formatter(verbosity=logging.INFO):
"""Create formatter for log output.
We offer different (more verbose) formatting when debugging is enabled,
hence this need.
Args:
verbosity (int): Logging level as defined by :mod:`logging`.
Returns:
colorlog.ColoredFormatter: Formatter object to use with :mod:`logging`.
"""
from colorlog import ColoredFormatter
log_colors = {
'DEBUG': 'blue',
'VERBOSE': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
format_string = "%(log_color)s"
datefmt = None
if verbosity <= logging.DEBUG:
format_string += "%(asctime)s.%(msecs)d "
datefmt = "%H:%M:%S"
format_string += "%(levelname)8s: "
if verbosity <= logging.VERBOSE:
format_string += "%(name)-22s "
format_string += "%(message)s"
return ColoredFormatter(format_string,
datefmt=datefmt,
log_colors=log_colors)


class CLI(UI):
"""Command-line user interface for COT.
Expand Down Expand Up @@ -312,16 +271,15 @@ def fill_examples(self, example_list):
def set_verbosity(self, level):
"""Enable logging and/or change the logging verbosity level.
Will call :func:`formatter` and associate the resulting formatter
with logging.
Will create a :class:`COTFormatter` and use it for log formatting.
Args:
level (int): Logging level as defined by :mod:`logging`
"""
if not self.handler:
self.handler = logging.StreamHandler()
self.handler.setLevel(level)
self.handler.setFormatter(formatter(level))
self.handler.setFormatter(COTFormatter(level))
if not self.master_logger:
self.master_logger = logging.getLogger('COT')
self.master_logger.addHandler(self.handler)
Expand Down Expand Up @@ -684,5 +642,5 @@ def main():
CLI().run(sys.argv[1:])


if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
main()
55 changes: 17 additions & 38 deletions COT/data_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
no_whitespace
non_negative_int
positive_int
to_string
validate_int
truth_value
Expand All @@ -56,40 +55,12 @@
NIC_TYPES
"""

import xml.etree.ElementTree as ET
import hashlib
import re
import sys
from collections import namedtuple
from distutils.util import strtobool


def to_string(obj):
"""Get string representation of an object, special-case for XML Element.
Args:
obj (object): Object to represent as a string.
Returns:
str: string representation
Examples:
::
>>> to_string("Hello")
'Hello'
>>> to_string(27.5)
'27.5'
>>> e = ET.Element('hello', attrib={'key': 'value'})
>>> print(e) # doctest: +ELLIPSIS
<Element ...hello... at ...>
>>> print(to_string(e))
<hello key="value" />
"""
if ET.iselement(obj):
if sys.version_info[0] >= 3:
return ET.tostring(obj, encoding='unicode')
else:
return ET.tostring(obj)
else:
return str(obj)
from COT.ui_shared import to_string


def alphanum_split(key):
Expand Down Expand Up @@ -261,7 +232,7 @@ def canonicalize_nic_subtype(subtype):
Unsupported value 'foobar' for NIC subtype ...
.. seealso::
:meth:`COT.platforms.GenericPlatform.validate_nic_type`
:meth:`COT.platforms.Platform.validate_nic_type`
"""
return canonicalize_helper("NIC subtype", subtype,
_NIC_MAPPINGS, re.IGNORECASE)
Expand Down Expand Up @@ -473,7 +444,7 @@ def no_whitespace(string):

def validate_int(string,
minimum=None, maximum=None,
label="input"):
label=None):
"""Parser helper function for validating integer arguments in a range.
Args:
Expand Down Expand Up @@ -506,6 +477,8 @@ def validate_int(string,
... print(e)
Value '100' for x is too high - must be at most 10
"""
if label is None:
label = "input"
try:
i = int(string)
except ValueError:
Expand All @@ -517,13 +490,14 @@ def validate_int(string,
return i


def non_negative_int(string):
def non_negative_int(string, label=None):
"""Parser helper function for integer arguments that must be 0 or more.
Alias for :func:`validate_int` setting :attr:`minimum` to 0.
Args:
string (str): String to validate.
label (str): Label to include in any errors raised
Returns:
int: Validated integer value
Raises:
Expand All @@ -542,16 +516,17 @@ def non_negative_int(string):
... print(e)
Value '-1' for input is too low - must be at least 0
"""
return validate_int(string, minimum=0)
return validate_int(string, minimum=0, label=label)


def positive_int(string):
def positive_int(string, label=None):
"""Parser helper function for integer arguments that must be 1 or more.
Alias for :func:`validate_int` setting :attr:`minimum` to 1.
Args:
string (str): String to validate.
label (str): Label to include in any errors raised
Returns:
int: Validated integer value
Raises:
Expand All @@ -568,7 +543,7 @@ def positive_int(string):
... print(e)
Value '0' for input is too low - must be at least 1
"""
return validate_int(string, minimum=1)
return validate_int(string, minimum=1, label=label)


def truth_value(value):
Expand Down Expand Up @@ -612,6 +587,10 @@ def truth_value(value):
)


ValidRange = namedtuple('ValidRange', ['minimum', 'maximum'])
"""Simple helper class representing a range of valid values."""


# Some handy exception and error types we can throw
class ValueMismatchError(ValueError):
"""Values which were expected to be equal turned out to be not equal."""
Expand Down Expand Up @@ -680,6 +659,6 @@ def __str__(self):
self.expected_value))


if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
import doctest
doctest.testmod()
2 changes: 1 addition & 1 deletion COT/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,6 @@ def create_subparser(self):
"'kind:value,options'.")


if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
import doctest
doctest.testmod()

0 comments on commit 8d52454

Please sign in to comment.