On Tue, Feb 04, 2014 at 05:00:24PM +0100, Santi Raffa wrote:
> Signed-off-by: Santi Raffa <[email protected]>
> ---
>  lib/cli.py                     |  12 ++++
>  lib/config.py                  |   5 +-
>  lib/ht.py                      |  19 +++++-
>  lib/serializer.py              | 138 
> +++++++++++++++++++++++++++++++++++++++++
>  src/Ganeti/JSON.hs             |   1 +
>  src/Ganeti/Types.hs            |  47 ++++++++++++++
>  test/hs/Test/Ganeti/Objects.hs |   3 +
>  test/hs/Test/Ganeti/OpCodes.hs |   8 +++
>  8 files changed, 230 insertions(+), 3 deletions(-)
> 
> diff --git a/lib/cli.py b/lib/cli.py
> index ad6c5bc..ce43a05 100644
> --- a/lib/cli.py
> +++ b/lib/cli.py
> @@ -44,6 +44,7 @@ from ganeti import netutils
>  from ganeti import qlang
>  from ganeti import objects
>  from ganeti import pathutils
> +from ganeti import serializer
>  
>  from ganeti.runtime import (GetClient)
>  
> @@ -678,6 +679,15 @@ def check_key_val(option, opt, value):  # pylint: 
> disable=W0613
>    return _SplitKeyVal(opt, value, True)
>  
>  
> +def check_key_private_val(option, opt, value):  # pylint: disable=W0613
> +  """Custom parser class for private and secret key=val,key=val options.
> +
> +  This will store the parsed values as a dict {key: val}.
> +
> +  """
> +  return serializer.PrivateDict(_SplitKeyVal(opt, value, True))
> +
> +
>  def _SplitListKeyVal(opt, value):
>    retval = {}
>    for elem in value.split("/"):
> @@ -780,6 +790,7 @@ class CliOption(Option):
>      "multilistidentkeyval",
>      "identkeyval",
>      "keyval",
> +    "keyprivateval",
>      "unit",
>      "bool",
>      "list",
> @@ -789,6 +800,7 @@ class CliOption(Option):
>    TYPE_CHECKER["multilistidentkeyval"] = check_multilist_ident_key_val
>    TYPE_CHECKER["identkeyval"] = check_ident_key_val
>    TYPE_CHECKER["keyval"] = check_key_val
> +  TYPE_CHECKER["keyprivateval"] = check_key_private_val
>    TYPE_CHECKER["unit"] = check_unit
>    TYPE_CHECKER["bool"] = check_bool
>    TYPE_CHECKER["list"] = check_list
> diff --git a/lib/config.py b/lib/config.py
> index fcdefc3..67a117c 100644
> --- a/lib/config.py
> +++ b/lib/config.py
> @@ -2488,7 +2488,10 @@ class ConfigWriter(object):
>      if destination is None:
>        destination = self._cfg_file
>      self._BumpSerialNo()
> -    txt = serializer.Dump(self._config_data.ToDict())
> +    txt = serializer.DumpJson(
> +      self._config_data.ToDict(_with_private=True),
> +      private_decoder=serializer.EncodeWithPrivateFields
> +    )
>  
>      getents = self._getents()
>      try:
> diff --git a/lib/ht.py b/lib/ht.py
> index 500c391..5f271f9 100644
> --- a/lib/ht.py
> +++ b/lib/ht.py
> @@ -29,7 +29,7 @@ from ganeti import compat
>  from ganeti import utils
>  from ganeti import constants
>  from ganeti import objects
> -
> +from ganeti.serializer import Private, PrivateDict
>  
>  _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
>  
> @@ -77,6 +77,9 @@ class _DescWrapper(_WrapperBase):
>    def __str__(self):
>      return self._text
>  
> +  def __repr__(self):
> +    return "<%s %r>" % (self._text, self._fn)
> +
>  
>  class _CommentWrapper(_WrapperBase):
>    """Wrapper class for comment.
> @@ -270,7 +273,7 @@ def TDict(val):
>    """Checks if the given value is a dictionary.
>  
>    """
> -  return isinstance(val, dict)
> +  return isinstance(val, dict) or isinstance(val, PrivateDict)

Given that it is somewhat strange to have an object posing a
dictionary, I suggest adding an explanation to the docstring.

>  def TIsLength(size):
> @@ -416,6 +419,18 @@ def TInstanceOf(cls):
>    return desc(lambda val: isinstance(val, cls))
>  
>  
> +def TPrivate(val_type):
> +  """Checks if a given value is an instance of Private.
> +
> +  """
> +  def fn(val):
> +    return isinstance(val, Private) and val_type(val.Get())
> +
> +  desc = WithDesc("Private %s" % Parens(val_type))
> +
> +  return desc(fn)
> +
> +
>  def TListOf(my_type):
>    """Checks if a given value is a list with all elements of the same type.
>  
> diff --git a/lib/serializer.py b/lib/serializer.py
> index f008155..7bd4c1f 100644
> --- a/lib/serializer.py
> +++ b/lib/serializer.py
> @@ -166,3 +166,141 @@ Dump = DumpJson
>  Load = LoadJson
>  DumpSigned = DumpSignedJson
>  LoadSigned = LoadSignedJson
> +
> +
> +class Private(object):
> +

