# -*- coding: utf-8 -*-
"""Defines very basic constants, classes and methods named after `Sparta <https://en.wikipedia.org/wiki/Sparta>`_ .
We keep this module simple and `Laconic <https://en.wikipedia.org/wiki/Laconic_phrase>`_ so No imports in this module
"""
# Date - Time formats
FMT_HTTP_DATE = "%a, %d %b %Y %H:%M:%S GMT"
FMT_RFC_2822_DATE_FMT = "%a, %d %b %Y %H:%M:%S +0000"
FMT_DT_GENERIC = "%y%m%d %H:%M:%S" # generic date time format
FMT_T_GENERIC = "%H:%M:%S" # generic date format
FMT_DT_COMPR = "%y%m%d%H%M%S%f%V%u" # compressed date+time+milliseconds + weekday + weeknumber
FMT_DT_COMPR_SI = "%y%m%d%H%M%S%V%u" # compressed date+time+weekday + weeknumber
FMT_DT_COMPR_S = "%y%m%d%H%M%S" # compressed up to seconds
FMT_DT_COMPR_M = "%y%m%d%H%M" # compressed up to minute
FMT_DT_COMPR_H = "%y%m%d%H" # compressed up to Hour
FMT_DHMS_DICT = "{days:03d}-{hours:02d}:{minutes:02d}:{seconds:02d}"
from Hellas import _IS_PY2
# other formats
FMT_INT_SEP = "{:,d}" # integer with comma separator every 3 digits
[docs]def seconds_to_DHMS(seconds, as_str=True):
"""converts seconds to Days, Hours, Minutes, Seconds
:param int seconds: number of seconds
:param bool as_string: to return a formated string defaults to True
:returns: a formated string if as_str else a dictionary
:Example:
>>> seconds_to_DHMS(60*60*24)
001-00:00:00
>>> seconds_to_DHMS(60*60*24, False)
{'hours': 0, 'seconds': 0, 'minutes': 0, 'days': 1}
"""
d = DotDot()
d.days = int(seconds // (3600 * 24))
d.hours = int((seconds // 3600) % 24)
d.minutes = int((seconds // 60) % 60)
d.seconds = int(seconds % 60)
return FMT_DHMS_DICT.format(**d) if as_str else d
[docs]def elapsed_seconds(dt_start, dt_end):
(dt_end - dt_start).total_seconds()
[docs]class Error(Exception):
pass
[docs]class DotDot(dict):
"""
A dictionary that can handle dot notation to access its members (useful when parsing JSON content),
although it can perform write operations using dot notation on single level dictionary its mainly use is for reads
also to keep casting to it cheap it doesn't handle creating multilevel keys using dot notation.
For this functionality look for `easydict <https://github.com/makinacorpus/easydict>`_
or `addict <https://github.com/mewwts/addict>`_
:Example:
>>> dd = DotDot()
>>> dd.a = 1
>>> dd
{'a': 1}
>>> dd.b.c = 100
'AttributeError ... '
>>> dd.b = {'b1': 21, 'b2': 22}
>>> dd.b.b3 = 23
>>> dd
{'a': 1, 'b': {'b1': 21, 'b2': 22}, 'b3': 23}
.. Warning:: don't use for write operations on a nested key using dot notation
i.e: `del dd.a.b` or dd.a.b = 1 or dd.a.b +=1 **(it will fail silently !)**
"""
def __getattr__(self, attr):
try:
item = self[attr]
except KeyError as e:
raise AttributeError(e) # expected Error by pickle on __getstate__ etc
if isinstance(item, dict) and not isinstance(item, DotDot):
item = DotDot(item)
return item
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
[docs]class DictDK(dict):
'''a dictionary with a single predefined key
subclasses can redefine the key as:
>>> class FOO (DictDK):
>>> key = 'bar'
or create an instance with new default key:
>>> f = type('FOO', (DictDK,), dict(key='foo'))
'''
key = ''
[docs] def __init__(self, val):
super(DictDK, self).__init__([(self.key, val)])
[docs]def dict_encode(in_dict):
"""returns a new dictionary with encoded values useful for encoding http queries (python < 3)"""
if _IS_PY2:
out_dict = {}
for k, v in list(in_dict.items()):
if isinstance(v, unicode):
v = v.encode('utf8')
elif isinstance(v, str):
# Must be encoded in UTF-8
v.decode('utf8')
out_dict[k] = v
return out_dict
else:
raise NotImplementedError
[docs]class AdHocTree(object):
"""builds an arbitrary tree structure using object attributes
:Usage:
>>> aht = AdHocTree().foo.bar
>>> aht
<AdHocTree: root/foo/bar>
- can be extended:
>>> newtree = newtree = aht.new_foo.new_bar
>>> newtree
<AdHocTree: root/foo/bar/new_foo/new_bar>
"""
__slots__ = ['parent', 'name'] # don't create __dict__ just those 2 slots
[docs] def __init__(self, parent=None, name="root"):
"""
:param obj parent: parent object, defaults to None
:param str name: name of the Tree, defaults to root
"""
self.parent = parent
self.name = name
[docs] def __call__(self, *args, **kwargs):
"""calls _adHocCmd_ method on root's parent if exists"""
elements = list(self)
try:
cmd = elements[-1].parent.__getattribute__('_adHocCmd_')
# we don't use get or getattr here to avoid circular references
except AttributeError:
raise NotImplementedError("_adHocCmd_ {:!s}".format((type(elements[-1].parent))))
return cmd(elements[0], *args, **kwargs)
def __getattr__(self, attr):
return AdHocTree(self, attr)
[docs] def __reduce__(self):
"""its pickle-able"""
return (self.__class__, (self.parent, self.name))
[docs] def __iter__(self):
"""iterates breadth-first up to root"""
curAttr = self
while isinstance(curAttr, AdHocTree):
yield curAttr
curAttr = curAttr.parent
def __reversed__(self):
return reversed(list(self))
def __str__(self, separator="/"):
return self.path()
def __repr__(self):
return '<{}: {}>'.format(self.__class__.__name__, self.path())
[docs] def path(self, separator="/"):
""":returns: a string representing the path to root element separated by separator"""
rt = ""
for i in reversed(self):
rt = "{}{}{}".format(rt, i.name, separator)
return rt[:-1]
[docs] def root_and_path(self):
""":returns: a tuple (parent, [members,... ]"""
rt = []
curAttr = self
while isinstance(curAttr.parent, AdHocTree):
rt.append(curAttr.name)
curAttr = curAttr.parent
rt.reverse()
return (curAttr.parent, rt)
[docs]class EnumLabels(object):
"""A simple class for enumerating labels to values descendants are easily auto documented with sphinx
:Example:
>>> class EnumColors(EnumLabels):
>>> RED = 1
>>> GREEN = 2
>>> EnumColors.GREEN
2
"""
@classmethod
[docs] def value_name(cls, value):
"""
Returns the label from a value if label exists otherwise returns the value
since method does a reverse look up it is slow
"""
for k, v in list(cls.__dict__.items()):
if v == value:
return k
return value
[docs]def relations_dict(rel_lst):
"""constructs a relation's dictionary from a list that describes amphidromus relations between objects
:param list rel_lst: a relationships list of the form [[a,b],[c, a, b]] # can include duplicates
:returns: a dictionary
:Example:
>>> rl = [('a', 'b', 'c'), ('a', 'x', 'y'), ('x', 'y', 'z')]
>>> relations_dict(rl)
{'a': ['x', 'c', 'b', 'y'], 'c': ['a', 'b'], 'b': ['a', 'c'], 'y': ['a', 'x', 'z'], 'x': ['a', 'z', 'y'],
'z': ['y', 'x']}
"""
dc = {}
for c in rel_lst:
for i in c:
for k in c:
dc.setdefault(i, [])
dc[i].append(k)
do = {}
for k in list(dc.keys()):
if dc[k]:
vl = list(set(dc[k])) # remove duplicates
vl.remove(k)
do[k] = vl
return do
[docs]def chunks(sliceable, n):
"""
returns a list of lists of any sliceable object each of max lentgh n
:Parameters:
-sliceable: (string|list|tuple) any sliceable object
- n max elements of ech chunk
:Example:
>>> chunksn([1,2,3,4,5,6,7,8,9,'x'], 4)
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 'x']]
>>> chunksn('123456789X', 3)
['123', '456', '789', 'X']
"""
return [sliceable[i:i+n] for i in range(0, len(sliceable), n)]
[docs]def chunks_str(str, n, separator="\n", fill_blanks_last=True):
"""returns lines with max n characters
:Example:
>>> print (chunks_str('123456X', 3))
123
456
X
"""
return separator.join(chunks(str, n))
[docs]def chunks_str_frame(a_str, n=None, center=True):
"""places a frame around a string
:Parameters:
- a_str: string to frame
- n: number of chars in each line
- center: center string in frame if True and n > len(str)
:Example:
>>> print(chunks_str_frame('the quick brown fox', 44))
╔════════════════════════════════════════════╗
║ the quick brown fox ║
╚════════════════════════════════════════════╝
>>> print(chunks_str_frame('the quick brown fox',12, False))
╔════════════╗
║the quick br║
║own fox ║
╚════════════╝
"""
if n is None:
n = len(str)
elif n > len(str) and center is True:
a_str = a_str.center(n)
spcs = "" if n == 1 or n == len(a_str) else " " * (n - (len(str) % n))
n = len(a_str) if n is None else n
r = chunks_str(a_str, n, "║\n║")
return "╔{}╗\n║{}{}║\n╚{}╝".format('═' * n, r, spcs, '═' * n)
[docs]def unicode_available():
"""checks to see if unicode is available basically distinguishes between python 2.x and 3.x"""
try:
unicode
return True
except NameError:
return False
[docs]def unicode_or_str():
return unicode if unicode_available() else str