You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/lib/utils/tools.py

400 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- coding: utf-8 -*-
# Copyright 2014-2016 Mir Calculate. http://www.calculate-linux.org
#
# 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.
from functools import wraps
from itertools import tee
from functools import total_ordering
from contextlib import contextmanager
try:
import json as json_module
except ImportError:
json_module = None
class SavableIterator:
"""
Итератор с возможность сохранять и восстанавливать состояние
"""
def __init__(self, seq):
self.seq = iter(seq)
self.back = None
def __iter__(self):
return self
def save(self):
self.seq, self.back = tee(self.seq)
return self
def restore(self):
if self.back:
self.seq, self.back = tee(self.back)
return self
def next(self):
return self.seq.next()
@contextmanager
def ignore(exception):
try:
yield
except exception:
pass
class AddonError(Exception):
"""
Исключение с добавочным сообщением
"""
def __init__(self, msg, addon=None):
self.message = msg
self.addon = addon
Exception.__init__(self, msg)
class FalseObject(object):
"""
Объект-заглушка при любом сравнении возвращает False
"""
def __lt__(self, other):
return False
def __nonzero__(self):
return False
__gt__ = __ge__ = __le__ = __eq__ = __ne__ = __lt__
class Sizes(object):
K = KiB = kibibyte = 1024
kB = kilobyte = 1000
M = MiB = mibibyte = K * 1024
Mb = megabyte = kB * 1000
G = GiB = gibibyte = M * 1024
Gb = gigabyte = Mb * 1000
T = TiB = tibibyte = G * 1024
Tb = terabyte = Gb * 1000
def __getattr__(self, name):
if name.startswith('from_'):
return lambda x: x * getattr(Sizes, name[5:])
elif name.startswith('to_'):
return lambda x: x / getattr(Sizes, name[3:])
else:
raise AttributeError
def imap_regexp(re_compiled, l, whole=False):
"""
Обработать список регулярным выражением и вернуть полученные группы
"""
if whole:
retfunc = lambda x: x.group()
else:
retfunc = lambda x: x.groups()
return (retfunc(x) for x in (re_compiled.search(x) for x in l) if x)
def cached(each_instance=False):
"""
Кэширование результата
"""
def cache_wrapper(func):
value = {}
def wrapper(*args, **kwargs):
if each_instance:
if args[0] not in value:
value[args[0]] = func(*args, **kwargs)
return value[args[0]]
else:
if None not in value:
value[None] = func(*args, **kwargs)
return value[None]
return wrapper
if each_instance in (True, False):
return cache_wrapper
else:
return cache_wrapper(each_instance)
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kw):
if cls not in cls._instances:
create_obj = getattr(super(Singleton, cls), '__call__')
cls._instances[cls] = create_obj(*args, **kw)
return cls._instances[cls]
class SingletonParam(type):
def __init__(cls, name, bases, dicts):
super(SingletonParam, cls).__init__(name, bases, dicts)
cls.instance = {}
def __call__(cls, *args, **kw):
if hasattr(cls, "param_id"):
keyarg = cls.param_id(*args, **kw)
else:
keyarg = args[0] if args else ""
if keyarg not in cls.instance:
create_obj = getattr(super(SingletonParam, cls), '__call__')
cls.instance[keyarg] = create_obj(*args, **kw)
return cls.instance[keyarg]
class ClassifyingIterator(object):
"""
Классифицирующий итератор
"""
def classificate(iterator):
"""
Классифицировать элементы по признаку (first первый last последний)
"""
class Mark:
def __init__(self, first=False, last=False):
self.first = first
self.last = last
def __repr__(self):
return "Mark(first=%s,last=%s)" % (self.first, self.last)
iterator = iter(iterator)
obj = iterator.next()
try:
obj_next = iterator.next()
yield Mark(first=True, last=False), obj
try:
while True:
obj = obj_next
obj_next = iterator.next()
yield Mark(), obj
except StopIteration:
yield Mark(first=False, last=True), obj
except StopIteration:
yield Mark(first=True, last=True), obj
def debug(prefix="DEBUG {name}({args},{kw})={ret}"):
def debug_decor(f):
@wraps(f)
def wrapper(*args, **kw):
ret = f(*args, **kw)
print prefix.format(name=f.func_name, args=args, kw=kw, ret=ret)
return ret
return wrapper
return debug_decor
class ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
return self.fget.__get__(obj, klass)()
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
type_ = type(obj)
return self.fset.__get__(obj, type_)(value)
def setter(self, func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
self.fset = func
return self
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
import os
import fcntl
from os import path
import time
class LockError(Exception):
pass
class Locker(object):
locker_directory = '/run/calculate/locker'
def __init__(self, name=None, fn=None, timeout=None):
if name is not None:
if not path.exists(self.locker_directory):
os.makedirs(self.locker_directory)
self.fn = path.join(self.locker_directory, name)
else:
dn = path.dirname(fn)
if not path.exists(dn):
os.makedirs(dn)
self.fn = fn
self.lockf = None
if timeout is None:
self.timeout = 10
else:
self.timeout = 0
def lockfile(self, fn, timeout=None):
"""
Блокировка файла с таймаутом
"""
if timeout is None:
timeout = self.timeout
for i in range(0, timeout * 40 or 1):
try:
self.lockf = os.open(fn, os.O_CREAT | os.O_RDWR)
fcntl.flock(self.lockf, fcntl.LOCK_EX | fcntl.LOCK_NB)
return True
except IOError, e:
if e.errno != os.errno.EAGAIN:
raise e
os.close(self.lockf)
self.lockf = None
except OSError, e:
if e.errno != os.errno.EEXIST:
raise e
time.sleep(0.25)
else:
raise LockError("Lock timeout of %s" % fn)
def acquire(self):
if self.lockf is None:
self.lockfile(self.fn)
return True
def release(self):
if self.lockf is not None:
os.close(self.lockf)
self.lockf = None
return True
def remove(self):
self.release()
if path.exists(self.fn):
try:
os.remove(self.fn)
except (OSError, IOError):
pass
def __enter__(self):
""" Activated when used in the with statement.
Should automatically acquire a lock to be used in the with block.
"""
self.acquire()
return self
def __exit__(self, type, value, traceback):
""" Activated at the end of the with statement.
It automatically releases the lock if it isn't locked.
"""
self.release()
def __del__(self):
""" Make sure that the FileLock instance doesn't leave a lockfile
lying around.
"""
self.release()
class Signal(object):
def __init__(self):
self.subscribers = []
def connect(self, subscriber, unique=True):
if not unique or subscriber not in self.subscribers:
self.subscribers.append(subscriber)
def emit(self, *args, **kwargs):
for subscriber in self.subscribers:
subscriber(*args, **kwargs)
def disconnect(self, subscriber, removeall=False):
while True:
if subscriber in self.subscribers:
self.subscribers.remove(subscriber)
if removeall:
continue
break
def max_default(iterable, key=lambda x: x, default=None):
try:
return max(iterable, key=key)
except ValueError:
return default
def traverse(o, tree_types=(list, tuple)):
"""
Вернуть последовательно все элемены списка, "распаковов" встроенные
[1, 2, [1,2,[4,6,7]], 7] -> (1,2,1,2,4,6,7,7)
:param o:
:param tree_types: типы, которые будут распакованы
:return:
"""
if isinstance(o, tree_types):
for value in o:
for subvalue in traverse(value, tree_types):
yield subvalue
else:
yield o
@total_ordering
class ReverseKey(object):
"""
Объект для создания ключей обратной сортировки
s = [("A","B"), ("A","C"), ("B","A")]
sorted(s, key=lambda x: (x[0], ReverseKey(x[1]))
"""
__slots__ = ['data']
def __init__(self, data):
self.data = data
def __eq__(self, o):
return o.data == self.data
def __lt__(self, o):
return o.data < self.data