Missing docstring.

> +  def __init__(self, item, descr="redacted"):
> +    if isinstance(item, Private):
> +      raise ValueError("Attempted to nest Private values.")
> +    self._item = item
> +    self._descr = descr

How about the following:

  We can unwrap the item from the inner private and use that as this
  object's item.  That way we can eliminate the possibility of the
  runtime error.  By the way, a recursive function here, which
  traverses the Private.item references, can make this more robust.

> +
> +  def Get(self):
> +    return self._item
> +
> +  def __str__(self):
> +    return "<{._descr}>".format(self)
> +
> +  def __repr__(self):
> +    return "Private(?, descr='{._descr}')".format(self)
> +
> +  # pylint: disable=W0212
> +  # If it doesn't access _item directly, the call will go through __getattr__
> +  # because this class defines __slots__ and "item" is not in it.
> +  # OTOH, if we do add it there, we'd risk shadowing an "item" attribute.
> +  def __eq__(self, other):
> +    if isinstance(other, Private):
> +      return self._item == other._item
> +    else:
> +      return self._item == other
> +
> +  def __hash__(self):
> +    return hash(self._item)
> +
> +  def __format__(self, *_1, **_2):
> +    return self.__str__()
> +
> +  def __getattr__(self, attr):
> +    return Private(getattr(self._item, attr),
> +                   descr="%s.%s" % (self._descr, attr))
> +
> +  def __call__(self, *args, **kwargs):
> +    return Private(self._item(*args, **kwargs),
> +                   descr="%s()" % self._descr)
> +
> +  # pylint: disable=R0201
> +  # While this could get away with being a function, it needs to be an attr.

Perhaps rename attr to method.

> +  # Required by the copy.deepcopy function used by FillDict.
> +  def __getnewargs__(self):
> +    return tuple()
> +
> +  def __nonzero__(self):
> +    return bool(self._item)
> +
> +  # Get in the way of Pickle by implementing __slots__ but not __getstate__
> +  # ...and get a performance boost, too.
> +  __slots__ = ["_item", "_descr"]
> +
> +
> +class PrivateDict(dict):
> +  """A dictionary that turns its values to private fields.
> +

Missing "Example::" and indentation (I think)

> +  >>> PrivateDict()
> +  {}
> +  >>> supersekkrit = PrivateDict({"password": "foobar"})
> +  >>> supersekkrit["password"]
> +  Private(?, descr='password')
> +  >>> supersekkrit["password"].Get()
> +  'foobar'
> +  >>> supersekkrit["user"] = "eggspam"
> +  >>> supersekkrit.GetPrivate("user")
> +  'eggspam'
> +  >>> supersekkrit.Unprivate()
> +  {'password': 'foobar', 'user': 'eggspam'}

Missing newline before the end of the docstring.

> +  """
> +

Extra newline after the end of the docstring.

