310 lines
8.2 KiB
Python
310 lines
8.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
logbook.helpers
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Various helper functions
|
|
|
|
:copyright: (c) 2010 by Armin Ronacher, Georg Brandl.
|
|
:license: BSD, see LICENSE for more details.
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
import errno
|
|
import time
|
|
import random
|
|
from datetime import datetime, timedelta
|
|
|
|
PY2 = sys.version_info[0] == 2
|
|
|
|
if PY2:
|
|
import __builtin__ as _builtins
|
|
import collections as collections_abc
|
|
else:
|
|
import builtins as _builtins
|
|
import collections.abc as collections_abc
|
|
|
|
try:
|
|
import json
|
|
except ImportError:
|
|
import simplejson as json
|
|
|
|
if PY2:
|
|
from cStringIO import StringIO
|
|
iteritems = dict.iteritems
|
|
from itertools import izip as zip
|
|
xrange = _builtins.xrange
|
|
else:
|
|
from io import StringIO
|
|
zip = _builtins.zip
|
|
xrange = range
|
|
iteritems = dict.items
|
|
|
|
_IDENTITY = lambda obj: obj
|
|
|
|
if PY2:
|
|
def u(s):
|
|
return unicode(s, "unicode_escape")
|
|
else:
|
|
u = _IDENTITY
|
|
|
|
if PY2:
|
|
integer_types = (int, long)
|
|
string_types = (basestring,)
|
|
else:
|
|
integer_types = (int,)
|
|
string_types = (str,)
|
|
|
|
if PY2:
|
|
import httplib as http_client
|
|
else:
|
|
from http import client as http_client
|
|
|
|
if PY2:
|
|
# Yucky, but apparently that's the only way to do this
|
|
exec("""
|
|
def reraise(tp, value, tb=None):
|
|
raise tp, value, tb
|
|
""", locals(), globals())
|
|
else:
|
|
def reraise(tp, value, tb=None):
|
|
if value.__traceback__ is not tb:
|
|
raise value.with_traceback(tb)
|
|
raise value
|
|
|
|
|
|
# this regexp also matches incompatible dates like 20070101 because
|
|
# some libraries (like the python xmlrpclib modules) use this
|
|
_iso8601_re = re.compile(
|
|
# date
|
|
r'(\d{4})(?:-?(\d{2})(?:-?(\d{2}))?)?'
|
|
# time
|
|
r'(?:T(\d{2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?(Z|[+-]\d{2}:\d{2})?)?$'
|
|
)
|
|
_missing = object()
|
|
if PY2:
|
|
def b(x):
|
|
return x
|
|
|
|
def _is_text_stream(x):
|
|
return True
|
|
else:
|
|
import io
|
|
|
|
def b(x):
|
|
return x.encode('ascii')
|
|
|
|
def _is_text_stream(stream):
|
|
return isinstance(stream, io.TextIOBase)
|
|
|
|
|
|
can_rename_open_file = False
|
|
if os.name == 'nt':
|
|
try:
|
|
import ctypes
|
|
|
|
_MOVEFILE_REPLACE_EXISTING = 0x1
|
|
_MOVEFILE_WRITE_THROUGH = 0x8
|
|
_MoveFileEx = ctypes.windll.kernel32.MoveFileExW
|
|
|
|
def _rename(src, dst):
|
|
if PY2:
|
|
if not isinstance(src, unicode):
|
|
src = unicode(src, sys.getfilesystemencoding())
|
|
if not isinstance(dst, unicode):
|
|
dst = unicode(dst, sys.getfilesystemencoding())
|
|
if _rename_atomic(src, dst):
|
|
return True
|
|
retry = 0
|
|
rv = False
|
|
while not rv and retry < 100:
|
|
rv = _MoveFileEx(src, dst, _MOVEFILE_REPLACE_EXISTING |
|
|
_MOVEFILE_WRITE_THROUGH)
|
|
if not rv:
|
|
time.sleep(0.001)
|
|
retry += 1
|
|
return rv
|
|
|
|
# new in Vista and Windows Server 2008
|
|
_CreateTransaction = ctypes.windll.ktmw32.CreateTransaction
|
|
_CommitTransaction = ctypes.windll.ktmw32.CommitTransaction
|
|
_MoveFileTransacted = ctypes.windll.kernel32.MoveFileTransactedW
|
|
_CloseHandle = ctypes.windll.kernel32.CloseHandle
|
|
can_rename_open_file = True
|
|
|
|
def _rename_atomic(src, dst):
|
|
ta = _CreateTransaction(None, 0, 0, 0, 0, 1000, 'Logbook rename')
|
|
if ta == -1:
|
|
return False
|
|
try:
|
|
retry = 0
|
|
rv = False
|
|
while not rv and retry < 100:
|
|
rv = _MoveFileTransacted(src, dst, None, None,
|
|
_MOVEFILE_REPLACE_EXISTING |
|
|
_MOVEFILE_WRITE_THROUGH, ta)
|
|
if rv:
|
|
rv = _CommitTransaction(ta)
|
|
break
|
|
else:
|
|
time.sleep(0.001)
|
|
retry += 1
|
|
return rv
|
|
finally:
|
|
_CloseHandle(ta)
|
|
except Exception:
|
|
def _rename(src, dst):
|
|
return False
|
|
|
|
def _rename_atomic(src, dst):
|
|
return False
|
|
|
|
def rename(src, dst):
|
|
# Try atomic or pseudo-atomic rename
|
|
if _rename(src, dst):
|
|
return
|
|
# Fall back to "move away and replace"
|
|
try:
|
|
os.rename(src, dst)
|
|
except OSError:
|
|
e = sys.exc_info()[1]
|
|
if e.errno not in (errno.EEXIST, errno.EACCES):
|
|
raise
|
|
old = "%s-%08x" % (dst, random.randint(0, 2 ** 31 - 1))
|
|
os.rename(dst, old)
|
|
os.rename(src, dst)
|
|
try:
|
|
os.unlink(old)
|
|
except Exception:
|
|
pass
|
|
else:
|
|
rename = os.rename
|
|
can_rename_open_file = True
|
|
|
|
_JSON_SIMPLE_TYPES = (bool, float) + integer_types + string_types
|
|
|
|
|
|
def to_safe_json(data):
|
|
"""Makes a data structure safe for JSON silently discarding invalid
|
|
objects from nested structures. This also converts dates.
|
|
"""
|
|
def _convert(obj):
|
|
if obj is None:
|
|
return None
|
|
elif PY2 and isinstance(obj, str):
|
|
return obj.decode('utf-8', 'replace')
|
|
elif isinstance(obj, _JSON_SIMPLE_TYPES):
|
|
return obj
|
|
elif isinstance(obj, datetime):
|
|
return format_iso8601(obj)
|
|
elif isinstance(obj, list):
|
|
return [_convert(x) for x in obj]
|
|
elif isinstance(obj, tuple):
|
|
return tuple(_convert(x) for x in obj)
|
|
elif isinstance(obj, dict):
|
|
rv = {}
|
|
for key, value in iteritems(obj):
|
|
if not isinstance(key, string_types):
|
|
key = str(key)
|
|
if not is_unicode(key):
|
|
key = u(key)
|
|
rv[key] = _convert(value)
|
|
return rv
|
|
return _convert(data)
|
|
|
|
|
|
def format_iso8601(d=None):
|
|
"""Returns a date in iso8601 format."""
|
|
if d is None:
|
|
d = datetime.utcnow()
|
|
rv = d.strftime('%Y-%m-%dT%H:%M:%S')
|
|
if d.microsecond:
|
|
rv += '.' + str(d.microsecond)
|
|
return rv + 'Z'
|
|
|
|
|
|
def parse_iso8601(value):
|
|
"""Parse an iso8601 date into a datetime object. The timezone is
|
|
normalized to UTC.
|
|
"""
|
|
m = _iso8601_re.match(value)
|
|
if m is None:
|
|
raise ValueError('not a valid iso8601 date value')
|
|
|
|
groups = m.groups()
|
|
args = []
|
|
for group in groups[:-2]:
|
|
if group is not None:
|
|
group = int(group)
|
|
args.append(group)
|
|
seconds = groups[-2]
|
|
if seconds is not None:
|
|
if '.' in seconds:
|
|
sec, usec = seconds.split('.')
|
|
args.append(int(sec))
|
|
args.append(int(usec.ljust(6, '0')))
|
|
else:
|
|
args.append(int(seconds))
|
|
|
|
rv = datetime(*args)
|
|
tz = groups[-1]
|
|
if tz and tz != 'Z':
|
|
args = [int(x) for x in tz[1:].split(':')]
|
|
delta = timedelta(hours=args[0], minutes=args[1])
|
|
if tz[0] == '+':
|
|
rv -= delta
|
|
else:
|
|
rv += delta
|
|
|
|
return rv
|
|
|
|
|
|
def get_application_name():
|
|
if not sys.argv or not sys.argv[0]:
|
|
return 'Python'
|
|
return os.path.basename(sys.argv[0]).title()
|
|
|
|
|
|
class cached_property(object):
|
|
"""A property that is lazily calculated and then cached."""
|
|
|
|
def __init__(self, func, name=None, doc=None):
|
|
self.__name__ = name or func.__name__
|
|
self.__module__ = func.__module__
|
|
self.__doc__ = doc or func.__doc__
|
|
self.func = func
|
|
|
|
def __get__(self, obj, type=None):
|
|
if obj is None:
|
|
return self
|
|
value = obj.__dict__.get(self.__name__, _missing)
|
|
if value is _missing:
|
|
value = self.func(obj)
|
|
obj.__dict__[self.__name__] = value
|
|
return value
|
|
|
|
|
|
def get_iterator_next_method(it):
|
|
return lambda: next(it)
|
|
|
|
|
|
# python 2 support functions and aliases
|
|
def is_unicode(x):
|
|
if PY2:
|
|
return isinstance(x, unicode)
|
|
return isinstance(x, str)
|
|
|
|
if PY2:
|
|
exec("""def with_metaclass(meta):
|
|
class _WithMetaclassBase(object):
|
|
__metaclass__ = meta
|
|
return _WithMetaclassBase
|
|
""")
|
|
else:
|
|
exec("""def with_metaclass(meta):
|
|
class _WithMetaclassBase(object, metaclass=meta):
|
|
pass
|
|
return _WithMetaclassBase
|
|
""")
|