# -*- python -*-
# -*- coding: utf-8 -*-
#
# This file is part of the easydev software
#
# Copyright (c) 2011-2014
#
# File author(s): Thomas Cokelaer <cokelaer@gmail.com>
#
# Distributed under the GPLv3 License.
# See accompanying file LICENSE.txt or copy at
# http://www.gnu.org/licenses/gpl-3.0.html
#
# Website: https://github.com/cokelaer/easydev
# Documentation: http://packages.python.org/easydev
#
##############################################################################
# $:Id $
"""toolkit to ease development"""
import subprocess
import json
import os
import sys
__all__ = ["shellcmd", "checkParam", "swapdict", "check_param_in_list",
"check_range", "precision", "AttrDict", "DevTools", "execute",
"touch"]
[docs]def precision(data, digit=2):
"""Return the value with only 2 digits
::
>>> precision(2.123)
2.12
>>> precision(2123, digit=-2)
2100
"""
data = int(data*pow(10, digit))
data /= pow(10., digit)
return data
[docs]def check_range(value, a, b, strict=False):
"""Check that a value lies in a given range
:param value: value to test
:param a: lower bound
:param b: upper bound
:return: nothing
.. doctest::
>>> from easydev.tools import check_range
>>> check_range(1,0, 2)
"""
if strict is True:
if value <= a:
raise ValueError(" {} must be greater (or equal) than {}".format(value, a))
if value >= b:
raise ValueError(" {} must be less (or less) than {}".format(value, b))
elif strict is False:
if value < a:
raise ValueError(" {} must be greater than {}".format(value, a))
if value > b:
raise ValueError(" {} must be less than {}".format(value, b))
[docs]def checkParam(param, valid_values):
"""
.. warning:: deprecated since 0.6.10 use :meth:`check_param_in_list` instead
"""
print("easydev WARNING:: deprecated; use check_param_in_list instead.")
check_param_in_list(param, valid_values)
[docs]def check_param_in_list(param, valid_values, name=None):
"""Checks that the value of param is amongst valid
:param param: a parameter to be checked
:param list valid_values: a list of values
::
check_param_in_list(1, [1,2,3])
check_param_in_list(mode, ["on", "off"])
"""
if isinstance(valid_values, list) is False:
raise TypeError("the valid_values second argument must be a list of valid values. {0} was provided.".format(valid_values))
if param not in valid_values:
if name:
msg = "Incorrect value provided for {} ({})".format(name, param)
else:
msg = "Incorrect value provided (%s)" % param
msg += " Correct values are %s" % valid_values
raise ValueError(msg)
[docs]def shellcmd(cmd, show=False, verbose=False, ignore_errors=False):
"""An alias to run system commands.
Based on subprocess.Popen.
:param str cmd: the command to call
:param bool show: print the command
:param bool verbose: print the output
:return: the output as a string
"""
if show:
print(cmd)
try:
ret = subprocess.Popen([cmd], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=True)
output = ret.stdout.read().strip()
error = ret.stderr.read().strip()
ret.wait()
if len(error) > 0:
if ignore_errors is False:
raise Exception(error)
else:
if verbose is True:
print("Errors/Warning" + str(error))
if verbose is True:
print(output)
return output
except Exception as err:
raise Exception("Error:: Command (%s) failed. Error message is %s" % (cmd, err))
[docs]def execute(cmd, showcmd=True, verbose=True):
import pexpect
if showcmd is True:
print(cmd)
p = pexpect.spawn(cmd,timeout=None)
line = p.readline()
while line:
if verbose:
try:
sys.stdout.write(line.decode())
except:
sys.stdout.write(line)
sys.stdout.flush()
line = p.readline()
[docs]def touch(fname, times=None):
with open(fname, 'a'):
os.utime(fname, times)
[docs]def swapdict(dic, check_ambiguity=True):
"""Swap keys for values in a dictionary
::
>>> d = {'a':1}
>>> swapdict(d)
{1:'a'}
"""
# this version is more elegant but slightly slower : return {v:k for k,v in dic.items()}
if check_ambiguity:
assert len(set(dic.keys())) == len(set(dic.values())), "values is not a set. ambiguities for keys."
return dict(zip(dic.values(), dic.keys()))
[docs]class AttrDict(dict):
"""dictionary-like object that exposes its keys as attributes.
When you have dictionary of dictionaries with many levels e.g.::
d = {'a': {'a1': {'a2': 2}}}
to get/set a values, one has to type something like::
d['a']['a1']['a2'] = 3
The :class:`AttrDict` allows the dictionary to work as attributes::
ad = AttrDict(**d)
ad.a.a1.a2 = 3
You can now add values as attribute, or with ['key'] syntax
.. doctest::
>>> from easydev import AttrDict
>>> a = AttrDict('value': 1)
>>> a.value
1
>>>
>>> a.unit = 'meter'
>>> a.keys()
['value', 'meter']
If you need to add new simple values after the creation of the instance,
just use the setter::
>>> d['newa'] = 2
>>> d.newa = 2 # equivalent to the statement above
but if you want to set a dictionary (whichever recursive level), use
the :meth:`update` method::
>>> d.update({'newd': {'g': {'h':2}}})
>>> d.newd.g.h
2
Note that if you use the setter for a value that is a dictionary, e.g.::
ad.a = {'b':1}
then *a* is indeed a dictionary.
"""
def __init__(self, **kwargs):
dict.__init__(self, kwargs)
self.__dict__ = self
self.update(kwargs)
[docs] def update(self, content):
"""See class/constructor documentation for details
:param dict content: a valid dictionary
"""
# accepts dict and attrdict classes
if content.__class__ not in [dict, AttrDict]:
raise TypeError
for k, v in content.items():
if v.__class__ not in [dict, AttrDict]:
# fixme copy ?
self[k] = v
else:
self[k] = AttrDict(**v)
[docs] def from_json(self, filename):
"""
does not remove existing keys put replace them if already present
"""
res = json.load(open(filename, "r"))
for k,v in res.items():
self[k] = v
[docs] def to_json(self, filename=None):
import json
if filename is not None:
with open(filename, "w") as fout:
json.dump(self, fout)
else:
return json.dumps(self)