> +  def __init__(self, data=None):
> +    dict.__init__(self)
> +    self.update(data)
> +
> +  def __setitem__(self, item, value):
> +    if not isinstance(value, Private):
> +      if not isinstance(item, dict):
> +        value = Private(value, descr=item)
> +      else:
> +        value = PrivateDict(value)
> +    dict.__setitem__(self, item, value)
> +
> +  # copied straight from cpython/Lib/UserDict.py
> +  # The actual conversion to Private containers is done by __setitem__
> +  def update(self, other=None, **kwargs):
> +    # Make progressively weaker assumptions about "other"
> +    if other is None:
> +      pass
> +    elif hasattr(other, 'iteritems'):  # iteritems saves memory and lookups
> +      for k, v in other.iteritems():
> +        self[k] = v
> +    elif hasattr(other, 'keys'):
> +      for k in other.keys():
> +        self[k] = other[k]
> +    else:
> +      for k, v in other:
> +        self[k] = v
> +    if kwargs:
> +      self.update(kwargs)
> +
> +  def GetPrivate(self, *args):
> +    """Like dict.get, but extracting the value in the process.
> +
> +    Arguments are semantically equivalent to dict.get
> +

Needs "Example::"

> +      >>> PrivateDict({"foo": "bar"}).GetPrivate("foo")
> +      'bar'
> +      >>> PrivateDict({"foo": "bar"}).GetPrivate("baz", "spam")
> +      'spam'

Missing newline before the end of docstring.

> +    """ # epydoc does not check doctests, btw.

This is interesting... Might this be an issue?
In any case, don't put it here.

> +    if len(args) == 1:
> +      key, = args
> +      return self[key].Get()
> +    elif len(args) == 2:
> +      key, default = args
> +      if key not in self:
> +        return default
> +      else:
> +        return self[key].Get()
> +    else:
> +      raise TypeError("GetPrivate() takes 2 arguments (%d given)" % 
> len(args))
> +
> +  def Unprivate(self):
> +    """Turn this dict of Private() values to a dict of values.
> +

Needs "Example::"

> +    >>> PrivateDict({"foo": "bar"}).Unprivate()
> +    {'foo': 'bar'}
> +
> +    @rtype: dict

Needs newline.

> +    """
> +    returndict = {}
> +    for key in self:
> +      returndict[key] = self[key].Get()
> +    return returndict
> diff --git a/src/Ganeti/JSON.hs b/src/Ganeti/JSON.hs
> index c1daa5a..fe84d8a 100644
> --- a/src/Ganeti/JSON.hs
> +++ b/src/Ganeti/JSON.hs
> @@ -48,6 +48,7 @@ module Ganeti.JSON
>    , toArray
>    , optionalJSField
>    , optFieldsToObj
> +  , readContainer
>    , HasStringRepr(..)
>    , GenericContainer(..)
>    , Container
> diff --git a/src/Ganeti/Types.hs b/src/Ganeti/Types.hs
> index 2050cf1..28961dd 100644
> --- a/src/Ganeti/Types.hs
> +++ b/src/Ganeti/Types.hs
> @@ -161,8 +161,11 @@ module Ganeti.Types
>    , hotplugActionToRaw
>    , ParamVisibility(..)
>    , paramVisibilityToRaw
> +  , Private(..)
> +  , showPrivateJSObject
>    ) where
>  
> +import Control.Applicative
>  import Control.Monad (liftM)
>  import qualified Text.JSON as JSON
>  import Text.JSON (JSON, readJSON, showJSON)
> @@ -903,3 +906,47 @@ $(THH.makeJSONInstance ''ParamVisibility)
>  
>  instance THH.PyValue ParamVisibility where
>    showValue = show . paramVisibilityToRaw

It would be nice to add a Haddock section here, like so

-- * Private type and instances

> +
> +-- | A container for values that should be happy to be manipulated yet
> +-- | refuses to be shown unless explicitly requested.
> +

The format is as follows (pay attention to the bars and the newlines):

  -- | Doc ...
  -- no bar at this level ...
  newtype Private ...

> +newtype Private value = Private { getPrivate :: value }
> +  deriving Eq

s/value/a/

> +
> +instance (Show a, JSON.JSON a) => JSON.JSON (Private a) where
> +  readJSON json = Private <$> JSON.readJSON json

If you could write this line in point-free style, it would be really good.

> +  showJSON (Private a) = JSON.showJSON a

Instead
  (Private x) = JSON.showJSON x

> +
> +-- It would be better not to implement this at all.
> +-- Alas, Show OpCode requires Show Private.

Format problem.

> +instance (Show a) => Show (Private a) where

No parenthesis here (Show a)

  instance Show a => Show (Private a) where

> +  show _ = "<redacted>"
> +
> +instance (THH.PyValue a) => THH.PyValue (Private a) where

No parens here as well (on the left side)

> +  showValue (Private a) = "Private(" ++ THH.showValue a ++ ")"
> +
> +instance Functor Private where
> +  fmap f (Private x) = Private $ f x
> +
> +instance Monad Private where
> +  (Private x) >>= f = f x
> +  return = Private
> +
> +--instance (JSON.JSON a) => PrivateJSON (Private a) where
> +--  unprivateAndShowJSON l | l >= Private_ = JSON.showJSON . getPrivate
> +--  unprivateAndShowJSON _                 = JSON.showJSON
> +
> +--instance (JSON.JSON a) => JSON.JSON [(String, Private JSON.JSValue)] where
> +--  showJSON value = JSON.toJSObject $ map f value
> +--    where f (k, v) = (k, Private $ JSON.showJSON v)
> +
> +showPrivateJSObject :: (JSON.JSON a) =>
> +                       [(String, a)] -> JSON.JSObject (Private JSON.JSValue)
> +showPrivateJSObject value = JSON.toJSObject $ map f value
> +  where f (k, v) = (k, Private $ JSON.showJSON v)
> +
> +--readPrivateJSObject :: String ->
> +--                       JSON.Result (JSON.JSObject (Private JSON.JSValue))
> +--readPrivateJSObject json = do
> +--  jsobj <- JSON.decode json
> +--  return fmap Private $ readContainer jsobj
> diff --git a/test/hs/Test/Ganeti/Objects.hs b/test/hs/Test/Ganeti/Objects.hs
> index 50a5efb..dbae17f 100644
> --- a/test/hs/Test/Ganeti/Objects.hs
> +++ b/test/hs/Test/Ganeti/Objects.hs
> @@ -219,6 +219,9 @@ instance Arbitrary ClusterNicParams where
>  instance Arbitrary OsParams where
>    arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
>  
> +instance (Arbitrary a) => Arbitrary (Private a) where

No parens on the left side.

> +  arbitrary = Private <$> arbitrary
> +
>  instance Arbitrary ClusterOsParams where
>    arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
>  
> diff --git a/test/hs/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs
> index e0a3b69..0719ae1 100644
> --- a/test/hs/Test/Ganeti/OpCodes.hs
> +++ b/test/hs/Test/Ganeti/OpCodes.hs
> @@ -430,6 +430,14 @@ genMacPrefix = do
>    octets <- vectorOf 3 $ choose (0::Int, 255)
>    mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
>  
> +-- | JSObject of arbitrary data.

Newline here between the short description and the big description.

Thanks,
Jose

> +-- Since JSValue does not implement Arbitrary, I'll simply generate
> +-- (String, String) objects.
> +arbitraryPrivateJSObj :: Gen (J.JSObject (Private J.JSValue))
> +arbitraryPrivateJSObj = constructor <$> (fromNonEmpty <$> genNameNE)
> +                                    <*> (fromNonEmpty <$> genNameNE)
> +    where constructor k v = showPrivateJSObject [(k, v)]
> +

body of the function should go below the definition, and not to the
right of equals (=).  For example,

  arbitraryPrivateJSObj =
    body goes here

Also, you can make constructor point-free, for example (untested)

  where constructor = showPrivateJSObject . [] . (,)

Thanks,
Jose

>  -- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
>  $(genArbitrary ''OpCodes.MetaOpCode)
>  
> -- 
> 1.9.0.rc1.175.g0b1dcb5
> 

-- 
Jose Antonio Lopes
Ganeti Engineering
Google Germany GmbH
Dienerstr. 12, 80331, München

Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
Geschäftsführer: Graham Law, Christine Elizabeth Flores
Steuernummer: 48/725/00206
Umsatzsteueridentifikationsnummer: DE813741370

Reply via email to