This commit is contained in:
Ronald Steinke 2018-01-09 17:17:44 +01:00
parent 8aaa943930
commit 30c6fa7b06
95 changed files with 0 additions and 10251 deletions

View File

@ -1,456 +0,0 @@
import sys
from logging import DEBUG
from threading import Thread
from traceback import print_stack
from futile.logging import LoggerMixin
from openmtc.exc import OpenMTCError
if sys.subversion[0] != "CPython":
from inspect import ismethod, getargspec
# TODO: kca: can't pass in values for then/error currently
def log_error(error):
if isinstance(error, OpenMTCError):
return False
return True
class Promise(LoggerMixin):
"""
This is a class that attempts to comply with the
Promises/A+ specification and test suite:
http://promises-aplus.github.io/promises-spec/
"""
__slots__ = ("_state", "value", "reason",
"_callbacks", "_errbacks", "name")
# These are the potential states of a promise
PENDING = -1
REJECTED = 0
FULFILLED = 1
def __init__(self, name=None):
"""
Initialize the Promise into a pending state.
"""
self._state = self.PENDING
self.value = None
self.reason = None
self._callbacks = []
self._errbacks = []
self.name = name
def _fulfill(self, value):
"""
Fulfill the promise with a given value.
"""
assert self._state == self.PENDING, "Promise state is not pending"
self._state = self.FULFILLED
self.value = value
for callback in self._callbacks:
try:
callback(value)
except Exception:
# Ignore errors in callbacks
self.logger.exception("Error in callback %s", callback)
# We will never call these callbacks again, so allow
# them to be garbage collected. This is important since
# they probably include closures which are binding variables
# that might otherwise be garbage collected.
self._callbacks = []
self._errbacks = []
def fulfill(self, value):
self._fulfill(value)
return self
def _reject(self, reason, bubbling=False):
"""
Reject this promise for a given reason.
"""
assert self._state == self.PENDING, "Promise state is not pending"
if not bubbling and log_error(reason):
exc_info = sys.exc_info()
self.logger.debug("Promise (%s) rejected: %s", self.name, reason,
exc_info=exc_info[0] and exc_info or None)
self.logger.debug(self._errbacks)
if self.logger.isEnabledFor(DEBUG):
print_stack()
else:
pass
self._state = self.REJECTED
self.reason = reason
for errback in self._errbacks:
try:
errback(reason)
except Exception:
self.logger.exception("Error in errback %s", errback)
# Ignore errors in callbacks
# We will never call these errbacks again, so allow
# them to be garbage collected. This is important since
# they probably include closures which are binding variables
# that might otherwise be garbage collected.
self._errbacks = []
self._callbacks = []
def reject(self, reason):
self._reject(reason)
return self
def isPending(self):
"""Indicate whether the Promise is still pending."""
return self._state == self.PENDING
def isFulfilled(self):
"""Indicate whether the Promise has been fulfilled."""
return self._state == self.FULFILLED
def isRejected(self):
"""Indicate whether the Promise has been rejected."""
return self._state == self.REJECTED
def get(self, timeout=None):
"""Get the value of the promise, waiting if necessary."""
self.wait(timeout)
if self._state == self.FULFILLED:
return self.value
raise self.reason
def wait(self, timeout=None):
"""
An implementation of the wait method which doesn't involve
polling but instead utilizes a "real" synchronization
scheme.
"""
import threading
if self._state != self.PENDING:
return
e = threading.Event()
self.addCallback(lambda v: e.set())
self.addErrback(lambda r: e.set())
e.wait(timeout)
def addCallback(self, f):
"""
Add a callback for when this promise is fulfilled. Note that
if you intend to use the value of the promise somehow in
the callback, it is more convenient to use the 'then' method.
"""
self._callbacks.append(f)
def addErrback(self, f):
"""
Add a callback for when this promise is rejected. Note that
if you intend to use the rejection reason of the promise
somehow in the callback, it is more convenient to use
the 'then' method.
"""
self._errbacks.append(f)
if sys.subversion[0] != "CPython":
def _invoke(self, func, value):
try:
if value is None:
args, _, _, _ = getargspec(func)
arglen = len(args)
if not arglen or (arglen == 1 and ismethod(func)):
return func()
return func(value)
except Exception as e:
if log_error(e):
self.logger.exception("Error in handler %s", func)
else:
self.logger.debug("Error in handler %s: %s", func, e)
raise
else:
def _invoke(self, func, value):
try:
if value is None:
try:
target = func.im_func
except AttributeError:
argcount = func.func_code.co_argcount
else:
argcount = target.func_code.co_argcount - 1
if argcount == 0:
return func()
return func(value)
except Exception as e:
if log_error(e):
self.logger.exception("Error in handler %s", func)
else:
self.logger.debug("Error in handler %s: %s", func, repr(e))
raise
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if self.isPending():
if exc_value is not None:
if log_error(exc_value):
self.logger.exception("Promise automatically rejected")
self._reject(exc_value, bubbling=True)
return True
else:
self.fulfill(None)
def then(self, success=None, failure=None, name=None):
"""
This method takes two optional arguments. The first argument
is used if the "self promise" is fulfilled and the other is
used if the "self promise" is rejected. In either case, this
method returns another promise that effectively represents
the result of either the first of the second argument (in the
case that the "self promise" is fulfilled or rejected,
respectively).
Each argument can be either:
* None - Meaning no action is taken
* A function - which will be called with either the value
of the "self promise" or the reason for rejection of
the "self promise". The function may return:
* A value - which will be used to fulfill the promise
returned by this method.
* A promise - which, when fulfilled or rejected, will
cascade its value or reason to the promise returned
by this method.
* A value - which will be assigned as either the value
or the reason for the promise returned by this method
when the "self promise" is either fulfilled or rejected,
respectively.
"""
if name is None:
try:
name = success.__name__
except AttributeError:
name = str(success)
ret = Promise(name=name)
state = self._state
if state == self.PENDING:
"""
If this is still pending, then add callbacks to the
existing promise that call either the success or
rejected functions supplied and then fulfill the
promise being returned by this method
"""
def callAndFulfill(v):
"""
A callback to be invoked if the "self promise"
is fulfilled.
"""
try:
# From 3.2.1, don't call non-functions values
if callable(success):
newvalue = self._invoke(success, v)
if _isPromise(newvalue):
newvalue.then(ret._fulfill,
ret._reject)
else:
ret._fulfill(newvalue)
else:
# From 3.2.6.4
ret._fulfill(v)
except Exception as e:
ret._reject(e)
def callAndReject(r):
"""
A callback to be invoked if the "self promise"
is rejected.
"""
try:
if callable(failure):
newvalue = failure(r)
if _isPromise(newvalue):
newvalue.then(ret._fulfill,
ret._reject)
else:
ret._fulfill(newvalue)
else:
# From 3.2.6.5
ret._reject(r)
except Exception as e:
ret._reject(e)
self._callbacks.append(callAndFulfill)
self._errbacks.append(callAndReject)
elif state == self.FULFILLED:
# If this promise was already fulfilled, then
# we need to use the first argument to this method
# to determine the value to use in fulfilling the
# promise that we return from this method.
try:
if callable(success):
newvalue = self._invoke(success, self.value)
if _isPromise(newvalue):
newvalue.then(ret._fulfill,
lambda r: ret._reject(r, bubbling=True))
else:
ret._fulfill(newvalue)
else:
# From 3.2.6.4
ret._fulfill(self.value)
except Exception as e:
ret._reject(e)
else:
# If this promise was already rejected, then
# we need to use the second argument to this method
# to determine the value to use in fulfilling the
# promise that we return from this method.
try:
if callable(failure):
newvalue = self._invoke(failure, self.reason)
if _isPromise(newvalue):
newvalue.then(ret._fulfill,
ret._reject)
else:
ret._fulfill(newvalue)
else:
# From 3.2.6.5
ret._reject(self.reason, bubbling=True)
except Exception as e:
ret._reject(e)
return ret
def _isPromise(obj):
"""
A utility function to determine if the specified
object is a promise using "duck typing".
"""
if isinstance(obj, Promise):
return True
try:
return callable(obj.fulfill) and callable(obj.reject) and\
callable(obj.then)
except AttributeError:
return False
def listPromise(*args):
"""
A special function that takes a bunch of promises
and turns them into a promise for a vector of values.
In other words, this turns an list of promises for values
into a promise for a list of values.
"""
ret = Promise()
def handleSuccess(v, ret):
for arg in args:
if not arg.isFulfilled():
return
value = map(lambda p: p.value, args)
ret._fulfill(value)
for arg in args:
arg.addCallback(lambda v: handleSuccess(v, ret))
arg.addErrback(lambda r: ret.reject(r))
# Check to see if all the promises are already fulfilled
handleSuccess(None, ret)
return ret
def dictPromise(m):
"""
A special function that takes a dictionary of promises
and turns them into a promise for a dictionary of values.
In other words, this turns an dictionary of promises for values
into a promise for a dictionary of values.
"""
ret = Promise()
def handleSuccess(v, ret):
for p in m.values():
if not p.isFulfilled():
return
value = {}
for k in m:
value[k] = m[k].value
ret.fulfill(value)
for p in m.values():
p.addCallback(lambda v: handleSuccess(v, ret))
p.addErrback(lambda r: ret.reject(r))
# Check to see if all the promises are already fulfilled
handleSuccess(None, ret)
return ret
class BackgroundThread(Thread):
def __init__(self, promise, func):
self.promise = promise
self.func = func
Thread.__init__(self)
def run(self):
try:
val = self.func()
self.promise.fulfill(val)
except Exception as e:
self.promise.reject(e)
def background(f):
p = Promise()
t = BackgroundThread(p, f)
t.start()
return p
def spawn(f):
from gevent import spawn
p = Promise()
def process():
try:
val = f()
p.fulfill(val)
except Exception as e:
p.reject(e)
spawn(process)
return p
def FulfilledPromise(result):
p = Promise()
p.fulfill(result)
return p
def RejectedPromise(error):
p = Promise()
p.reject(error)
return p

View File

@ -1,4 +0,0 @@
try:
from StringIO import StringIO
except ImportError:
from io import StringIO

View File

@ -1,82 +0,0 @@
from futile.basictypes import basestring, BASE_STR
from futile.logging import LoggerMixin
Base = LoggerMixin
class NOT_SET(object):
__slots__ = ()
def __bool__(self):
return False
__nonzero__ = __bool__
def __str__(self):
return ""
NOT_SET = NOT_SET()
DEFAULT_ENCODING = "utf-8"
DEFAULT_CHUNK_SIZE = 128 * 1024
THREADSAFE = True
def noop(*args, **kw):
pass
def not_implemented(*args, **kw):
raise NotImplementedError()
def tostr(o):
if isinstance(o, basestring):
return o
return BASE_STR(o)
if basestring == str:
uc = tostr
encstr = not_implemented
else:
def uc(s):
if isinstance(s, unicode):
return s
if isinstance(s, basestring):
return s.decode(DEFAULT_ENCODING)
return unicode(s)
def encstr(s):
if isinstance(s, str):
return s
if not isinstance(s, unicode):
s = unicode(s)
return s.encode(DEFAULT_ENCODING)
def identity(x):
return x
_isc = issubclass
def issubclass(o, classes):
"A safer version of __builtin__.issubclass that does not raise TypeError when called with a non-type object"
return isinstance(o, type) and _isc(o, classes)
try:
callable
except NameError:
def callable(x):
return hasattr(x, "__call__")
class ObjectProxy(object):
__slots__ = ("_o")
def __init__(self, proxyobject, *args, **kw):
super(ObjectProxy, self).__init__(*args, **kw)
self._o = proxyobject
def __getattr__(self, k):
return getattr(self._o, k)

View File

@ -1,12 +0,0 @@
'''
Created on 13.11.2012
@author: kca
'''
try:
from abc import ABCMeta, abstractmethod, abstractproperty
except ImportError:
from futile import identity
ABCMeta = type
abstractmethod = abstractproperty = identity

View File

@ -1,20 +0,0 @@
'''
Created on 11.05.2013
@author: kca
'''
try:
from types import ClassType
except ImportError:
ClassType = type
try:
basestring = basestring
except NameError:
basestring = str
try:
BASE_STR = unicode
except NameError:
BASE_STR = str

View File

@ -1,63 +0,0 @@
'''
Created on 17.07.2011
@author: kca
'''
from ..collections import OrderedDict
import futile
class LRUCache(OrderedDict):
max_items = 100
def __init__(self, max_items = None, threadsafe = None, *args, **kw):
super(LRUCache, self).__init__(*args, **kw)
if max_items is not None:
if max_items <= 0:
raise ValueError(max_items)
self.max_items = max_items
if threadsafe is None:
threadsafe = futile.THREADSAFE
if threadsafe:
from threading import RLock
self.__lock = RLock()
else:
self.__lock = None
self.__getitem__ = self._getitem
self.__setitem__ = self._setitem
def __getitem__(self, k):
if self.__lock is None:
return self._getitem(k)
with self.__lock:
return self._getitem(k)
def get(self, k, default = None):
try:
return self[k]
except KeyError:
return default
def _getitem(self, k):
v = super(LRUCache, self).__getitem__(k)
del self[k]
super(LRUCache, self).__setitem__(k, v)
return v
def __iter__(self):
for k in tuple(super(LRUCache, self).__iter__()):
yield k
def __setitem__(self, k, v):
if self.__lock is None:
return self._setitem(k, v)
with self.__lock:
self._setitem(k, v)
def _setitem(self, k, v):
super(LRUCache, self).__setitem__(k, v)
if len(self) > self.max_items:
self.popitem(False)

View File

@ -1,110 +0,0 @@
# Copyright (C) 2009 Raymond Hettinger
# *** MIT License ***
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
## {{{ http://code.activestate.com/recipes/576694/ (r7)
# kca: fixed exception at interpreter shutdown
# kca: added list methods
import collections
KEY, PREV, NEXT = range(3)
class OrderedSet(collections.MutableSet):
def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next]
if iterable is not None:
self |= iterable
def __len__(self):
return len(self.map)
def __contains__(self, key):
return key in self.map
def add(self, key):
if key not in self.map:
end = self.end
curr = end[PREV]
curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end]
append = add
def discard(self, key):
_KEY, PREV, NEXT = 0, 1, 2
if key in self.map:
key, prev, next = self.map.pop(key)
prev[NEXT] = next
next[PREV] = prev
def __iter__(self):
end = self.end
curr = end[NEXT]
while curr is not end:
yield curr[KEY]
curr = curr[NEXT]
def __reversed__(self):
KEY, PREV, NEXT = 0, 1, 2
end = self.end
curr = end[PREV]
while curr is not end:
yield curr[KEY]
curr = curr[PREV]
def pop(self, last=True):
# changed default to last=False - by default, treat as queue.
if not self:
raise KeyError('set is empty')
key = next(reversed(self)) if last else next(iter(self))
self.discard(key)
return key
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self))
def __eq__(self, other):
if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other)
def __del__(self):
self.clear() # remove circular references
def __getitem__(self, index):
return list(self)[index]
if __name__ == '__main__':
print(OrderedSet('abracadaba'))
print(OrderedSet('simsalabim'))
## end of http://code.activestate.com/recipes/576694/ }}}
## kca:
print OrderedSet('simsalabim')[1]
# Test case for exception at shutdown (yes, really...)
x = OrderedSet('simsalabim')

View File

@ -1,44 +0,0 @@
'''
Created on 17.07.2011
@author: kca
'''
import futile
from futile.basictypes import basestring
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
from abc import ABCMeta
from collections import Iterable, Sequence
def is_iterable(o):
return isinstance(o, Iterable) and not isinstance(o, basestring)
def get_iterable(o):
if o is None:
return ()
return ((not isinstance(o, Iterable) or isinstance(o, basestring))
and (o,) or o)
def get_list(o):
if o is None:
return []
return ((not isinstance(o, Iterable) or isinstance(o, basestring))
and [o] or list(o))
def yield_buffer(buffer, chunk_size=None):
chunk_size = chunk_size or futile.DEFAULT_CHUNK_SIZE
while True:
chunk = buffer.read(chunk_size)
if not chunk:
return
yield chunk

View File

@ -1,127 +0,0 @@
# Copyright (c) 2009 Raymond Hettinger
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
from UserDict import DictMixin
class OrderedDict(dict, DictMixin):
def __init__(self, *args, **kwds):
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__end
except AttributeError:
self.clear()
self.update(*args, **kwds)
def clear(self):
self.__end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.__map = {} # key --> [key, prev, next]
dict.clear(self)
def __setitem__(self, key, value):
if key not in self:
end = self.__end
curr = end[1]
curr[2] = end[1] = self.__map[key] = [key, curr, end]
dict.__setitem__(self, key, value)
def __delitem__(self, key):
dict.__delitem__(self, key)
key, prev, next = self.__map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.__end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.__end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def popitem(self, last=True):
if not self:
raise KeyError('dictionary is empty')
if last:
key = reversed(self).next()
else:
key = iter(self).next()
value = self.pop(key)
return key, value
def __reduce__(self):
items = [[k, self[k]] for k in self]
tmp = self.__map, self.__end
del self.__map, self.__end
inst_dict = vars(self).copy()
self.__map, self.__end = tmp
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
def keys(self):
return list(self)
setdefault = DictMixin.setdefault
update = DictMixin.update
pop = DictMixin.pop
values = DictMixin.values
items = DictMixin.items
iterkeys = DictMixin.iterkeys
itervalues = DictMixin.itervalues
iteritems = DictMixin.iteritems
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, self.items())
def copy(self):
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
def __eq__(self, other):
if isinstance(other, OrderedDict):
if len(self) != len(other):
return False
for p, q in zip(self.items(), other.items()):
if p != q:
return False
return True
return dict.__eq__(self, other)
def __ne__(self, other):
return not self == other

View File

@ -1,38 +0,0 @@
try:
from blist import sortedlist
except ImportError:
from futile.logging import get_logger
from heapq import heappush, heappop, heapify
get_logger(__name__).warning("blist.sortedlist is not available. Using a fallback implementation")
class sortedlist(object):
def __init__(self, iterable=(), *args, **kw):
super(sortedlist, self).__init__(*args, **kw)
l = self._list = list(iterable)
if iterable is not None:
heapify(l)
def add(self, v):
heappush(self._list, v)
def pop(self, index=-1):
if index != 0:
raise NotImplementedError()
return heappop(self._list)
def remove(self, object):
self._list.remove(object)
heapify(self._list)
def __getitem__(self, index):
if index != 0:
raise NotImplementedError()
return self._list[index]
def __len__(self):
return len(self._list)

View File

@ -1,21 +0,0 @@
'''
Created on 14.07.2011
@author: kca
'''
from futile import ObjectProxy
class closing(ObjectProxy):
def __enter__(self):
return self._o
def __exit__(self, exc_type, exc_val, exc_tb):
self._o.close()
class exiting(ObjectProxy):
def __enter__(self):
return self._o
def __exit__(self, exc_type, exc_val, exc_tb):
self._o.__exit__(exc_type, exc_val, exc_tb)

View File

@ -1,44 +0,0 @@
'''
Created on 25.07.2011
@author: kca
'''
import sys
from .logging import get_logger
try:
from lxml import etree as impl
from lxml.etree import tostring as _ts
get_logger(__name__).debug("Using lxml etree implementation1.")
def tostring(element, encoding="utf-8", pretty_print=False):
return _ts(element, encoding=encoding, pretty_print=pretty_print)
except ImportError:
logger = get_logger(__name__)
logger.warning(
"lxml library not found, trying builtin ElementTree implementations. Pretty printing will be disabled.")
try:
from xml.etree import cElementTree as impl
try:
impl.ParseError = impl.XMLParserError
except AttributeError:
pass
logger.debug("Using native xml.etree.cElementTree")
except ImportError:
from xml.etree import ElementTree as impl
logger.debug("Using python xml.etree.ElementTree")
_ts = impl.tostring
def tostring(element, encoding="utf-8", pretty_print=False):
return _ts(element, encoding=encoding)
impl.tostring = tostring
impl.XMLSyntaxError = impl.ParseError
sys.modules[__name__ + ".impl"] = sys.modules[__name__ + ".ElementTree"] = ElementTree = impl

View File

@ -1,22 +0,0 @@
'''
Created on 14.07.2011
@author: kca
'''
from . import issubclass
def errorstr(e):
try:
message = e.message
except AttributeError:
message = str(e)
else:
if not message:
message = str(e)
return message
def raise_error(e):
if isinstance(e, Exception) or (isinstance(e, type) and issubclass(e, Exception)):
raise e
raise Exception(e)

View File

@ -1,230 +0,0 @@
"""
Created on 15.07.2011
@author: kca
"""
import logging
import logging.handlers
from futile.basictypes import ClassType, basestring
from futile.threading import current_thread
from logging import Filter
from futile.collections import get_iterable
# statics
_handlers = []
_formatter = logging.Formatter('%(asctime)s %(levelname)s - %(name)s: %(message)s')
_level = logging.NOTSET
# log level constants for convenience
from logging import CRITICAL, FATAL, ERROR, WARNING, INFO, DEBUG, NOTSET
CRITICAL = CRITICAL
FATAL = FATAL
ERROR = ERROR
WARNING = WARNING
INFO = INFO
DEBUG = DEBUG
NOTSET = NOTSET
def get_default_level():
return _level
def set_default_level(l):
global _level
_level = l
logging.basicConfig(level=l)
# try:
# from colorlog import ColoredFormatter
# formatter = ColoredFormatter(
# "%(blue)s%(asctime)s %(log_color)s%(levelname) - 8s%(reset)s%(name)s: %(message)s",
# datefmt=None,
# reset=True,
# log_colors={
# 'DEBUG': 'cyan',
# 'INFO': 'green',
# 'WARNING': 'yellow',
# 'ERROR': 'red',
# 'CRITICAL': 'red',
# }
# )
# import logging
# hand = logging.StreamHandler()
# hand.setFormatter(formatter)
# futile.logging.add_handler( hand)
# except ImportError:
# pass
def get_default_formatter():
return _formatter
def set_default_formatter(frmt):
global _formatter
if frmt and isinstance(frmt, logging.Formatter):
_formatter = frmt
else:
raise TypeError("Not a logging Formatter: %s" % (frmt, ))
def add_handler(h):
if not isinstance(h, logging.Handler):
raise TypeError(h)
_handlers.append(h)
def add_log_file(path, level=None, formatter=None):
""" Adds a log file to all future loggers.
Files will be rotated depending on max_bytes and backups parameters.
@param path: path to logfile
@param level: minimum log level
@param formatter: a logging.Formatter for this log file
"""
handler = logging.handlers.WatchedFileHandler(path)
handler.setFormatter(formatter or _formatter)
# TODO(rst): probably try/except is necessary
handler.setLevel(level or _level)
add_handler(handler)
def get_logger(logger_name=None, level=None):
level = level if level is not None else _level
# logging.basicConfig(level=level)
if logger_name:
if not isinstance(logger_name, basestring):
if not isinstance(logger_name, (type, ClassType)):
l_class = logger_name.__class__
else:
l_class = logger_name
logger_name = l_class.__module__ + "." + l_class.__name__
else:
logger_name = __name__
try:
logger = logging.getLogger(logger_name)
except Exception as e:
print ("Failed to get logger '%s': %s" % (logger_name, e))
raise
try:
logger.setLevel(level) # raises TypeError: not a valid string or int
except TypeError:
logger.setLevel(NOTSET) # TODO(rst): set another level if wrong level?
for h in _handlers:
logger.addHandler(h)
return logger
class LoggerMixin(object):
log_file = None
log_level = None
def __init__(self):
self.__logger = None
@classmethod
def _get_logger(cls, logger_name=None):
logger = get_logger(logger_name, cls.log_level)
if cls.log_file:
formatter = get_default_formatter()
handler = logging.handlers.WatchedFileHandler(cls.log_file)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def get_logger(self):
try:
if self.__logger is not None:
return self.__logger
except AttributeError:
pass
self.__logger = l = self.get_class_logger()
return l
def set_logger(self, logger):
self.__logger = logger
logger = property(get_logger, set_logger)
@classmethod
def get_class_logger(cls):
try:
return cls.__dict__["__logger__"]
except KeyError:
l = cls.__logger__ = cls._get_logger(cls.__name__)
return l
def __getstate__(self):
l = getattr(self, "_LoggerMixin__logger", None)
self.__logger = None
try:
sgs = super(LoggerMixin, self).__getstate__
except AttributeError:
state = self.__dict__.copy()
else:
state = sgs()
self.__logger = l
return state
class ThreadFilter(Filter):
def __init__(self, thread=None, name=''):
Filter.__init__(self, name=name)
self.thread = thread or current_thread()
def filter(self, record):
return current_thread() == self.thread
class ErrorLogger(LoggerMixin):
def __init__(self, name="operation", logger=None,
level=get_default_level(), *args, **kw):
super(ErrorLogger, self).__init__(*args, **kw)
if logger is not None:
self.logger = logger
self.name = name
self.log_level = level
assert level is not None
def __enter__(self):
self.logger.debug("Entering %s", self.name)
return self
def __exit__(self, type, value, traceback):
if type is not None:
self.logger.exception("Error in %s", self.name)
else:
self.logger.log(self.log_level, "%s finished", self.name)
def log_errors(f):
def _f(*args, **kw):
with ErrorLogger(f.__name__):
result = f(*args, **kw)
get_logger(f).debug("%s returning: %s", f.__name__, result)
return result
_f.__name__ = f.__name__
return _f
def sanitize_dict(d, keys=("password",), replacement="*", inplace=False):
keys = get_iterable(keys)
if not inplace:
d = dict(d)
if replacement is None:
for k in keys:
d.pop(k, None)
else:
for k in keys:
v = d[k]
if isinstance(v, basestring):
d[k] = replacement * len(v)
else:
d[k] = replacement
return d

View File

@ -1,14 +0,0 @@
'''
Created on 30.08.2011
@author: kca
'''
from logging.handlers import BufferingHandler as _BufferingHandler
class BufferingHandler(_BufferingHandler):
def __init__(self, capacity = None):
_BufferingHandler.__init__(self, capacity = capacity)
def shouldFlush(self, record):
return self.capacity and super(BufferingHandler, self).shouldFlush(record) or False

View File

@ -1,9 +0,0 @@
'''
Created on 30.08.2011
@author: kca
'''
from collections import namedtuple
Logbook = namedtuple("Logbook", ("name", "component", "entries"))

View File

@ -1,60 +0,0 @@
'''
Created on 29.08.2011
@author: kca
'''
import logging
from . import ThreadFilter
from ..collections import get_list
from futile import NOT_SET
from logging import LogRecord, DEBUG
from futile.logging import ErrorLogger
class LogTap(ErrorLogger):
def __init__(self, handler, logger = None, name = None, level = DEBUG, *args, **kw):
super(LogTap, self).__init__(name = name, logger = logger, level = level, *args, **kw)
handler = get_list(handler)
self.handlers = handler
self.target_logger = logger or logging.root
def attach(self):
map(self.target_logger.addHandler, self.handlers)
def detach(self):
for handler in self.handlers:
self.target_logger.removeHandler(handler)
handler.close()
def emit(self, record):
for handler in self.handlers:
handler.emit(record)
def __enter__(self):
self.attach()
return super(LogTap, self).__enter__()
def __exit__(self, type, value, traceback):
super(LogTap, self).__exit__(type, value, traceback)
self.detach()
class BufferingLogTap(LogTap):
log = None
def __init__(self, handler = None, name = None, logger = None, level = DEBUG, capacity = None, memhandler = None, *args, **kw):
if not memhandler:
from handlers import BufferingHandler
memhandler = BufferingHandler(capacity)
memhandler.addFilter(ThreadFilter())
self.memhandler = memhandler
handler = [ memhandler ] + get_list(handler)
super(BufferingLogTap, self).__init__(handler = handler, logger = logger, name = name, level = level, *args, **kw)
def detach(self):
self.log = map(lambda r: isinstance(r, LogRecord) and self.memhandler.format(r) or r, self.memhandler.buffer)
super(BufferingLogTap, self).detach()
def emit(self, record, level = NOT_SET):
if isinstance(record, LogRecord):
return super(BufferingLogTap, self).emit(record)
self.memhandler.buffer.append(record)

View File

@ -1,82 +0,0 @@
'''
Created on 30.04.2011
@author: kca
'''
import os
from fcntl import lockf, LOCK_EX, LOCK_SH, LOCK_UN
from contextlib import contextmanager
from futile.signal import timeout
class RWLock(object):
def __init__(self, path = None, threadsafe = True, *args, **kw):
if not path:
raise NotImplementedError()
if not os.path.exists(path):
open(path, "a").close()
self.__path = path
if threadsafe:
import threading
self.__local = threading.local()
else:
class Local(object):
pass
self.__local = Local
self.__local.f = None
@contextmanager
def read_transaction(self, timeout = None):
self.read_acquire(timeout = timeout)
try:
yield
finally:
self.read_release()
pass
pass
@contextmanager
def write_transaction(self, timeout = None):
self.write_acquire(timeout = timeout)
try:
yield
finally:
self.write_release()
def __acquire(self, fmode, lmode, to):
assert getattr(self.__local, "f", None) is None
f = open(self.__path, fmode)
try:
if timeout:
with timeout(to):
lockf(f, lmode)
else:
lockf(f, lmode)
except:
f.close()
raise
self.__local.f = f
return f
def read_acquire(self, timeout = None):
return self.__acquire("r", LOCK_SH, timeout)
def read_release(self):
with self.__local.f as f:
self.__local.f = None
lockf(f, LOCK_UN)
write_release = read_release
def write_acquire(self, timeout = None):
return self.__acquire("a", LOCK_EX, timeout)
__enter__ = write_acquire
def __exit__(self, *args):
self.write_release()

View File

@ -1,3 +0,0 @@
from RWLock import RWLock
Lock = RWLock

View File

@ -1,83 +0,0 @@
'''
Created on 15.07.2011
@author: kca
'''
from asyncore import dispatcher, loop
from socket import AF_INET, SOCK_STREAM, error
from sockethelper import socket
from futile.exc import errorstr
from collections import namedtuple
import sys
from time import time
class TestResult(namedtuple("TestResultTuple", ("result", "message"))):
def __new__(cls, result, message = ""):
return super(TestResult, cls).__new__(cls, result, message)
def __bool__(self):
return self.result
__nonzero__ = __bool__
def __str__(self):
if self.message:
return "%s - %s" % (self.result, self.message)
return str(self.result)
def __eq__(self, o):
try:
return self.result == o.result
except AttributeError:
return False
def __ne__(self, o):
return not (self == o)
def test_port(host, port, family = AF_INET, type = SOCK_STREAM):
try:
with socket(family, type) as s:
s.connect((host, port))
except error, e:
return TestResult(False, "%s (%d)" % (e.strerror, e.errno))
except Exception, e:
return TestResult(False, errorstr(e))
return TestResult(True)
class PortTester(dispatcher):
result = TestResult(False, "Test did not run")
def __init__(self, host, port, family = AF_INET, type = SOCK_STREAM, map = None):
dispatcher.__init__(self, map = map)
self.create_socket(family, type)
self.connect((host, port))
self.host = host
self.port = port
def handle_connect(self):
self.result = TestResult(True)
self.close()
def handle_error(self):
self.result = TestResult(False, errorstr(sys.exc_value))
self.close()
def run_test(map, timeout = 0.0):
if timeout and timeout > 0.0:
timeout = float(timeout)
start = time()
while True:
loop(map = map, timeout = timeout, count = 1)
if map:
now = time()
timeout -= now - start
if timeout <= 0.0:
for r in map.itervalues():
r.result = TestResult(False, "Timeout")
break
start = now
else:
break
else:
loop(map = map)

View File

@ -1,4 +0,0 @@
class NetworkError(Exception):
pass

View File

@ -1,96 +0,0 @@
'''
Created on 17.07.2011
@author: kca
'''
try:
from httplib import HTTPConnection as _HTTPConnection, HTTPSConnection as _HTTPSConnection
except ImportError:
from http.client import HTTPConnection as _HTTPConnection, HTTPSConnection as _HTTPSConnection
from futile.contextlib import closing
from futile import NOT_SET
import socket
from . import exc as _exc
import sys
import types
try:
from urllib import quote, quote_plus, unquote, unquote_plus
except ImportError:
from urllib.parse import quote, quote_plus, unquote, unquote_plus
class HTTPResponseWrapper(object):
def __init__(self, connection, response, *args, **kw):
super(HTTPResponseWrapper, self).__init__(*args, **kw)
self.__response = response
self.__connection = connection
#def __del__(self):
# self.close()
def __getattr__(self, k):
return getattr(self.__response, k)
def __enter__(self):
return self.__response
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
try:
self.__response.close()
except:
pass
finally:
self.__connection.close()
class HTTPConnection(_HTTPConnection):
response_wrapper = closing
def __init__(self, host, port=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None, response_wrapper = NOT_SET):
_HTTPConnection.__init__(self, host, port, strict, timeout, source_address)
if response_wrapper is not NOT_SET:
self.response_wrapper = response_wrapper
def getresponse(self, buffering = False):
r = _HTTPConnection.getresponse(self, buffering)
if self.response_wrapper:
r = self.response_wrapper(r)
return r
class HTTPSConnection(_HTTPSConnection):
response_wrapper = closing
def __init__(self, host, port=None, key_file = None, cert_file = None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None, response_wrapper = NOT_SET):
_HTTPSConnection.__init__(self, host, port, key_file = key_file, cert_file = cert_file, strict = strict, timeout = timeout, source_address = source_address)
if response_wrapper is not NOT_SET:
self.response_wrapper = response_wrapper
def getresponse(self, buffering = False):
r = _HTTPSConnection.getresponse(self, buffering)
if self.response_wrapper:
r = self.response_wrapper(r)
return r
class exc(types.ModuleType):
def __getattr__(self, k):
try:
v = getattr(_exc, k)
except AttributeError:
if not k.startswith("HTTPError"):
raise
v = _exc.get_error_class(k[9:])
setattr(self, k, v)
return v
name = __name__ + ".exc"
exc = exc(name)
sys.modules[name] = exc
del name

View File

@ -1,55 +0,0 @@
'''
Created on 19.03.2013
@author: kca
'''
from logging import DEBUG, WARNING
import futile.logging
import urllib3.connectionpool
from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool
from futile.logging import LoggerMixin
from futile import ObjectProxy
if not futile.logging.get_logger().isEnabledFor(DEBUG):
urllib3.connectionpool.log.setLevel(WARNING)
class Urllib3ResponseWrapper(ObjectProxy):
def getheader(self, header, default=None):
return self._o.getheader(header.lower(), default)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
self._o.release_conn()
def isclosed(self):
return False
class ConnectionPoolManager(LoggerMixin):
def __init__(self, host, port, certfile = None, keyfile = None, cacertfile=None, force_ssl = False, *args, **kw):
super(ConnectionPoolManager, self).__init__(*args, **kw)
self.logger.debug("Creating ConnectionPoolManager for %s:%s", host, port)
if certfile or keyfile or force_ssl:
#https://docs.python.org/2/library/ssl.html#ssl.SSLContext
from ssl import SSLContext, PROTOCOL_SSLv23
ssl_context=SSLContext(PROTOCOL_SSLv23)
ssl_context.load_cert_chain(certfile = certfile, keyfile = keyfile)
ssl_context.load_verify_locations(cafile=cacertfile)
#https://docs.python.org/2/library/httplib.html
self.__pool = HTTPSConnectionPool(host, port, maxsize = 16, context = ssl_context)
else:
self.__pool = HTTPConnectionPool(host, port, maxsize = 16)
def request(self, method, path, body, headers, timeout):
return Urllib3ResponseWrapper(self.__pool.urlopen(method, path, body,
headers, timeout = timeout, pool_timeout = 30, preload_content = False, assert_same_host = False))

View File

@ -1,353 +0,0 @@
'''
Created on 21.05.2011
@author: kca
'''
from base64 import b64encode
from cStringIO import StringIO
from datetime import datetime
from logging import DEBUG
from socket import getservbyname
from time import time
from urllib import quote_plus
from urllib2 import quote
from urlparse import urlparse
from futile import ObjectProxy
from futile.logging import LoggerMixin
from futile.net.http.exc import NetworkError, HTTPError
def compose_qs(values):
return "&".join([ "%s=%s" % (quote(k), quote(v)) for k, v in dict(values).iteritems() ])
class LoggingResponseWrapper(LoggerMixin, ObjectProxy):
def __init__(self, response, *args, **kw):
super(LoggingResponseWrapper, self).__init__(proxyobject = response, *args, **kw)
self.__buffer = StringIO()
self.__finalized = False
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def __enter__(self):
return self
def read(self, n = None):
s = self._o.read(n)
self.__buffer.write(s)
return s
def readline(self):
s = self._o.readline()
self.__buffer.write(s)
return s
def readlines(self, sizehint = None):
lines = self._o.readlines(sizehint)
self.__buffer.write(''.join(lines))
return lines
def close(self):
if self.__finalized:
self.logger.debug("%s is already finalized" % (self, ))
return
self.__finalized = True
try:
if not self._o.isclosed():
self.__buffer.write(self._o.read())
self.logger.debug("Read data:\n %s", self.__buffer.getvalue())
except:
self.logger.exception("Finalizing response failed")
finally:
self._o.close()
self.__buffer.close()
class CachingHttplibResponseWrapper(ObjectProxy, LoggerMixin):
def __init__(self, response, path, tag, last_modified, cache, *args, **kw):
super(CachingHttplibResponseWrapper, self).__init__(proxyobject = response, *args, **kw)
self.__cache = cache
self.__buffer = StringIO()
self.__path = path
self.__tag = tag
self.__last_modified = last_modified
self.__finalized = False
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def __enter__(self):
return self
def read(self, n = None):
s = self._o.read(n)
self.__buffer.write(s)
return s
def readline(self):
s = self._o.readline()
self.__buffer.write(s)
return s
def readlines(self, sizehint = None):
lines = self._o.readlines(sizehint)
self.__buffer.write(''.join(lines))
return lines
def close(self):
if self.__finalized:
self.logger.debug("%s is already finalized" % (self, ))
return
self.__finalized = True
try:
if not self._o.isclosed():
self.__buffer.write(self._o.read())
val = self.__buffer.getvalue()
self.logger.debug("Putting to cache: %s -> %s, %s\n %s", self.__path, self.__tag, self.__last_modified, val)
self.__cache[self.__path] = (self.__tag, self.__last_modified, val)
except:
self.logger.exception("Finalizing response failed")
finally:
self._o.close()
self.__buffer.close()
def __getattr__(self, name):
return getattr(self._o, name)
class closing(ObjectProxy):
def __getattr__(self, k):
return getattr(self._o, k)
def __enter__(self):
return self._o
def __exit__(self, exc_type, exc_val, exc_tb):
self._o.close()
def close(self):
self._o.close()
class RestClient(LoggerMixin):
ERROR_RESPONSE_MAX = 320
get_timeout = timeout = 120.0
def __init__(self, uri, username=None, password=None, certfile=None,
keyfile=None, cacertfile=None, content_type="text/plain",
headers=None,
cache=True, timeout=None, get_timeout=None,
component_name = "server", connection_manager = None,
*args, **kw):
super(RestClient, self).__init__(*args, **kw)
self.logger.debug("Creating RestClient for %s", uri)
self.timeout = timeout or self.timeout
self.get_timeout = get_timeout or timeout or self.get_timeout
if cache:
if cache is True:
from futile.caching import LRUCache
cache = LRUCache()
self.__cache = cache
if "://" not in uri:
uri = "http://" + uri
self.__content_type = content_type
self.component_name = component_name
info = urlparse(uri)
self.logger.debug("Restclient certfile is %s"%certfile)
if info.scheme == "https":
if bool(certfile) ^ bool(keyfile):
raise ValueError("Must give both certfile and keyfile if any")
if certfile:
from os.path import exists
if not exists(certfile):
raise ValueError("Certificate file not found: %s" % (certfile, ))
if not exists(keyfile):
raise ValueError("Key file not found: %s" % (keyfile, ))
elif info.scheme != "http":
raise ValueError(info.scheme)
else:
# In case of http, we do not want any certificates
keyfile = certfile = None
port = info.port and int(info.port) or getservbyname(info.scheme)
self.__base = info.path or ""
#if not self.__base.endswith("/"):
# self.__base += "/"
if not username:
username = info.username
if not headers:
headers = {}
headers.setdefault("Accept", "*/*")
headers["Accept-Encoding"] = "identity"
if username:
password = password or info.password or ""
headers["Authorization"] = "Basic " + b64encode("%s:%s" % (username, password))
self.__headers = headers
if not connection_manager:
#from SimpleConnectionManager import SimpleConnectionManager as connection_manager
from ConnectionPoolManager import ConnectionPoolManager as connection_manager
self.__connection_manager = connection_manager(host=info.hostname,
port=port,
certfile = certfile, keyfile = keyfile, cacertfile = cacertfile, force_ssl = info.scheme == "https")
def set_authinfo(self, username, password=""):
if not username:
self.__headers.pop("Authorization")
else:
self.__headers["Authorization"] = "Basic " + b64encode("%s:%s" % (quote_plus(username), password))
def request(self, method, path, data = None, headers = {}, args = None):
if isinstance(data, unicode):
data = data.encode("utf-8")
fullpath = self.__base + path
request_headers = self.__headers.copy()
if args:
fullpath += ("?" in fullpath and "&" or "?") + compose_qs(args)
if headers:
request_headers.update(headers)
if method == "GET":
timeout = self.get_timeout
if self.__cache:
try:
etag, modified, cached = self.__cache[fullpath]
if etag:
request_headers["If-None-Match"] = etag
request_headers["If-Modified-Since"] = modified
except KeyError:
request_headers.pop("If-None-Match", None)
request_headers.pop("If-Modified-Since", None)
else:
timeout = self.timeout
if data:
request_headers.setdefault("Content-Type", self.__content_type)
if hasattr(data, "read") and not hasattr(data, "fileno"):
data = data.read()
log_headers = request_headers
#if self.logger.isEnabledFor(DEBUG) and "Authorization" in request_headers:
#log_headers = request_headers.copy()
#log_headers["Authorization"] = "<purged>"
if method == "GET":
self.logger.debug("%s: %s (%s)", method, fullpath, log_headers)
else:
self.logger.debug("%s: %s (%s)\n%s", method, fullpath, log_headers, repr(data))
t = time()
try:
response = self.__connection_manager.request(method, fullpath, data, request_headers, timeout)
except Exception as e:
if self.logger.isEnabledFor(DEBUG):
self.logger.exception("Error during request")
if str(e) in ("", "''"):
e = repr(e)
try:
error_msg = "An error occurred while contacting the %s: %s. Request was: %s %s (%.4fs)" % (self.component_name, e, method, fullpath, time() - t)
except:
self.logger.exception("Failed to format error message.")
error_msg = "Error during request."
raise NetworkError(error_msg)
self.logger.debug("%s %s result: %s (%.4fs)", method, fullpath, response.status, time() - t)
r_status = response.status
if r_status == 304:
response.close()
try:
self.logger.debug("Using cached answer for %s (%s, %s):\n %s", fullpath, etag, modified, cached)
return closing(StringIO(cached))
except NameError:
raise NetworkError("Error: The %s returned 304 though no cached version is available. Request was: %s %s" % (self.component_name, method, fullpath))
if r_status == 302:
raise NotImplementedError("HTTP redirect")
if r_status < 200 or r_status >= 300:
with response:
via = response.getheader("Via")
try:
data = response.read(self.ERROR_RESPONSE_MAX and self.ERROR_RESPONSE_MAX + 1 or None)
if not data or (not self.logger.isEnabledFor(DEBUG) and "<html>" in data):
data = "<no further information available>"
else:
if self.ERROR_RESPONSE_MAX and len(data) > self.ERROR_RESPONSE_MAX:
data = data[:self.ERROR_RESPONSE_MAX] + " (truncated)\n"
data = data.encode("utf-8")
except Exception as e:
data = "<failed to read error response: %s>" % (e, )
if not data.endswith("\n"):
data += "\n"
try:
msg = "Error during execution. The %s said: %s %s - %sRequest was: %s %s. " % (self.component_name, response.status, response.reason, data, method, fullpath)
except:
msg = "Error during execution. The %s said %s. " % (self.component_name, response.status)
if via:
culprit = via.split(",")[0]
p = culprit.rfind("(")
if p >= 0 and culprit.endswith(")"):
culprit = culprit[p + 1:-1]
msg += "The error occurred after the request went through %s (Via: %s)." % (culprit, via)
else:
msg += "The error seems to have occurred at the %s (No Via header found in response)." % (self.component_name, )
raise HTTPError(msg=msg, status=response.status)
if method == "DELETE":
try:
self.__cache.pop(fullpath, None)
except AttributeError:
pass
else:
etag = response.getheader("Etag")
modified = response.getheader("Last-Modified")
if self.__cache is not False and (etag or modified):
if not modified:
modified = datetime.utcnow().strftime("%a, %d %b %Y %X GMT")
response = CachingHttplibResponseWrapper(response, fullpath, etag, modified, self.__cache)
elif self.logger.isEnabledFor(DEBUG):
response = LoggingResponseWrapper(response)
return response
def get(self, path, headers = None, args = None):
return self.request("GET", path, headers = headers, args = args)
def post(self, path, data, headers = None):
return self.request("POST", path, data, headers)
add = post
def put(self, path, data, headers = None):
return self.request("PUT", path, data, headers)
update = put
def delete(self, path, headers = None):
return self.request("DELETE", path, None, headers)

View File

@ -1,327 +0,0 @@
'''
Created on 21.05.2011
@author: kca
'''
from base64 import b64encode
from cStringIO import StringIO
from logging import DEBUG
from socket import getservbyname
from urllib2 import quote
from urlparse import urlparse
#import vertx
from aplus import Promise
from futile import ObjectProxy
from futile.logging import LoggerMixin
from futile.net.http.exc import NetworkError, HTTPError
def compose_qs(values):
return "&".join([ "%s=%s" % (quote(k), quote(v)) for k, v in dict(values).iteritems() ])
class LoggingResponseWrapper(LoggerMixin, ObjectProxy):
def __init__(self, response, *args, **kw):
super(LoggingResponseWrapper, self).__init__(proxyobject = response, *args, **kw)
self.__buffer = StringIO()
self.__finalized = False
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def __enter__(self):
return self
def read(self, n = None):
s = self._o.read(n)
self.__buffer.write(s)
return s
def readline(self):
s = self._o.readline()
self.__buffer.write(s)
return s
def readlines(self, sizehint = None):
lines = self._o.readlines(sizehint)
self.__buffer.write(''.join(lines))
return lines
def close(self):
if self.__finalized:
self.logger.debug("%s is already finalized" % (self, ))
return
self.__finalized = True
try:
if not self._o.isclosed():
self.__buffer.write(self._o.read())
self.logger.debug("Read data:\n %s", self.__buffer.getvalue())
except:
self.logger.exception("Finalizing response failed")
finally:
self._o.close()
self.__buffer.close()
class CachingHttplibResponseWrapper(ObjectProxy, LoggerMixin):
def __init__(self, response, path, tag, last_modified, cache, *args, **kw):
super(CachingHttplibResponseWrapper, self).__init__(proxyobject = response, *args, **kw)
self.__cache = cache
self.__buffer = StringIO()
self.__path = path
self.__tag = tag
self.__last_modified = last_modified
self.__finalized = False
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def __enter__(self):
return self
def read(self, n = None):
s = self._o.read(n)
self.__buffer.write(s)
return s
def readline(self):
s = self._o.readline()
self.__buffer.write(s)
return s
def readlines(self, sizehint = None):
lines = self._o.readlines(sizehint)
self.__buffer.write(''.join(lines))
return lines
def close(self):
if self.__finalized:
self.logger.debug("%s is already finalized" % (self, ))
return
self.__finalized = True
try:
if not self._o.isclosed():
self.__buffer.write(self._o.read())
val = self.__buffer.getvalue()
self.logger.debug("Putting to cache: %s -> %s, %s\n %s", self.__path, self.__tag, self.__last_modified, val)
self.__cache[self.__path] = (self.__tag, self.__last_modified, val)
except:
self.logger.exception("Finalizing response failed")
finally:
self._o.close()
self.__buffer.close()
def __getattr__(self, name):
return getattr(self._o, name)
class closing(ObjectProxy):
def __getattr__(self, k):
return getattr(self._o, k)
def __enter__(self):
return self._o
def __exit__(self, exc_type, exc_val, exc_tb):
self._o.close()
def close(self):
self._o.close()
class RestClient(LoggerMixin):
ERROR_RESPONSE_MAX = 320
get_timeout = timeout = 120.0
def __init__(self, uri, username=None, password=None, certfile=None,
keyfile=None, content_type="text/plain", headers=None,
cache=True, timeout=None, get_timeout=None,
component_name = "server", connection_manager = None,
*args, **kw):
super(RestClient, self).__init__(*args, **kw)
self.logger.debug("Creating RestClient for %s", uri)
self.timeout = timeout or self.timeout
self.get_timeout = get_timeout or timeout or self.get_timeout
if cache:
if cache is True:
from futile.caching import LRUCache
cache = LRUCache()
self.__cache = cache
if "://" not in uri:
uri = "http://" + uri
self.__content_type = content_type
self.component_name = component_name
info = urlparse(uri)
if info.scheme == "https":
if bool(certfile) ^ bool(keyfile):
raise ValueError("Must give both certfile and keyfile if any")
if certfile:
from os.path import exists
if not exists(certfile):
raise ValueError("Certificate file not found: %s" % (certfile, ))
if not exists(keyfile):
raise ValueError("Key file not found: %s" % (keyfile, ))
elif info.scheme != "http":
raise ValueError(info.scheme)
port = info.port and int(info.port) or getservbyname(info.scheme)
self.__base = info.path or ""
#if not self.__base.endswith("/"):
# self.__base += "/"
if not username:
username = info.username
if not headers:
headers = {}
headers.setdefault("Accept", "*/*")
headers["Accept-Encoding"] = "identity"
if username:
password = password or info.password or ""
headers["Authorization"] = "Basic " + b64encode("%s:%s" % (username, password))
self.__headers = headers
#if not connection_manager:
# #from SimpleConnectionManager import SimpleConnectionManager as connection_manager
# from ConnectionPoolManager import ConnectionPoolManager as connection_manager
#
# self.__connection_manager = connection_manager(host = info.hostname, port = port,
# certfile = certfile, keyfile = keyfile, force_ssl = info.scheme == "https")
#
self.client= vertx.create_http_client()
self.client.host = info.netloc.split(":")[0]
self.client.port = port
#temporary test server
#import json
#self.srv = vertx.create_http_server()
#def srv_handle(re):
# re.response.put_header("Content-Type","application/json; charset=utf-8")
# re.response.put_header("Location","locationlocation.location")
# re.response.end(json.dumps({"One":"Two"}))
#self.srv.request_handler(srv_handle)
#self.srv.listen(5000)
def request(self, method, path, data = None, headers = {}, args = None):
if isinstance(data, unicode):
data = data.encode("utf-8")
fullpath = self.__base + path
request_headers = self.__headers.copy()
if args:
fullpath += ("?" in fullpath and "&" or "?") + compose_qs(args)
if headers:
request_headers.update(headers)
if method == "GET":
timeout = self.get_timeout
try:
etag, modified, cached = self.__cache[fullpath]
if etag:
request_headers["If-None-Match"] = etag
request_headers["If-Modified-Since"] = modified
except KeyError:
request_headers.pop("If-None-Match", None)
request_headers.pop("If-Modified-Since", None)
else:
timeout = self.timeout
request_headers.setdefault("Content-Type", self.__content_type)
log_headers = request_headers
if self.logger.isEnabledFor(DEBUG) and "Authorization" in request_headers:
log_headers = request_headers.copy()
log_headers["Authorization"] = "<purged>"
if method == "GET":
self.logger.debug("%s: %s (%s)", method, fullpath, log_headers)
else:
self.logger.debug("%s: %s (%s)\n%s", method, fullpath, log_headers, repr(data))
#t = time()
promise=Promise()
try:
#response = self.__connection_manager.request(method, fullpath, data, request_headers, timeout)
def response_handler(resp):
if resp.status_code == 304:
try:
promise.fulfill(closing(StringIO(cached)))
except NameError:
promise.reject(NetworkError("Error: The %s returned 304 though no cached version is available. Request was: %s %s" % (self.component_name, method, fullpath)))
if resp.status_code < 200 or resp.status_code >= 300:
try:
promise.reject(HTTPError(msg = resp.status_message, status = resp.status_code))
except:
promise.reject(HTTPError(msg = "Http error", status = response.status))
else:
promise.fulfill(resp)
req=self.client.request(method,fullpath,response_handler)
for head,value in request_headers.items():
req.put_header(head,value)
if data:
req.chunked = True
req.write_str(data)
req.end()
except Exception as e:
print "Exception triggered: %s"%e
promise.reject(e)
return promise
#if method == "DELETE":
# try:
# self.__cache.pop(fullpath, None)
# except AttributeError:
# pass
#else:
# etag = response.getheader("Etag")
# modified = response.getheader("Last-Modified")
# if etag or modified:
# if not modified:
# modified = datetime.utcnow().strftime("%a, %d %b %Y %X GMT")
# response = CachingHttplibResponseWrapper(response, fullpath, etag, modified, self.__cache)
# elif self.logger.isEnabledFor(DEBUG):
# response = LoggingResponseWrapper(response)
def get(self, path, headers = None, args = None):
p = self.request("GET", path, headers = headers, args = args)
return p
def post(self, path, data, headers = None):
p = self.request("POST", path, data, headers)
return p
add = post
def put(self, path, data, headers = None):
p = self.request("PUT", path, data)
return p
update = put
def delete(self, path, headers = None):
p = self.request("DELETE", path, None, headers)
return p

View File

@ -1,61 +0,0 @@
'''
Created on 19.03.2013
@author: kca
'''
from httplib import HTTPConnection, HTTPSConnection
from futile.logging import LoggerMixin
class HttplibResponseWrapper(LoggerMixin):
def __init__(self, connection, *args, **kw):
super(HttplibResponseWrapper, self).__init__(*args, **kw)
self.__response = connection.getresponse()
self.__connection = connection
def __getattr__(self, k):
return getattr(self.__response, k)
def __enter__(self):
return self.__response
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
try:
self.__response.close()
except:
self.logger.exception("Error closing response")
finally:
self.__connection.close()
class SimpleConnectionManager(LoggerMixin):
def __init__(self, host, port, certfile = None, keyfile = None, force_ssl = False, *args, **kw):
super(SimpleConnectionManager, self).__init__(*args, **kw)
self.logger.debug("Creating SimpleConnectionManager for %s:%s", host, port)
if keyfile or certfile or force_ssl:
self.__certfile = certfile
self.__keyfile = keyfile
self._get_connection = self._get_secure_connection
self.__host = host
self.__port = port
def request(self, method, path, body, headers, timeout):
connection = self._get_connection(timeout)
try:
connection.request(method, path, body, headers)
return HttplibResponseWrapper(connection)
except:
connection.close()
raise
def _get_connection(self, timeout):
return HTTPConnection(self.__host, self.__port, timeout = timeout)
def _get_secure_connection(self, timeout):
return HTTPSConnection(self.__host, self.__port, self.__keyfile, self.__certfile, timeout = timeout)

View File

@ -1,134 +0,0 @@
'''
Created on 21.07.2011
@author: kca
'''
from futile.net.exc import NetworkError
STATUS_STRINGS = {
100: "Continue",
101: "Switching Protocols",
200: "Ok",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modfied",
305: "Use Proxy",
306: "",
307: "Temporary Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Request Entity Too Large",
414: "Request-URI Too Long",
415: "Unsupported Media Type",
416: "Requested Range Not Satisfiable",
417: "Expectation Failed",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
}
STATUS_MIN = 100
STATUS_MAX = 504
ERROR_MIN = 400
ERROR_MAX = 504
def get_error_message(statuscode):
try:
return STATUS_STRINGS[statuscode]
except KeyError:
raise ValueError(statuscode)
class HTTPErrorType(type):
__classes = {}
@classmethod
def get_error_class(cls, status):
try:
status = int(status)
except (TypeError, ValueError):
raise ValueError("Not a valid HTTP error code: '%s'" % (status, ))
try:
errorcls = cls.__classes[status]
except KeyError:
if status < STATUS_MIN or status > STATUS_MAX:
raise ValueError("Not a valid HTTP error code: %s" % (status,))
name = "HTTPError%s" % (status, )
errorcls = cls(name, (HTTPError, ), {"__init__":
cls._make_init(status)})
cls.__classes[status] = errorcls
globals()[name] = errorcls
return errorcls
def __call__(self, *args, **kw):
if self is HTTPError:
try:
status = kw.pop("status")
except KeyError:
try:
status = args[0]
args = args[1:]
except IndexError:
return super(HTTPErrorType, self).__call__(*args, **kw)
self = self.get_error_class(status)
return super(HTTPErrorType, self).__call__(*args, **kw)
@classmethod
def _make_init(cls, status):
def __init__(self, msg=None, reason=None, *args, **kw):
super(self.__class__, self).__init__(status=status,
reason=reason, msg=msg, *args, **kw)
return __init__
get_error_class = HTTPErrorType.get_error_class
class HTTPError(NetworkError):
__metaclass__ = HTTPErrorType
def __init__(self, status, reason=None, msg=None, *args, **kw):
status = int(status)
if not reason:
reason = STATUS_STRINGS.get(status, "Unknown Error")
if not msg:
msg = "HTTP Error %s - %s" % (status, reason)
super(HTTPError, self).__init__(msg, status, reason, *args, **kw)
@property
def message(self):
return self.args[0]
@property
def status(self):
return self.args[1]
@property
def reason(self):
return self.args[2]

View File

@ -1,54 +0,0 @@
'''
Created on 18.08.2011
@author: kca
'''
from futile.logging import LoggerMixin
from ssl import wrap_socket, SSLSocket, SSLError, CERT_OPTIONAL, CERT_NONE
from socket import error
from futile import NOT_SET
class HTTPSMixin(LoggerMixin):
certfile = keyfile = ca_certs = None
cert_reqs = CERT_NONE
def init_https(self, certfile, keyfile = None, ca_certs = None, cert_reqs = NOT_SET, secure = True):
self.keyfile = keyfile
self.certfile = certfile
self.ca_certs = ca_certs
if cert_reqs is NOT_SET:
cert_reqs = ca_certs and CERT_OPTIONAL or CERT_NONE
self.cert_reqs = cert_reqs
if secure:
self.enable_https()
def enable_https(self):
if not self.secure:
if not self.certfile:
raise SSLError("Certificate info missing.")
if self.cert_reqs != CERT_NONE and not self.ca_certs:
raise SSLError("Certificate validation requested but no ca certs available.")
self.logger.debug("Enabling https with certfile=%s kefile=%s ca_certs=%s cert_reqs=%s", self.certfile, self.keyfile, self.ca_certs, self.cert_reqs)
self.socket = wrap_socket(self.socket, server_side = True, keyfile = self.keyfile, certfile = self.certfile, ca_certs = self.ca_certs, cert_reqs = self.cert_reqs)
def disable_https(self):
if self.secure:
self.socket = self.socket._sock
def get_request(self):
try:
return self.socket.accept()
except error, e:
self.logger.exception("Error during accept(): %s", e)
raise
def is_secure(self):
return isinstance(self.socket, SSLSocket)
def set_secure(self, s):
if s:
self.enable_https()
else:
self.disable_https()
return s
secure = property(is_secure)

View File

@ -1,19 +0,0 @@
'''
Created on 17.07.2011
@author: kca
'''
from wsgiref.simple_server import WSGIRequestHandler, WSGIServer as _WSGIServer
from SocketServer import ThreadingMixIn, ForkingMixIn
class WSGIServer(_WSGIServer):
def __init__(self, server_address, app = None, RequestHandlerClass = WSGIRequestHandler):
_WSGIServer.__init__(self, server_address, RequestHandlerClass)
self.set_app(app)
class ThreadingWSGIServer(ThreadingMixIn, WSGIServer):
pass
class ForkingWSGIServer(ForkingMixIn, WSGIServer):
pass

View File

@ -1,22 +0,0 @@
'''
Created on 22.08.2011
@author: kca
'''
from ..ssl import HTTPSMixin
from ..wsgi import WSGIServer
from SocketServer import ThreadingMixIn, ForkingMixIn
from wsgiref.simple_server import WSGIRequestHandler
from futile import NOT_SET
class SecureWSGIServer(HTTPSMixin, WSGIServer):
def __init__(self, server_address, certfile, keyfile = None, ca_certs = None, cert_reqs = NOT_SET, app = None, RequestHandlerClass = WSGIRequestHandler):
WSGIServer.__init__(self, server_address, app = app, RequestHandlerClass = RequestHandlerClass)
self.init_https(certfile, keyfile, ca_certs = ca_certs, cert_reqs = cert_reqs)
class SecureThreadingWSGIServer(ThreadingMixIn, SecureWSGIServer):
pass
class SecureForkingWSGIServer(ForkingMixIn, SecureWSGIServer):
pass

View File

@ -1,13 +0,0 @@
'''
Created on 14.07.2011
@author: kca
'''
from socket import socket as _socket, AF_INET, SOCK_STREAM
from futile.contextlib import closing
def socket(family = AF_INET, type = SOCK_STREAM, proto = 0):
return closing(_socket(family, type, proto))

View File

@ -1,14 +0,0 @@
'''
Created on 21.01.2012
@author: kca
'''
from wsgiref.simple_server import WSGIServer
from SocketServer import ThreadingMixIn, ForkingMixIn
class ThreadingWSGIServer(ThreadingMixIn, WSGIServer):
pass
class ForkingWSGIServer(ForkingMixIn, WSGIServer):
pass

View File

@ -1,40 +0,0 @@
'''
Created on 17.07.2011
@author: kca
'''
from futile import Base
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
class WSGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher, Base):
def __init__(self, encoding=None):
SimpleXMLRPCDispatcher.__init__(self, allow_none = True, encoding = encoding)
def __call__(self, environ, start_response):
if environ["REQUEST_METHOD"] != "POST":
headers = [("Content-type", "text/html")]
if environ["REQUEST_METHOD"] == "HEAD":
data = ""
else:
data = "<html><head><title>400 Bad request</title></head><body><h1>400 Bad request</h1></body></html>"
headers.append(("Content-length", str(len(data))))
start_response("400 Bad request", headers)
return (data, )
l = int(environ["CONTENT_LENGTH"])
request = environ["wsgi.input"].read(l)
response = self._marshaled_dispatch(request)
headers = [("Content-type", "text/xml"), ("Content-length", str(len(response)))]
start_response("200 OK", headers)
return (response, )
def _dispatch(self, *args, **kw):
try:
result = SimpleXMLRPCDispatcher._dispatch(self, *args, **kw)
# self.logger.debug("Result: %s" % (result, ))
return result
except:
self.logger.exception("Error while processing request")
raise

View File

@ -1,9 +0,0 @@
from operator import attrgetter
def attrproperty(name):
return property(attrgetter(name))
def resolve_attr(obj, attr):
for name in attr.split("."):
obj = getattr(obj, name)
return obj

View File

@ -1,6 +0,0 @@
def get_fileobj(f):
if not hasattr(f, "read"):
return open(f)
return f

View File

@ -1,53 +0,0 @@
'''
Created on 24.01.2012
@author: kca
'''
from ..path import Path
from ..subprocess import check_output
def umount(where, force = False):
cmd = [ "umount", where ]
if force:
cmd.append("-f")
check_output(cmd)
unmount = umount
def mount(what, where, fstype = None, options = None):
return Mount(what, where, fstype, options).mount()
class Mount(object):
def __init__(self, what, where, fstype = None, options = None):
self.what = Path(what)
self.where = Path(where)
self.fstype = fstype
options = self.options = options and set(options) or set()
if what.isfile():
options.add("loop")
elif not what.isblockdev():
raise ValueError("Mount source must be a file or block device: %s" % (what, ))
def mount(self, fstype = None, options = None):
cmd = [ "mount", self.what, self.where ]
fstype = fstype or self.fstype
if fstype:
cmd += [ "-t", self.fstype ]
opts = self.options
if options:
opts += set(self.options)
if opts:
cmd += [ "-o", ','.join(self.options) ]
check_output(cmd)
return self
__enter__ = mount
def umount(self, force = False):
umount(self.where, force)
unmount = umount
def __exit__(self, exc_type, exc_val, exc_tb):
self.umount(True)

View File

@ -1,865 +0,0 @@
""" path.py - An object representing a path to a file or directory.
Example:
from path import path
d = path('/home/guido/bin')
for f in d.files('*.py'):
f.chmod(0755)
This module requires Python 2.2 or later.
URL: http://www.jorendorff.com/articles/python/path
Author: Jason Orendorff <jason@jorendorff.com> (and others - see the url!)
Date: 7 Mar 2004
"""
# Note - this is an umodified version of Jason Orendorff's 'path' module.
# TODO
# - Bug in write_text(). It doesn't support Universal newline mode.
# - Better error message in listdir() when self isn't a
# directory. (On Windows, the error message really sucks.)
# - Make sure everything has a good docstring.
# - Add methods for regex find and replace.
# - guess_content_type() method?
# - Perhaps support arguments to touch().
# - Could add split() and join() methods that generate warnings.
# - Note: __add__() technically has a bug, I think, where
# it doesn't play nice with other types that implement
# __radd__(). Test this.
from __future__ import generators
def quote(p):
from urllib2 import quote
return quote(p, "")
import sys, os, fnmatch, glob, shutil, codecs
__version__ = '2.0.4'
__all__ = ['path']
# Pre-2.3 support. Are unicode filenames supported?
_base = str
try:
if os.path.supports_unicode_filenames:
_base = unicode
except AttributeError:
pass
# Pre-2.3 workaround for basestring.
try:
basestring
except NameError:
basestring = (str, unicode)
# Universal newline support
_textmode = 'r'
if hasattr(file, 'newlines'):
_textmode = 'U'
class path(_base):
""" Represents a filesystem path.
For documentation on individual methods, consult their
counterparts in os.path.
"""
# --- Special Python methods.
def __repr__(self):
return 'path(%s)' % _base.__repr__(self)
# Adding a path and a string yields a path.
def __add__(self, more):
return path(_base(self) + more)
def __radd__(self, other):
return path(other + _base(self))
# The / operator joins paths.
def __div__(self, rel):
""" fp.__div__(rel) == fp / rel == fp.joinpath(rel)
Join two path components, adding a separator character if
needed.
"""
return path(os.path.join(self, rel))
# Make the / operator work even when true division is enabled.
__truediv__ = __div__
def getcwd():
""" Return the current working directory as a path object. """
return path(os.getcwd())
getcwd = staticmethod(getcwd)
# --- Operations on path strings.
def abspath(self): return path(os.path.abspath(self))
def normcase(self): return path(os.path.normcase(self))
def normpath(self): return path(os.path.normpath(self))
def realpath(self): return path(os.path.realpath(self))
def expanduser(self): return path(os.path.expanduser(self))
def expandvars(self): return path(os.path.expandvars(self))
def dirname(self): return path(os.path.dirname(self))
basename = os.path.basename
def expand(self):
""" Clean up a filename by calling expandvars(),
expanduser(), and normpath() on it.
This is commonly everything needed to clean up a filename
read from a configuration file, for example.
"""
return self.expandvars().expanduser().normpath()
def _get_namebase(self):
base, _ext = os.path.splitext(self.name)
return base
def _get_ext(self):
_f, ext = os.path.splitext(_base(self))
return ext
def _get_drive(self):
drive, _r = os.path.splitdrive(self)
return path(drive)
parent = property(
dirname, None, None,
""" This path's parent directory, as a new path object.
For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
""")
name = property(
basename, None, None,
""" The name of this file or directory without the full path.
For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'
""")
namebase = property(
_get_namebase, None, None,
""" The same as path.name, but with one file extension stripped off.
For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
but path('/home/guido/python.tar.gz').namebase == 'python.tar'
""")
ext = property(
_get_ext, None, None,
""" The file extension, for example '.py'. """)
drive = property(
_get_drive, None, None,
""" The drive specifier, for example 'C:'.
This is always empty on systems that don't use drive specifiers.
""")
def splitpath(self):
""" p.splitpath() -> Return (p.parent, p.name). """
parent, child = os.path.split(self)
return path(parent), child
def splitdrive(self):
""" p.splitdrive() -> Return (p.drive, <the rest of p>).
Split the drive specifier from this path. If there is
no drive specifier, p.drive is empty, so the return value
is simply (path(''), p). This is always the case on Unix.
"""
drive, rel = os.path.splitdrive(self)
return path(drive), rel
def splitext(self):
""" p.splitext() -> Return (p.stripext(), p.ext).
Split the filename extension from this path and return
the two parts. Either part may be empty.
The extension is everything from '.' to the end of the
last path segment. This has the property that if
(a, b) == p.splitext(), then a + b == p.
"""
filename, ext = os.path.splitext(self)
return path(filename), ext
def stripext(self):
""" p.stripext() -> Remove one file extension from the path.
For example, path('/home/guido/python.tar.gz').stripext()
returns path('/home/guido/python.tar').
"""
return self.splitext()[0]
if hasattr(os.path, 'splitunc'):
def splitunc(self):
unc, rest = os.path.splitunc(self)
return path(unc), rest
def _get_uncshare(self):
unc, r = os.path.splitunc(self)
return path(unc)
uncshare = property(
_get_uncshare, None, None,
""" The UNC mount point for this path.
This is empty for paths on local drives. """)
def joinpath(self, *args):
""" Join two or more path components, adding a separator
character (os.sep) if needed. Returns a new path
object.
"""
return path(os.path.join(self, *args))
def splitall(self):
""" Return a list of the path components in this path.
The first item in the list will be a path. Its value will be
either os.curdir, os.pardir, empty, or the root directory of
this path (for example, '/' or 'C:\\'). The other items in
the list will be strings.
path.path.joinpath(*result) will yield the original path.
"""
parts = []
loc = self
while loc != os.curdir and loc != os.pardir:
prev = loc
loc, child = prev.splitpath()
if loc == prev:
break
parts.append(child)
parts.append(loc)
parts.reverse()
return parts
def relpath(self):
""" Return this path as a relative path,
based from the current working directory.
"""
cwd = path(os.getcwd())
return cwd.relpathto(self)
def relpathto(self, dest):
""" Return a relative path from self to dest.
If there is no relative path from self to dest, for example if
they reside on different drives in Windows, then this returns
dest.abspath().
"""
origin = self.abspath()
dest = path(dest).abspath()
orig_list = origin.normcase().splitall()
# Don't normcase dest! We want to preserve the case.
dest_list = dest.splitall()
if orig_list[0] != os.path.normcase(dest_list[0]):
# Can't get here from there.
return dest
# Find the location where the two paths start to differ.
i = 0
for start_seg, dest_seg in zip(orig_list, dest_list):
if start_seg != os.path.normcase(dest_seg):
break
i += 1
# Now i is the point where the two paths diverge.
# Need a certain number of "os.pardir"s to work up
# from the origin to the point of divergence.
segments = [os.pardir] * (len(orig_list) - i)
# Need to add the diverging part of dest_list.
segments += dest_list[i:]
if len(segments) == 0:
# If they happen to be identical, use os.curdir.
return path(os.curdir)
else:
return path(os.path.join(*segments))
# --- Listing, searching, walking, and matching
def listdir(self, pattern=None):
""" D.listdir() -> List of items in this directory.
Use D.files() or D.dirs() instead if you want a listing
of just files or just subdirectories.
The elements of the list are path objects.
With the optional 'pattern' argument, this only lists
items whose names match the given pattern.
"""
names = os.listdir(self)
if pattern is not None:
names = fnmatch.filter(names, pattern)
return [self / child for child in names]
def dirs(self, pattern=None):
""" D.dirs() -> List of this directory's subdirectories.
The elements of the list are path objects.
This does not walk recursively into subdirectories
(but see path.walkdirs).
With the optional 'pattern' argument, this only lists
directories whose names match the given pattern. For
example, d.dirs('build-*').
"""
return [p for p in self.listdir(pattern) if p.isdir()]
def devs(self, pattern = None):
return [p for p in self.listdir(pattern) if p.isdev()]
def blockdevs(self, pattern = None):
return [p for p in self.listdir(pattern) if p.isblockdev()]
def chardevs(self, pattern = None):
return [p for p in self.listdir(pattern) if p.ischardev()]
def files(self, pattern=None):
""" D.files() -> List of the files in this directory.
The elements of the list are path objects.
This does not walk into subdirectories (see path.walkfiles).
With the optional 'pattern' argument, this only lists files
whose names match the given pattern. For example,
d.files('*.pyc').
"""
return [p for p in self.listdir(pattern) if p.isfile()]
def walk(self, pattern=None):
""" D.walk() -> iterator over files and subdirs, recursively.
The iterator yields path objects naming each child item of
this directory and its descendants. This requires that
D.isdir().
This performs a depth-first traversal of the directory tree.
Each directory is returned just before all its children.
"""
for child in self.listdir():
if pattern is None or child.fnmatch(pattern):
yield child
if child.isdir():
for item in child.walk(pattern):
yield item
def walkdirs(self, pattern=None):
""" D.walkdirs() -> iterator over subdirs, recursively.
With the optional 'pattern' argument, this yields only
directories whose names match the given pattern. For
example, mydir.walkdirs('*test') yields only directories
with names ending in 'test'.
"""
for child in self.dirs():
if pattern is None or child.fnmatch(pattern):
yield child
for subsubdir in child.walkdirs(pattern):
yield subsubdir
def walkfiles(self, pattern=None):
""" D.walkfiles() -> iterator over files in D, recursively.
The optional argument, pattern, limits the results to files
with names that match the pattern. For example,
mydir.walkfiles('*.tmp') yields only files with the .tmp
extension.
"""
for child in self.listdir():
if child.isfile():
if pattern is None or child.fnmatch(pattern):
yield child
elif child.isdir():
for f in child.walkfiles(pattern):
yield f
def fnmatch(self, pattern):
""" Return True if self.name matches the given pattern.
pattern - A filename pattern with wildcards,
for example '*.py'.
"""
return fnmatch.fnmatch(self.name, pattern)
def glob(self, pattern):
""" Return a list of path objects that match the pattern.
pattern - a path relative to this directory, with wildcards.
For example, path('/users').glob('*/bin/*') returns a list
of all the files users have in their bin directories.
"""
return map(path, glob.glob(_base(self / pattern)))
# --- Reading or writing an entire file at once.
def open(self, mode='r'):
""" Open this file. Return a file object. """
return file(self, mode)
def bytes(self):
""" Open this file, read all bytes, return them as a string. """
f = self.open('rb')
try:
return f.read()
finally:
f.close()
def write_bytes(self, bytes, append=False):
""" Open this file and write the given bytes to it.
Default behavior is to overwrite any existing file.
Call this with write_bytes(bytes, append=True) to append instead.
"""
if append:
mode = 'ab'
else:
mode = 'wb'
f = self.open(mode)
try:
f.write(bytes)
finally:
f.close()
def text(self, encoding=None, errors='strict'):
""" Open this file, read it in, return the content as a string.
This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r'
are automatically translated to '\n'.
Optional arguments:
encoding - The Unicode encoding (or character set) of
the file. If present, the content of the file is
decoded and returned as a unicode object; otherwise
it is returned as an 8-bit str.
errors - How to handle Unicode errors; see help(str.decode)
for the options. Default is 'strict'.
"""
if encoding is None:
# 8-bit
f = self.open(_textmode)
try:
return f.read()
finally:
f.close()
else:
# Unicode
f = codecs.open(self, 'r', encoding, errors)
# (Note - Can't use 'U' mode here, since codecs.open
# doesn't support 'U' mode, even in Python 2.3.)
try:
t = f.read()
finally:
f.close()
return (t.replace(u'\r\n', u'\n')
.replace(u'\r\x85', u'\n')
.replace(u'\r', u'\n')
.replace(u'\x85', u'\n')
.replace(u'\u2028', u'\n'))
def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False):
""" Write the given text to this file.
The default behavior is to overwrite any existing file;
to append instead, use the 'append=True' keyword argument.
There are two differences between path.write_text() and
path.write_bytes(): newline handling and Unicode handling.
See below.
Parameters:
- text - str/unicode - The text to be written.
- encoding - str - The Unicode encoding that will be used.
This is ignored if 'text' isn't a Unicode string.
- errors - str - How to handle Unicode encoding errors.
Default is 'strict'. See help(unicode.encode) for the
options. This is ignored if 'text' isn't a Unicode
string.
- linesep - keyword argument - str/unicode - The sequence of
characters to be used to mark end-of-line. The default is
os.linesep. You can also specify None; this means to
leave all newlines as they are in 'text'.
- append - keyword argument - bool - Specifies what to do if
the file already exists (True: append to the end of it;
False: overwrite it.) The default is False.
--- Newline handling.
write_text() converts all standard end-of-line sequences
('\n', '\r', and '\r\n') to your platform's default end-of-line
sequence (see os.linesep; on Windows, for example, the
end-of-line marker is '\r\n').
If you don't like your platform's default, you can override it
using the 'linesep=' keyword argument. If you specifically want
write_text() to preserve the newlines as-is, use 'linesep=None'.
This applies to Unicode text the same as to 8-bit text, except
there are three additional standard Unicode end-of-line sequences:
u'\x85', u'\r\x85', and u'\u2028'.
(This is slightly different from when you open a file for
writing with fopen(filename, "w") in C or file(filename, 'w')
in Python.)
--- Unicode
If 'text' isn't Unicode, then apart from newline handling, the
bytes are written verbatim to the file. The 'encoding' and
'errors' arguments are not used and must be omitted.
If 'text' is Unicode, it is first converted to bytes using the
specified 'encoding' (or the default encoding if 'encoding'
isn't specified). The 'errors' argument applies only to this
conversion.
"""
if isinstance(text, unicode):
if linesep is not None:
# Convert all standard end-of-line sequences to
# ordinary newline characters.
text = (text.replace(u'\r\n', u'\n')
.replace(u'\r\x85', u'\n')
.replace(u'\r', u'\n')
.replace(u'\x85', u'\n')
.replace(u'\u2028', u'\n'))
text = text.replace(u'\n', linesep)
if encoding is None:
encoding = sys.getdefaultencoding()
bytes = text.encode(encoding, errors)
else:
# It is an error to specify an encoding if 'text' is
# an 8-bit string.
assert encoding is None
if linesep is not None:
text = (text.replace('\r\n', '\n')
.replace('\r', '\n'))
bytes = text.replace('\n', linesep)
self.write_bytes(bytes, append)
def lines(self, encoding=None, errors='strict', retain=True):
""" Open this file, read all lines, return them in a list.
Optional arguments:
encoding - The Unicode encoding (or character set) of
the file. The default is None, meaning the content
of the file is read as 8-bit characters and returned
as a list of (non-Unicode) str objects.
errors - How to handle Unicode errors; see help(str.decode)
for the options. Default is 'strict'
retain - If true, retain newline characters; but all newline
character combinations ('\r', '\n', '\r\n') are
translated to '\n'. If false, newline characters are
stripped off. Default is True.
This uses 'U' mode in Python 2.3 and later.
"""
if encoding is None and retain:
f = self.open(_textmode)
try:
return f.readlines()
finally:
f.close()
else:
return self.text(encoding, errors).splitlines(retain)
def write_lines(self, lines, encoding=None, errors='strict',
linesep=os.linesep, append=False):
""" Write the given lines of text to this file.
By default this overwrites any existing file at this path.
This puts a platform-specific newline sequence on every line.
See 'linesep' below.
lines - A list of strings.
encoding - A Unicode encoding to use. This applies only if
'lines' contains any Unicode strings.
errors - How to handle errors in Unicode encoding. This
also applies only to Unicode strings.
linesep - The desired line-ending. This line-ending is
applied to every line. If a line already has any
standard line ending ('\r', '\n', '\r\n', u'\x85',
u'\r\x85', u'\u2028'), that will be stripped off and
this will be used instead. The default is os.linesep,
which is platform-dependent ('\r\n' on Windows, '\n' on
Unix, etc.) Specify None to write the lines as-is,
like file.writelines().
Use the keyword argument append=True to append lines to the
file. The default is to overwrite the file. Warning:
When you use this with Unicode data, if the encoding of the
existing data in the file is different from the encoding
you specify with the encoding= parameter, the result is
mixed-encoding data, which can really confuse someone trying
to read the file later.
"""
if append:
mode = 'ab'
else:
mode = 'wb'
f = self.open(mode)
try:
for line in lines:
isUnicode = isinstance(line, unicode)
if linesep is not None:
# Strip off any existing line-end and add the
# specified linesep string.
if isUnicode:
if line[-2:] in (u'\r\n', u'\x0d\x85'):
line = line[:-2]
elif line[-1:] in (u'\r', u'\n',
u'\x85', u'\u2028'):
line = line[:-1]
else:
if line[-2:] == '\r\n':
line = line[:-2]
elif line[-1:] in ('\r', '\n'):
line = line[:-1]
line += linesep
if isUnicode:
if encoding is None:
encoding = sys.getdefaultencoding()
line = line.encode(encoding, errors)
f.write(line)
finally:
f.close()
# --- Methods for querying the filesystem.
exists = os.path.exists
isabs = os.path.isabs
isdir = os.path.isdir
isfile = os.path.isfile
islink = os.path.islink
ismount = os.path.ismount
if hasattr(os.path, 'samefile'):
samefile = os.path.samefile
getatime = os.path.getatime
atime = property(
getatime, None, None,
""" Last access time of the file. """)
getmtime = os.path.getmtime
mtime = property(
getmtime, None, None,
""" Last-modified time of the file. """)
if hasattr(os.path, 'getctime'):
getctime = os.path.getctime
ctime = property(
getctime, None, None,
""" Creation time of the file. """)
getsize = os.path.getsize
size = property(
getsize, None, None,
""" Size of the file, in bytes. """)
def isdev(self):
from stat import S_ISBLK, S_ISCHR
mode = self.__st_mode()
return S_ISBLK(mode) or S_ISCHR(mode)
def __st_mode(self):
try:
return self.stat().st_mode
except OSError as e:
if e.errno != 2:
raise
return 0
def ischardev(self):
from stat import S_ISCHR
return S_ISCHR(self.__st_mode())
def isblockdev(self):
from stat import S_ISBLK
return S_ISBLK(self.__st_mode())
if hasattr(os, 'access'):
def access(self, mode):
""" Return true if current user has access to this path.
mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK
"""
return os.access(self, mode)
def stat(self):
""" Perform a stat() system call on this path. """
return os.stat(self)
def lstat(self):
""" Like path.stat(), but do not follow symbolic links. """
return os.lstat(self)
if hasattr(os, 'statvfs'):
def statvfs(self):
""" Perform a statvfs() system call on this path. """
return os.statvfs(self)
if hasattr(os, 'pathconf'):
def pathconf(self, name):
return os.pathconf(self, name)
# --- Modifying operations on files and directories
def utime(self, times):
""" Set the access and modified times of this file. """
os.utime(self, times)
def chmod(self, mode):
os.chmod(self, mode)
if hasattr(os, 'chown'):
def chown(self, uid, gid):
os.chown(self, uid, gid)
def rename(self, new):
os.rename(self, new)
def renames(self, new):
os.renames(self, new)
# --- Create/delete operations on directories
def mkdir(self, mode=0750):
os.mkdir(self, mode)
def makedirs(self, mode=0750):
os.makedirs(self, mode)
def rmdir(self):
os.rmdir(self)
def removedirs(self):
os.removedirs(self)
# --- Modifying operations on files
def touch(self, mode = 0640):
""" Set the access/modified times of this file to the current time.
Create the file if it does not exist.
"""
fd = os.open(self, os.O_WRONLY | os.O_CREAT, mode)
os.close(fd)
os.utime(self, None)
def remove(self):
os.remove(self)
def unlink(self):
os.unlink(self)
# --- Links
if hasattr(os, 'link'):
def link(self, newpath):
""" Create a hard link at 'newpath', pointing to this file. """
os.link(self, newpath)
if hasattr(os, 'symlink'):
def symlink(self, newlink):
""" Create a symbolic link at 'newlink', pointing here. """
os.symlink(self, newlink)
if hasattr(os, 'readlink'):
def readlink(self):
""" Return the path to which this symbolic link points.
The result may be an absolute or a relative path.
"""
return path(os.readlink(self))
def readlinkabs(self):
""" Return the path to which this symbolic link points.
The result is always an absolute path.
"""
p = self.readlink()
if p.isabs():
return p
else:
return (self.parent / p).abspath()
def checkdir(self):
if not self.isdir():
raise Exception("Not a directory: '%s'" % (self, ))
def checkfile(self):
if not self.isfile():
raise Exception("Not a file: '%s'" % (self, ))
def forcedir(self, mode = 0750):
if not self.isdir():
if self.exists():
raise Exception("Not a directory: '%s'" % (self, ))
self.makedirs(mode)
def forcefile(self, mode = 0640):
if not self.exists():
return self.touch(mode = 0640)
if not self.isfile():
raise Exception("Not a file: %s" % (self ,))
# --- High-level functions from shutil
copyfile = shutil.copyfile
copymode = shutil.copymode
copystat = shutil.copystat
copy = shutil.copy
copy2 = shutil.copy2
copytree = shutil.copytree
if hasattr(shutil, 'move'):
move = shutil.move
def rmtree(self):
if self.isdir():
return shutil.rmtree(self)
self.unlink()
quote = quote
# --- Special stuff from os
if hasattr(os, 'chroot'):
def chroot(self):
os.chroot(self)
if hasattr(os, 'startfile'):
startfile = os.startfile
Path = path

View File

@ -1,16 +0,0 @@
from time import time
def timeit(f):
def _timeit(*args, **kw):
_timeit.__runs__ += 1
start = time()
try:
return f(*args, **kw)
finally:
spent = _timeit.__last_time__ = time() - start
_timeit.__total_time__ += spent
_timeit.__runs__ = 0
_timeit.__total_time__ = 0.0
_timeit.__last_time__ = None
_timeit.__name__ = f.__name__
return _timeit

View File

@ -1,8 +0,0 @@
'''
Created on 24.09.2011
@author: kca
'''
class ParseError(Exception):
pass

View File

@ -1,51 +0,0 @@
'''
Created on 28.08.2011
@author: kca
'''
from ..logging import LoggerMixin
from logging import DEBUG
from ..etree.impl import ElementTree, XML, ParseError as XMLParseError, XMLSyntaxError, tostring
from abc import ABCMeta, abstractmethod
from futile.serializer.exc import ParseError
class AbstractXMLSerializer(LoggerMixin):
__metaclass__ = ABCMeta
def load(self, input):
if self.logger.isEnabledFor(DEBUG):
from cStringIO import StringIO
input = input.read()
self.logger.debug("Parsing input: %s", input)
input = StringIO(input)
root = self._load(input)
return self._parse_input(root)
def _load(self, input):
try:
if isinstance(input, str):
return XML(input)
else:
return ElementTree().parse(input)
except Exception, e:
self._handle_parse_error(e)
raise ParseError(e)
def _handle_parse_error(self, e):
self.logger.exception("Error parsing input: %s", e)
@abstractmethod
def _parse_input(self, root):
raise NotImplementedError()
def dump(self, o, pretty_print = True):
raise NotImplementedError()
def dumps(self, o, pretty_print = True):
xml = self._dump_object(o)
return tostring(xml, pretty_print = pretty_print)
@abstractmethod
def _dump_object(self, o):
raise NotImplementedError()

View File

@ -1 +0,0 @@
from timeout import timeout, Timeout

View File

@ -1,29 +0,0 @@
'''
Created on 20.05.2011
@author: kca
'''
from signal import signal, SIGALRM, alarm
from contextlib import contextmanager
from futile import noop
@contextmanager
def timeout(seconds):
if not seconds:
yield
return
original_handler = signal(SIGALRM, noop)
try:
alarm(seconds)
yield
finally:
alarm(0)
signal(SIGALRM, original_handler)
def Timeout(seconds):
return lambda: timeout(seconds)

View File

@ -1,30 +0,0 @@
'''
Created on 23.07.2011
@author: kca
'''
from futile import Base
from futile.logging import LoggerMixin
class SingletonType(type, LoggerMixin):
__instances = {}
def get_instance(self):
try:
i = self.__instances[self]
self.logger.debug("Reusing singleton instance for %s.%s" % (self.__module__, self.__name__))
except KeyError:
self.logger.debug("Creating singleton instance for %s.%s" % (self.__module__, self.__name__))
i = super(SingletonType, self).__call__()
self.__instances[self] = i
return i
class ForcedSingletonType(SingletonType):
def __call__(self, *args, **kw):
return self.get_instance()
class Singleton(Base):
__metaclass__ = SingletonType
class ForcedSingleton(Base):
__metaclass__ = ForcedSingletonType

View File

@ -1,23 +0,0 @@
import string
letters_digits_underscore = string.letters + string.digits + "_"
class InvalidIdentifier(ValueError):
pass
def is_identifier(s):
if not s or s[0] not in string.letters:
return False
for c in s:
if c not in letters_digits_underscore:
return False
return True
def check_identifier(s):
if not is_identifier(s):
raise InvalidIdentifier(s)

View File

@ -1,46 +0,0 @@
'''
Created on 17.07.2011
@author: kca
'''
import logging, sys
from futile.logging import get_logger
from subprocess import check_output as _check_output, check_call as _check_call, CalledProcessError, STDOUT, Popen
try:
from subprocces import SubprocessError, TimeoutExpired
except ImportError:
class SubprocessError(Exception):
pass
class TimeoutExpired(SubprocessError):
pass
def _pre_call(args):
#needed for chroot safety
import encodings.string_escape
cmd = ' '.join(args)
get_logger().debug("running %s" % (cmd, ))
return cmd
def check_output(args, stdin=None, stderr=STDOUT, shell=False, cwd=None, env=None, *popenargs, **popenkw):
cmd = _pre_call(args)
try:
return _check_output(args, stdin=stdin, stderr=stderr, shell=shell, cwd=cwd, env=env, *popenargs, **popenkw)
except CalledProcessError as e:
get_logger().debug("Command %s returned exit code %s. This is the programs output:\n%s<<EOF>>" % (cmd, e.returncode, e.output))
raise
def check_call(args, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, env=None, *popenargs, **popenkw):
cmd = _pre_call(args)
try:
return _check_call(args, stdin=stdin, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, env=env, *popenargs, **popenkw)
except CalledProcessError as e:
get_logger().debug("Command %s returned exit code %s." % (cmd, e.returncode))
raise

View File

@ -1,165 +0,0 @@
'''
Created on 02.02.2012
@author: kca
'''
from time import sleep
from abc import ABCMeta, abstractproperty, abstractmethod
from futile import Base
from futile.path import Path
from . import check_call, STDOUT
class DaemonController(Base):
__metaclass__ = ABCMeta
def __init__(self, sleep = 5, stop_sleep = 3, *args, **kw):
super(DaemonController, self).__init__(*args, **kw)
self.__sleep = int(sleep)
self.__stop_sleep = int(stop_sleep)
@abstractproperty
def is_running(self):
raise NotImplementedError()
def start(self):
self._start()
sleep(self.__sleep)
@abstractmethod
def _start(self):
raise NotImplementedError()
def stop(self):
self._stop()
sleep(self.__stop_sleep)
@abstractmethod
def _stop(self):
raise NotImplementedError()
def __enter__(self):
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
class DummyController(DaemonController):
def __init__(self, sleep = 0, stop_sleep = 0, *args, **kw):
super(DummyController).__init__(sleep = sleep, stop_sleep = stop_sleep, *args, **kw)
def _start(self):
pass
_stop = _start
@property
def is_running(self):
return False
import os
import errno
class CheckPIDFileController(DaemonController):
def __init__(self, pidfile, *args, **kw):
super(CheckPIDFileController, self).__init__(*args, **kw)
self.__pidfile = Path(pidfile)
@property
def pidfile(self):
return self.__pidfile
@property
def is_running(self):
if not self.pidfile.exists():
return False
if not self.pidfile.isfile():
raise Exception("pidfile '%s' is not a file" % (self.pidfile, ))
try:
pid = int(self.__pidfile.open().readline(16))
except:
self.logger.exception("Error reading pidfile %s" % (self.pidfile))
raise
try:
os.kill(pid, 0)
return True
except OSError, e:
if e.errno == errno.ESRCH:
return False
raise
class StartStopDaemonController(CheckPIDFileController):
def __init__(self, executable, fork = False, workingdir = None, pidfile = None, makepidfile = False, daemonargs = None, ssd = "/sbin/start-stop-daemon", ldpath = None, outfile = "/dev/null", *args, **kw):
if not pidfile:
pidfile = "/tmp/" + executable.replace("/", "_") + ".pid"
super(StartStopDaemonController, self).__init__(pidfile = pidfile, *args, **kw)
self.__executable = unicode(executable)
self.__workingdir = workingdir and unicode(workingdir) or None
if ldpath is not None:
if not isinstance(ldpath, (list, set, tuple, frozenset)):
ldpath = [ ldpath ]
ldpath = tuple(set(ldpath))
self.__ldpath = ldpath
self.__makepidfile = makepidfile
self.__daemonargs = daemonargs
self.__fork = fork
self.__ssd = ssd
self.__outfile = outfile
def get_daemonargs(self):
return self.__daemonargs
def set_daemonargs(self, da):
self.__daemonargs = da
daemonargs = property(get_daemonargs, set_daemonargs)
def __make_cmd(self, cmd, test):
cmd = [ self.__ssd, cmd, '-x', self.__executable, '-p', self.pidfile, '-o' ]
if self.__workingdir:
cmd += [ '-d', self.__workingdir ]
if test:
cmd.append('-t')
env = None
if self.__ldpath:
env = dict(LD_LIBRARY_PATH = ':'.join(self.__ldpath))
return cmd, env
def __check_cmd(self, cmd, env):
self.logger.debug("ssd env: " + str(env))
outfile = self.__outfile
if outfile:
outfile = Path(outfile).open("a")
try:
check_call(cmd, stdout = outfile, stderr = STDOUT, close_fds = True, cwd = self.__workingdir, env = env)
finally:
if outfile is not None:
outfile.close()
def _start(self):
cmd, env = self.__make_cmd("-S", False)
if self.__makepidfile:
cmd.append('-m')
if self.__fork:
cmd.append('-b')
if self.__daemonargs:
cmd += [ '--' ] + list(self.__daemonargs)
self.__check_cmd(cmd, env)
def _stop(self):
cmd, env = self.__make_cmd("-K", False)
self.__check_cmd(cmd, env)

View File

@ -1,38 +0,0 @@
from tempfile import mkdtemp as _mkdtemp
from shutil import rmtree
from .. import Base
from futile import noop
class TempDir(Base):
delete_on_error = delete = True
def __init__(self, suffix='', prefix='tmp', dir=None, delete = None, delete_on_error = None, *args, **kw):
super(TempDir, self).__init__(*args, **kw)
self.__name = _mkdtemp(suffix, prefix, dir)
if delete is not None:
self.delete = delete
if delete_on_error is not None:
self.delete_on_error = delete_on_error
@property
def name(self):
return self.__name
def rmtree(self):
rmtree(self.__name)
self.rmtree = noop
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.delete or (exc_type and self.delete_on_error):
self.rmtree()
def __del__(self):
self.__exit__(None, None, None)
def __str__(self):
return self.__name
mkdtemp = TempDir

View File

@ -1,106 +0,0 @@
#! /usr/bin/env python
'''
Created on 01.04.2011
@author: kca
'''
#TODO: proper timeout handling
from __future__ import with_statement
from threading import Lock, Event
from contextlib import contextmanager
class Timeout(Exception):
pass
class ReverseSemaphore(object):
def __init__(self, *args, **kw):
super(ReverseSemaphore, self).__init__(*args, **kw)
self.counter = 0
self.lock = Lock()
self.event = Event()
self.event.set()
pass
def acquire(self):
with self.lock:
self.counter += 1
self.event.clear()
pass
pass
def release(self):
with self.lock:
self.counter -= 1
if self.counter == 0:
self.event.set()
if self.counter < 0:
self.counter = 0
pass
pass
pass
def wait(self):
return self.event.wait()
pass
def __enter__(self):
self.acquire()
pass
def __exit__ (self, type, value, tb):
self.release()
pass
pass
class RWLock(object):
def __init__(self, *args, **kw):
super(RWLock, self).__init__(*args, **kw)
self.write_lock = Lock()
self.read_lock = ReverseSemaphore()
self.write_event = Event()
self.write_event.set()
@contextmanager
def read_transaction(self, timeout = None):
self.read_acquire(timeout = timeout)
try:
yield
finally:
self.read_release()
pass
pass
@contextmanager
def write_transaction(self, timeout = None):
self.write_acquire(timeout = timeout)
try:
yield
finally:
self.write_release()
pass
pass
def read_acquire(self, timeout = None):
self.write_event.wait(timeout = timeout)
if not self.write_event.is_set():
raise Timeout()
self.read_lock.acquire()
return True
def read_release(self):
self.read_lock.release()
pass
def write_acquire(self, timeout = None):
self.write_lock.acquire()
self.write_event.clear()
self.read_lock.wait()
pass
def write_release(self):
self.write_event.set()
self.write_lock.release()

View File

@ -1,18 +0,0 @@
import sys
try:
from threading import current_thread
except ImportError:
from threading import currentThread as current_thread
if sys.version_info < (2, 7):
from threading import _Event
class Event(_Event):
def wait(self, timeout = None):
super(_Event, self).wait(timeout = timeout)
return self.is_set()
else:
from threading import Event

View File

@ -1,28 +0,0 @@
'''
Created on 08.08.2011
@author: kca
'''
from threading import Condition
def synchronized(f):
done = Condition()
f.in_progress = False
def sync(*args, **kw):
done.acquire()
if not f.in_progress:
f.in_progress = True
done.release()
try:
return f(*args, **kw)
finally:
f.in_progress = False
with done:
done.notify_all()
else:
done.wait()
assert(not f.in_progress)
done.release()
return sync

View File

@ -1,19 +0,0 @@
import sys
from traceback import format_exception
def get_traceback(self, exc_info=None):
return ''.join(format_exception(*(exc_info or sys.exc_info())))
def current_stack(skip=0):
try:
1 / 0
except ZeroDivisionError:
f = sys.exc_info()[2].tb_frame
for _ in xrange(skip + 2):
f = f.f_back
lst = []
while f is not None:
lst.append((f, f.f_lineno))
f = f.f_back
return lst

View File

@ -1,9 +0,0 @@
'''
Created on 01.09.2011
@author: kca
'''
from futile.types import AbstractTypeManager
class TypeManager(AbstractTypeManager):
pass

View File

@ -1,52 +0,0 @@
'''
Created on 01.09.2011
@author: kca
'''
import sys
from types import ModuleType
from futile.collections import get_iterable
from ..logging import LoggerMixin
class ImmutableType(type):
def __call__(self, *args, **kw):
if args and isinstance(args[0], self):
return args[0]
return super(ImmutableType, self).__call__(*args, **kw)
class TypeManagerType(LoggerMixin, type):
def __init__(self, *args, **kw):
super(TypeManagerType, self).__init__(*args, **kw)
modname = self.__module__ + "." + self.__name__
if self.__module__ != __name__:
sys.modules[modname] = self
self.__module_name__ = modname
class AbstractTypeManager(LoggerMixin, ModuleType):
__metaclass__ = TypeManagerType
def __init__(self, name = None, *args, **kw):
name = name or str(id(name))
self.modulename = self.__module_name__ + "." + getattr(self, "__prefix__", self.__class__.__name__) + name
sys.modules[self.modulename] = self
def create_type(self, name, base = (), dict = {}, metaclass = type):
try:
existing = getattr(self, name)
if not isinstance(existing, type):
raise ValueError(name)
return existing
except AttributeError:
pass
base = get_iterable(base)
self.logger.debug("Creating %s %s(%s) with %s", metaclass.__name__,
name, base, dict)
dict["__module__"] = self.modulename
type = metaclass(name, base, dict)
setattr(self, name, type)
return type

View File

@ -1,178 +0,0 @@
import logging
from abc import ABCMeta, abstractmethod
from enum import Enum
from futile import NOT_SET, identity
from futile.logging import LoggerMixin
from openmtc.exc import OpenMTCError
class ConfigurationError(OpenMTCError):
pass
class ConfigurationKeyError(KeyError, ConfigurationError):
pass
class ConfigurationAttributeError(AttributeError, ConfigurationError):
pass
class ConfigurationValueError(ValueError, ConfigurationError):
pass
class ExtraOptionsStrategy(Enum):
ignore = "ignore"
warn = "warn"
prune = "prune"
fatal = "fatal"
class ConfigurationOption(LoggerMixin):
__metaclass__ = ABCMeta
def __init__(self, type, default=NOT_SET, converter=identity,
*args, **kw):
super(ConfigurationOption, self).__init__(*args, **kw)
self.type = type
self.default = default
self.converter = converter
def convert(self, v):
if v is None:
if self.default is not NOT_SET:
return self.default
raise ConfigurationValueError("Value must not be None")
v = self._convert(v)
return self.converter(v)
@abstractmethod
def _convert(self, v):
return v
class SimpleOption(ConfigurationOption):
def __init__(self, type=str, default=NOT_SET, converter=identity,
*args, **kw):
super(SimpleOption, self).__init__(type=type, default=default,
converter=converter)
def _convert(self, v):
if isinstance(v, self.type):
return v
return self.type(v)
class ListOption(SimpleOption):
def __init__(self, content_type, type=list, default=NOT_SET,
converter=identity, *args, **kw):
super(ListOption, self).__init__(type=type, default=default,
converter=converter)
self.content_type = content_type
def _convert(self, v):
v = super(ListOption, self)._convert(v)
return map(self._convert_content, v)
def _convert_content(self, v):
if not isinstance(v, self.content_type):
v = self.content_type(v)
return v
class BooleanOption(ConfigurationOption):
def __init__(self, default=NOT_SET, converter=identity, *args, **kw):
super(BooleanOption, self).__init__(type=bool, default=default,
converter=converter)
def _convert(self, v):
if isinstance(v, (bool, int)):
return bool(v)
if isinstance(v, basestring):
return v and v.lower() not in ("0", "no", "n", "f", "false")
raise ConfigurationValueError("Illegal value for boolean: %s" % (v, ))
class EnumOption(SimpleOption):
def _convert(self, v):
try:
return super(EnumOption, self)._convert(v)
except Exception as exc:
try:
return getattr(self.type, v)
except:
raise exc
class LowerCaseEnumOption(EnumOption):
def _convert(self, v):
try:
return super(LowerCaseEnumOption, self)._convert(v)
except Exception as exc:
try:
return getattr(self.type, v.lower())
except:
raise exc
class Configuration(dict):
__options__ = {}
__name__ = "configuration"
__extra_options_strategy__ = ExtraOptionsStrategy.ignore
def __init__(self, *args, **kw):
config = dict(*args, **kw)
options = self.__options__.copy()
for k, v in config.copy().items():
try:
option = options.pop(k)
except KeyError:
strategy = self.__extra_options_strategy__
if strategy == ExtraOptionsStrategy.fatal:
raise ConfigurationError("Unknown configuration key in %s:"
" %s" % (self.__name__, k))
if strategy == ExtraOptionsStrategy.prune:
del config[k]
elif strategy == ExtraOptionsStrategy.warn:
self.logger.warn("Unknown configuration key in %s: %s",
self.__name__, k)
else:
config[k] = option.convert(v)
for k, v in options.items():
if v.default is NOT_SET:
raise ConfigurationKeyError("Missing configuration key in"
" %s: %s" %
(self.__name__, k, ))
config[k] = v.default
super(Configuration, self).__init__(config)
def __getitem__(self, k):
try:
return dict.__getitem__(self, k)
except KeyError:
raise ConfigurationKeyError("Missing configuration key in"
" %s: %s" %
(self.__name__, k, ))
def __getattr__(self, k, default=NOT_SET):
try:
return self[k]
except ConfigurationKeyError as exc:
if default is not NOT_SET:
return default
raise ConfigurationAttributeError(str(exc))
class LogLevel(Enum):
trace = logging.DEBUG
debug = logging.DEBUG
warning = logging.WARNING
error = logging.ERROR
fatal = logging.FATAL

View File

@ -1,13 +0,0 @@
from futile.net.exc import NetworkError
class OpenMTCError(Exception):
pass
class OpenMTCNetworkError(OpenMTCError, NetworkError):
pass
class ConnectionFailed(OpenMTCNetworkError):
pass

View File

@ -1,97 +0,0 @@
from futile.logging import LoggerMixin
from futile import ObjectProxy
from openmtc.model import Collection
from openmtc.mapper.exc import MapperError
class MemberProxy(ObjectProxy):
def __get__(self, instance, owner=None):
if instance is None:
return self._o
if not instance._synced:
if not _is_attached(instance) or self.name not in instance._changes:
instance._mapper._init_resource(instance)
return self._o.__get__(instance, owner)
def __set__(self, instance, value):
if _is_attached(instance):
instance._changes.add(self._o.name)
return self._o.__set__(instance, value)
class MapperCollection(Collection):
def __init__(self, name, type, parent, collection=(), *args, **kw):
super(MapperCollection, self).__init__(name=name, type=type,
parent=parent,
collection=collection, *args,
**kw)
def _handle_newitem(self, item):
if _is_attached(item) or item.path is not None:
raise NotImplementedError()
super(MapperCollection, self)._handle_newitem(item)
self._changes.added.add(item)
if _is_attached(self.parent):
self.parent._changes.collection_changes.add(self.name)
if self.parent.parent is not None:
self.parent.parent._changes.subresource_changes.add(
self.parent.name)
class BasicMapper(LoggerMixin):
def __init__(self, *args, **kw):
super(BasicMapper, self).__init__(*args, **kw)
# self._patch_model()
self._send_request = lambda x: x
def create(self, path, instance):
raise NotImplementedError()
def update(self, instance, fields):
raise NotImplementedError()
def _do_update(self, instance, fields):
raise NotImplementedError()
def get(self, path):
raise NotImplementedError()
def delete(self, instance):
raise NotImplementedError()
def _get_data(self, path):
raise NotImplementedError()
def _map(self, path, typename, data):
raise NotImplementedError()
def _init_resource(self, res):
return self._fill_resource(res, self._get_data(res.path)[1])
def _make_subresource(self, type, path, parent):
subresource = type(path=path, parent=parent)
subresource._synced = False
# return self._attach_instance(subresource)
return subresource
def _fill_resource(self, res, data):
raise NotImplementedError()
@classmethod
def _patch_model(cls):
import openmtc.model as model
model.Resource._synced = True
model.Resource._mapper = None
for t in model.get_types():
if "_initialized" not in t.__dict__:
setattr(t, "_initialized", True)
for a in t.__members__:
# TODO: deal with name differences
setattr(t, a.name, MemberProxy(a))
for a in t.collections:
if a.type is not Collection:
raise NotImplementedError()
a.type = MapperCollection

View File

@ -1,11 +0,0 @@
"""
Created on 02.06.2013
@author: kca
"""
from openmtc.exc import OpenMTCError
class MapperError(OpenMTCError):
pass

View File

@ -1,706 +0,0 @@
from abc import ABCMeta
from collections import Sequence, OrderedDict, Mapping
from datetime import datetime
from enum import Enum
from iso8601 import parse_date, ParseError
from operator import attrgetter
from futile import basestring, issubclass, NOT_SET
from futile.logging import LoggerMixin
from openmtc.model.exc import ModelError, ModelTypeError
class StrEnum(str, Enum):
pass
class Collection(Sequence, Mapping):
def __init__(self, name, type, parent, collection=(), *args, **kw):
super(Collection, self).__init__(*args, **kw)
self._map = OrderedDict()
self.type = type
self.parent = parent
self.name = name
for c in collection:
self.append(c)
def __getitem__(self, index):
if isinstance(index, (int, slice)):
return self._map.values()[index]
return self._map[index]
def __contains__(self, v):
return v in self._map or v in self._map.values()
def append(self, v):
if not isinstance(v, self.type):
raise ModelTypeError(v)
self._handle_newitem(v)
assert v.name is not None, "name is None: %s %s" % (v, v.path)
self._map[v.name] = v
add = append
def get(self, k, default=None):
return self._map.get(k, default)
def __iter__(self):
return self._map.itervalues()
def __len__(self):
return len(self._map)
def __delitem__(self, index):
if isinstance(index, int):
instance = self[index]
index = instance.name
del self._map[index]
discard = __delitem__
def _handle_newitem(self, item):
if item.parent and item.parent is not self.parent:
# TODO !
return
# raise NotImplementedError()
item.parent = self.parent
def __str__(self):
try:
return "openmtc.Collection(%s, %s)" % (
self.name, self._map)
except AttributeError:
return "openmtc.Collection(%s)" % (self.__len__())
class Member(LoggerMixin):
def __init__(self, type=unicode, version="1.0", *args, **kw):
super(Member, self).__init__(*args, **kw)
self.type = type
self.version = version
def _init(self, name):
self.name = name
def __set__(self, instance, value):
if value is not None and not isinstance(value, self.type):
value = self.convert(value, instance)
self.set_value(instance, value)
def set_value(self, instance, value):
setattr(instance, "_" + self.name, value)
def convert(self, value, instance):
try:
return self.type(value)
except (TypeError, ValueError):
raise ModelTypeError("Illegal value for %s (%s): %r" %
(self.name, self.type, value))
def __repr__(self):
return '%s(name="%s", type=%s)' % (type(self).__name__, self.name,
self.type.__name__)
class Attribute(Member):
RW = "RW"
RO = "RO"
WO = "WO"
def __init__(self, type=unicode, default=None,
accesstype=None, mandatory=None,
update_mandatory=None,
id_attribute=None, path_attribute=None,
id_immutable=None, *args, **kw):
super(Attribute, self).__init__(type=type, *args, **kw)
if path_attribute and id_attribute:
raise ModelError("Attribute can't be id_attribute and "
"path_attribute at the same time")
self.default = default
self.id_attribute = id_attribute
self.path_attribute = path_attribute
self.id_immutable = id_immutable
if accesstype is None:
if path_attribute:
accesstype = self.RO
elif id_attribute:
accesstype = self.WO
else:
accesstype = self.RW
self.accesstype = accesstype
if mandatory is None:
if accesstype == self.WO:
mandatory = True
else:
mandatory = False
self.mandatory = mandatory
if update_mandatory is None:
if accesstype == self.RW:
update_mandatory = mandatory
else:
update_mandatory = False
self.update_mandatory = update_mandatory
def __get__(self, instance, owner=None):
if instance is None:
return self
try:
return getattr(instance, "_" + self.name)
except AttributeError:
return self.default
try:
unicode
class UnicodeAttribute(Attribute):
def __init__(self, default=None, accesstype=None,
mandatory=False, *args, **kw):
super(UnicodeAttribute, self).__init__(type=unicode,
default=default,
accesstype=accesstype,
mandatory=mandatory, *args,
**kw)
def convert(self, value, instance):
if isinstance(value, str):
return value.decode("utf-8")
return super(UnicodeAttribute, self).convert(value, instance)
except NameError:
UnicodeAttribute = Attribute
class DatetimeAttribute(Attribute):
def __init__(self, default=None, accesstype=None,
mandatory=False, *args, **kw):
super(DatetimeAttribute, self).__init__(type=datetime,
default=default,
accesstype=accesstype,
mandatory=mandatory, *args,
**kw)
def convert(self, value, instance):
if isinstance(value, basestring):
try:
return parse_date(value)
except ParseError as e:
raise ValueError(str(e))
return super(DatetimeAttribute, self).convert(value, instance)
class ListAttribute(Attribute):
def __init__(self, content_type=unicode, type=list,
default=NOT_SET, *args, **kw):
super(ListAttribute, self).__init__(type=type,
default=default, *args, **kw)
self.content_type = content_type
def __get__(self, instance, owner=None):
if instance is None:
return self
key = "_" + self.name
try:
return getattr(instance, key)
except AttributeError:
if self.default is NOT_SET:
subresource = self.type()
else:
subresource = self.default
setattr(instance, key, subresource)
return subresource
def _convert_mapping(self, value, instance):
self.logger.debug("Creating %s from %s", self.content_type, value)
return self.content_type(**value)
def convert_content(self, value, instance):
if isinstance(value, self.content_type):
return value
if issubclass(self.content_type, Entity):
if isinstance(value, Mapping):
return self._convert_mapping(value, instance)
raise ValueError("Illegal value for sequence '%s' (%s): %s (%s)" %
(self.name, self.content_type, value, type(value)))
return self.content_type(value)
def set_value(self, instance, value):
if value:
value = self.type([self.convert_content(v, instance)
for v in value])
super(ListAttribute, self).set_value(instance, value)
class StringListAttribute(Attribute):
def __init__(self, content_type=unicode, type=list,
default=NOT_SET, *args, **kw):
super(StringListAttribute, self).__init__(type=type, default=default,
*args, **kw)
self.content_type = content_type
def __get__(self, instance, owner=None):
if instance is None:
return self
key = "_" + self.name
try:
return getattr(instance, key)
except AttributeError:
if self.default is NOT_SET:
subresource = self.type()
else:
subresource = self.default
setattr(instance, key, subresource)
return subresource
def convert(self, value, instance):
if isinstance(value, str):
return value.strip(' ').split(' ')
return super(StringListAttribute, self).convert(value, instance)
def _convert_mapping(self, value, instance):
self.logger.debug("Creating %s from %s", self.content_type, value)
return self.content_type(**value)
def convert_content(self, value, instance):
if isinstance(value, self.content_type):
return value
if issubclass(self.content_type, Entity):
if isinstance(value, Mapping):
return self._convert_mapping(value, instance)
raise ValueError("Illegal value for sequence '%s' (%s): %s (%s)" %
(self.name, self.content_type, value, type(value)))
return self.content_type(value)
def set_value(self, instance, value):
if value:
value = self.type([self.convert_content(v, instance)
for v in value])
super(StringListAttribute, self).set_value(instance, value)
class EntityAttribute(Attribute):
def __init__(self, type, default=None, accesstype=None, mandatory=None,
update_mandatory=None):
super(EntityAttribute, self).__init__(type=type, default=default,
accesstype=accesstype,
mandatory=mandatory,
update_mandatory=update_mandatory)
def convert(self, value, instance):
if isinstance(value, Mapping):
self.logger.debug("Creating %s from %s", self.type, value)
return self.type(**value)
return super(EntityAttribute, self).convert(value, instance)
class CollectionMember(Member):
def __init__(self, content_type, type=Collection, *args,
**kw): # TODO: kca: use type for content_type
super(CollectionMember, self).__init__(type=type, *args, **kw)
self.content_type = content_type
def convert(self, value, instance):
try:
return self.type(collection=value, name=self.name,
parent=instance, type=self.content_type)
except:
return super(CollectionMember, self).convert(value, instance)
def __get__(self, instance, owner=None):
if instance is None:
return self
key = "_" + self.name
try:
return getattr(instance, key)
except AttributeError:
subresource = self.type(name=self.name, parent=instance,
type=self.content_type)
setattr(instance, key, subresource)
return subresource
class SubresourceMember(Member):
default = None
def __init__(self, type, virtual=False, default=NOT_SET, *args, **kw):
if type and not issubclass(type, Resource):
raise TypeError(type)
super(SubresourceMember, self).__init__(type=type, *args, **kw)
def __get__(self, instance, owner=None):
if instance is None:
return self
key = "_" + self.name
try:
v = getattr(instance, key)
if v is not None:
return v
except AttributeError:
pass
# Here we automatically create missing subresources
# Might be a stupid idea to do it here
path = instance.path and instance.path + "/" + self.name or None
subresource = self.type(
path=path,
parent=instance
)
# TODO: needs to go into the appropriate resource type(s)
if hasattr(subresource, "creationTime"):
creation_time = instance.creationTime
subresource.creationTime = creation_time
subresource.lastModifiedTime = creation_time
setattr(instance, key, subresource)
return subresource
@property
def virtual(self):
return self.type.virtual
class ResourceType(ABCMeta):
def __init__(self, *args, **kw):
super(ResourceType, self).__init__(*args, **kw)
if ("typename" not in self.__dict__ and
not self.__name__.endswith("Collection")):
self.typename = self.__name__[0].lower() + self.__name__[1:]
self.id_attribute = self.path_attribute = None
attributes = self.attributes = []
subresources = self.subresources = []
collections = self.collections = []
for name in dir(self):
if name[0] != "_":
attr = getattr(self, name)
if isinstance(attr, Member):
if "_" in name:
name = name.replace("_", "-")
setattr(self, name, attr)
attr._init(name)
if isinstance(attr, SubresourceMember):
subresources.append(attr)
elif isinstance(attr, CollectionMember):
collections.append(attr)
else:
attributes.append(attr)
if attr.id_attribute and attr.path_attribute:
raise ModelTypeError(
"Attribute %s of resource %s can only be "
"either id_attribute or path_attribute, not "
"both." % (name, self.__name__))
if attr.id_attribute:
if self.id_attribute is not None:
raise ModelTypeError(
"Resource %s defines more than one id "
"attribute: %s and %s" %
(self.__name__, self.id_attribute, name))
self.id_attribute = attr.name
self.id_immutable = attr.id_immutable
if attr.path_attribute:
if self.path_attribute is not None:
raise ModelTypeError(
"Resource %s defines more than one path "
"attribute: %s and %s" %
(self.__name__, self.id_attribute, name))
self.path_attribute = attr.name
self.__members__ = attributes + subresources + collections
# TODO: caching
@property
def attribute_names(self):
return map(attrgetter("name"), self.attributes)
@property
def collection_names(self):
return map(attrgetter("name"), self.collections)
@property
def subresource_names(self):
return map(attrgetter("name"), self.subresources)
@property
def member_names(self):
return map(attrgetter("name"), self.__members__)
class Entity(LoggerMixin):
__metaclass__ = ResourceType
def __init__(self, *args, **kw):
self.set_values(kw)
def set_values(self, values):
self.logger.debug("Setting values for entity of type %s with %s",
type(self), values)
values = values.copy()
for member in self.__members__:
try:
v = values.pop(member.name)
if (v is not None and isinstance(member, ListAttribute) and
not isinstance(v, (list, tuple, set))):
l = [v]
v = l
setattr(self, member.name, v)
except KeyError:
try:
v = values.pop(member.name + "Reference")
# TODO: proper solution?
if (v is not None and isinstance(member, ListAttribute) and
not isinstance(v, (list, tuple, set))):
v = v.values()[0]
setattr(self, member.name, v)
except KeyError:
pass
if values:
self._set_extra_values(values)
def _set_extra_values(self, values):
"""
names = type(self).subresource_names
for k in values.keys():
if k.strip("Reference") in names:
values.pop(k)
print names, values
from traceback import print_stack
print_stack()
"""
if values:
raise ModelTypeError("%s resource has no attribute %s" %
(self.typename, values.keys()[0]))
@classmethod
def get_typename(cls):
return cls.typename
def get_attribute_values(self, filter=False):
vals = {}
for attr in self.attributes:
a_name = attr.name
val = getattr(self, a_name)
if (val is None or val == '' or val == []) and filter:
continue
vals[a_name] = val
return vals
attribute_values = property(get_attribute_values)
def get_values_representation(self, fields=None, internal=False):
vals = {}
id_attribute = self.id_attribute
for attr in self.attributes:
a_name = attr.name
if (fields is None or a_name == id_attribute or a_name in fields) \
and (internal or attr.accesstype is not None):
val = getattr(self, "_" + a_name, None)
if val is None:
continue
if isinstance(attr, ListAttribute):
# TODO: return simple values. No representation
if attr.content_type is AnyURI: # any uri list
vals[a_name] = {"reference": val}
elif issubclass(attr.content_type, Entity): # complex list
vals[a_name] = {
a_name: [x.get_values_representation() for x in val]
}
else: # simple list
vals[a_name] = {a_name[:-1]: val}
elif isinstance(attr, EntityAttribute):
vals[a_name] = val.values
else:
try:
val = val.isoformat()
except AttributeError:
pass
vals[a_name] = val
return vals
def get_values(self, filter=False):
return self.get_attribute_values(filter)
@property
def values(self):
return self.get_values()
@property
def subresource_values(self):
vals = {}
for attr in self.subresources:
vals[attr.name] = getattr(self, attr.name)
return vals
class ContentResource(Entity):
virtual = True
__model_name__ = None
__model_version__ = None
def __init__(self, value, *args, **kw):
kw = {'CONTENT': value}
super(ContentResource, self).__init__(*args, **kw)
@property
def values(self):
return self.get_values().get('CONTENT')
class Resource(Entity):
virtual = False
__model_name__ = None
__model_version__ = None
def __init__(self, path=None, parent=None, *args, **kw):
if path is not None and not isinstance(path, basestring):
raise TypeError(path)
self.__path = path
self.parent = parent
super(Resource, self).__init__(*args, **kw)
def get_path(self):
return self.__path
def set_path(self, path):
self.__path = path
if self.id_attribute and getattr(self, self.id_attribute) is None:
setattr(self, self.id_attribute, path.rpartition("/")[-1])
if self.path_attribute and getattr(self, self.path_attribute) is None:
setattr(self, self.path_attribute, path)
path = property(get_path, set_path)
@property
def parent_path(self):
if self.__path is not None:
return self.__path.rpartition("/")[0]
# TODO: deprecated
@property
def name(self):
return self.basename
@property
def basename(self):
if self.path is not None:
return self.path.rpartition("/")[-1]
if self.id_attribute is not None:
return getattr(self, self.id_attribute)
def set_values(self, values):
values = values.copy()
keys = [k for k in values.keys() if "_" in k]
for k in keys:
values[k.replace("_", "-")] = values.pop(k)
path = self.path
if path is not None:
id_attribute = self.id_attribute
if (id_attribute is not None and
id_attribute not in values):
values[id_attribute] = path.rpartition("/")[-1]
path_attribute = self.path_attribute
if (path_attribute is not None and
path_attribute not in values):
values[path_attribute] = path
for member in self.__members__:
try:
v = values.pop(member.name)
# FIXME: move into de-serializer and handle dicts
if (v is not None and isinstance(member, ListAttribute) and
not isinstance(v, (list, tuple, set))):
v = v.values()[0]
setattr(self, member.name, v)
except KeyError:
try:
v = values.pop(member.name + "Reference")
# TODO: proper solution?
if (v is not None and isinstance(member, ListAttribute) and
not isinstance(v, (list, tuple, set))):
v = v.values()[0]
setattr(self, member.name, v)
except KeyError:
pass
if values:
self._set_extra_values(values)
def __repr__(self):
return "%s(path='%s', name='%s')" % (type(self).__name__, self.path,
self.name)
def __eq__(self, o):
try:
return self.path == o.path
except AttributeError:
return False
def __ne__(self, o):
return not self.__eq__(o)
class FlexibleAttributesMixin(object):
def __init__(self, path=None, parent=None, *args, **kw):
self._flex_attrs = set()
super(FlexibleAttributesMixin, self).__init__(path=path, parent=parent,
*args, **kw)
def __setattr__(self, k, v):
if not k.startswith("_") and not hasattr(self, k) and k != "parent":
self._flex_attrs.add(k)
return super(FlexibleAttributesMixin, self).__setattr__(k, v)
def __delattr__(self, k):
self._flex_attrs.discard(k)
return super(FlexibleAttributesMixin, self).__delattr__(k)
@property
def flex_values(self):
return {k: getattr(self, k) for k in self._flex_attrs}
def get_values(self, filter=False):
vals = super(FlexibleAttributesMixin, self).get_values(filter)
vals.update(self.flex_values)
return vals
def get_values_representation(self, fields=None, internal=False):
r = super(FlexibleAttributesMixin, self) \
.get_values_representation(fields=fields, internal=internal)
if fields is None:
r.update(self.flex_values)
return r
def _set_extra_values(self, values):
for k, v in values.items():
setattr(self, k, v)
class AnyURI(str):
pass
class AnyURIList(Entity):
reference = ListAttribute(mandatory=False)

View File

@ -1,14 +0,0 @@
'''
Created on 26.05.2013
@author: kca
'''
from openmtc.exc import OpenMTCError
class ModelError(OpenMTCError):
pass
class ModelTypeError(ModelError, TypeError):
pass

View File

@ -1,39 +0,0 @@
from datetime import datetime, timedelta, tzinfo
import time
ZERO = timedelta(0)
class Utc(tzinfo):
"""UTC
"""
__slots__ = ()
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
UTC = Utc()
#del Utc
def datetime_now():
return datetime.now(UTC)
def datetime_the_future(offset = 0):
""" Returns a datetime instance <offset> seconds in the future.
@note: if no offset is provided or offset == 0, this is equivalent to datetime_now
@param offset: seconds from now
@return: datetime in <offset> seconds
"""
f = time.time() + offset
return datetime.fromtimestamp(f, UTC)

View File

@ -1 +0,0 @@
VERSION="4.0.0"

View File

@ -1,2 +0,0 @@
class OpenMTCAppError(Exception):
pass

View File

@ -1,89 +0,0 @@
from signal import SIGTERM, SIGINT
from flask import (Flask, request, abort, redirect, url_for,
Response as FlaskResponse)
from gevent import signal as gevent_signal
from gevent.pywsgi import WSGIServer
from geventwebsocket.handler import WebSocketHandler
from socketio import Server as SioServer, Middleware as SioMiddleware
from futile.net.http.exc import HTTPError
from openmtc_app.runner import AppRunner
class Response(FlaskResponse):
pass
class SimpleFlaskRunner(AppRunner):
def __init__(self, m2m_app, port=None, listen_on="0.0.0.0", *args, **kw):
super(SimpleFlaskRunner, self).__init__(m2m_app=m2m_app, *args, **kw)
self.port = port or 5050
self.listen_on = listen_on
self.flask_app = Flask(type(self.m2m_app).__module__)
def _get_server(self):
return WSGIServer((self.listen_on, self.port), self.flask_app)
def _run(self):
self.m2m_app.run(self, self.m2m_ep)
_server = self._get_server()
self.logger.debug("Serving on %s:%s", self.listen_on, self.port)
gevent_signal(SIGTERM, _server.stop)
gevent_signal(SIGINT, _server.stop)
_server.serve_forever()
def add_route(self, route, handler, methods=("POST", "GET")):
def wrapper():
try:
return handler(request)
except HTTPError as e:
self.logger.exception("Aborting")
abort(e.status)
self.logger.debug("Adding route: %s -> %s" % (route, handler))
self.flask_app.add_url_rule(route, view_func=wrapper,
endpoint=route + str(handler),
methods=methods)
class FlaskRunner(SimpleFlaskRunner):
def __init__(self, m2m_app, port=None, listen_on="0.0.0.0", *args, **kw):
super(FlaskRunner, self).__init__(m2m_app=m2m_app, port=port,
listen_on=listen_on, *args, **kw)
@self.flask_app.route("/")
def home():
return redirect(url_for('static', filename='index.html'))
self.sio_app = SioServer(async_mode='gevent')
@self.sio_app.on('connect')
def connect(sid, environ):
self.logger.debug('client connected: %s' % sid)
def _get_server(self):
return WSGIServer((self.listen_on, self.port),
SioMiddleware(self.sio_app, self.flask_app),
handler_class=WebSocketHandler)
def emit(self, event, message=None, sid=None):
self.sio_app.emit(event, message, room=sid)
def get_handler_decorator(self, name):
return self.sio_app.on(name)
def add_message_handler(self, name, handler, client=False, response=False):
def wrapper(*args, **kw):
if not client:
args = args[1:]
if response:
return handler(*args, **kw)
else:
handler(*args, **kw)
self.sio_app.on(name, wrapper)

View File

@ -1,276 +0,0 @@
from gevent import spawn
from gevent.pywsgi import WSGIServer
from inspect import getargspec
from futile.logging import LoggerMixin
from openmtc_onem2m.exc import OneM2MError
from openmtc_onem2m.model import (
EventNotificationCriteria,
NotificationEventTypeE,
Subscription,
)
from openmtc_onem2m.serializer import get_onem2m_decoder
from urlparse import urlparse
from openmtc_onem2m.util import split_onem2m_address
_handler_map = {}
def register_handler(cls, schemes=()):
_handler_map.update({
scheme: cls for scheme in map(str.lower, schemes)
})
def get_handler(scheme, poa, callback_func, ssl_certs=None):
return _handler_map[scheme](poa, callback_func, ssl_certs)
class NotificationManager(LoggerMixin):
handlers = []
endpoints = []
callbacks = {}
def __init__(self, poas, ep, onem2m_mapper, ca_certs=None, cert_file=None, key_file=None):
"""
:param list poas:
:param str ep:
:param openmtc_onem2m.mapper.OneM2MMapper onem2m_mapper:
"""
self.mapper = onem2m_mapper
self.sp_id, self.cse_id, _ = split_onem2m_address(onem2m_mapper.originator)
self.ssl_certs = {
'ca_certs': ca_certs,
'cert_file': cert_file,
'key_file': key_file
}
for poa in map(urlparse, poas):
if poa.hostname == 'auto':
poa = poa._replace(netloc="%s:%s" % (self._get_auto_host(ep), poa.port))
if not poa.scheme:
poa = poa._replace(scheme='http')
try:
self.handlers.append(get_handler(poa.scheme, poa, self._handle_callback,
self.ssl_certs))
self.endpoints.append(poa.geturl())
except:
pass
self.logger.debug('Available POAs: %s' % ', '.join(self.endpoints))
super(NotificationManager, self).__init__()
@staticmethod
def _get_auto_host(ep):
try:
import socket
from urlparse import urlparse
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
netloc = urlparse(ep).netloc.split(':')
s.connect((netloc[0], int(netloc[1])))
host = s.getsockname()[0]
s.close()
except:
host = "127.0.0.1"
return host
def _normalize_path(self, path):
path = path[len(self.sp_id):] if path.startswith(self.sp_id) and self.sp_id else path
path = path[len(self.cse_id) + 1:] if path.startswith(self.cse_id) and self.cse_id else path
return path
def _init(self):
for handler in self.handlers:
try:
handler.start()
except:
pass
def nop():
pass
self._init = nop
def register_callback(self, func, sur):
self.callbacks[sur] = func if len(getargspec(func)[0]) > 1 \
else lambda _, **notification: func(notification['rep'])
def _handle_callback(self, originator, **notification):
sur = notification.pop('sur')
sur = self._normalize_path(sur)
try:
callback = self.callbacks[sur]
except KeyError:
if not sur.startswith('/'):
# TODO(rst): maybe not the best, check alternatives
# assumes originator is always in the form //SP-ID/CSE-ID
sur = originator[originator.rfind('/'):] + '/' + sur
try:
callback = self.callbacks[sur]
except KeyError:
return
else:
return
try:
spawn(callback, originator, **notification)
except:
pass
def get_expiration_time(self):
return None
def subscribe(self, path, func, filter_criteria=None, expiration_time=None,
notification_types=(NotificationEventTypeE.updateOfResource, )):
self._init()
event_notification_criteria = filter_criteria or EventNotificationCriteria()
event_notification_criteria.notificationEventType = (
event_notification_criteria.notificationEventType or list(notification_types))
subscription = self.mapper.create(path, Subscription(
notificationURI=[self.mapper.originator],
expirationTime=expiration_time or self.get_expiration_time(),
eventNotificationCriteria=event_notification_criteria,
))
reference = self._normalize_path(subscription.subscriberURI or subscription.path)
self.register_callback(func, reference)
return subscription
def unsubscribe(self, sur):
self.mapper.delete(sur)
del self.callbacks[sur]
def shutdown(self):
for subscription in self.callbacks.keys():
try:
self.unsubscribe(subscription)
except OneM2MError:
pass
for handler in self.handlers:
try:
handler.stop()
except:
pass
class BaseNotificationHandler(object):
def __init__(self, poa, callback_func, ssl_certs=None):
self._endpoint = poa
self._callback = callback_func
self._ssl_certs = ssl_certs
@classmethod
def _unpack_notification(cls, notification):
return {
'sur': notification.subscriptionReference,
'net': notification.notificationEvent.notificationEventType,
'rep': notification.notificationEvent.representation,
}
def start(self):
raise NotImplementedError
def stop(self):
pass
class MqttNotificationHandler(BaseNotificationHandler):
_client = None
def start(self):
from openmtc_onem2m.client.mqtt import get_client
from openmtc_onem2m.transport import OneM2MResponse
from openmtc_onem2m.exc import get_response_status
def wrapper(request):
notification = self._unpack_notification(request.content)
self._callback(request.originator, **notification)
return OneM2MResponse(status_code=get_response_status(2002), request=request)
self._client = get_client(self._endpoint.geturl(), handle_request_func=wrapper)
def stop(self):
self._client.stop()
register_handler(MqttNotificationHandler, ('mqtt', 'mqtts', 'secure-mqtt'))
class HttpNotificationHandler(BaseNotificationHandler):
server = None
def __init__(self, poa, callback_func, ssl_certs=None):
super(HttpNotificationHandler, self).__init__(poa, callback_func, ssl_certs)
self.ca_certs = ssl_certs.get('ca_certs')
self.cert_file = ssl_certs.get('cert_file')
self.key_file = ssl_certs.get('key_file')
# TODO(rst): maybe tis needs to be tested when the server is started
if poa.scheme == 'https' and not (self.ca_certs and self.cert_file and self.key_file):
raise Exception()
def start(self):
from flask import (
Flask,
request,
Response,
)
from gevent import signal as gevent_signal
from signal import (
SIGTERM,
SIGINT,
)
app = Flask(__name__)
@app.after_request
def attach_headers(response):
response.headers['x-m2m-ri'] = request.headers['x-m2m-ri']
return response
@app.route('/', methods=['POST'])
def index():
assert 'x-m2m-origin' in request.headers, 'No originator set'
assert 'x-m2m-ri' in request.headers, 'Missing request id'
assert 'content-type' in request.headers, 'Unspecified content type'
notification = self._unpack_notification(get_onem2m_decoder(request.content_type).decode(request.data))
self._callback(request.headers['x-m2m-origin'], **notification)
return Response(
headers={
'x-m2m-rsc': 2000,
},
)
if self._endpoint.scheme == 'https':
self.server = WSGIServer(
(
self._endpoint.hostname,
self._endpoint.port or 6050
),
application=app,
keyfile=self.key_file, certfile=self.cert_file, ca_certs=self.ca_certs
)
else:
self.server = WSGIServer(
(
self._endpoint.hostname,
self._endpoint.port or 6050
),
application=app,
)
gevent_signal(SIGINT, self.server.stop)
gevent_signal(SIGTERM, self.server.stop)
spawn(self.server.serve_forever)
def stop(self):
self.server.stop()
register_handler(HttpNotificationHandler, ('http', 'https'))

View File

@ -1,744 +0,0 @@
from base64 import (
b64decode,
b64encode,
)
from datetime import datetime
from gevent import (
spawn,
spawn_later,
)
from iso8601 import parse_date
from json import (
dumps as json_dumps,
loads as json_loads,
)
from futile.logging import LoggerMixin
import logging
from openmtc.util import (
UTC,
datetime_now,
datetime_the_future,
)
from openmtc_app.flask_runner import FlaskRunner
from openmtc_app.notification import NotificationManager
from openmtc_onem2m.exc import (
CSENotFound,
CSENotImplemented,
STATUS_CONFLICT,
)
from openmtc_onem2m.mapper import OneM2MMapper
from openmtc_onem2m.model import (
AE,
Container,
ContentInstance,
EncodingTypeE,
get_short_member_name,
NotificationEventTypeE,
EventNotificationCriteria)
from openmtc_onem2m.serializer import get_onem2m_decoder
from openmtc_onem2m.transport import OneM2MErrorResponse
import time
import re
from urllib import urlencode
logging.getLogger("iso8601").setLevel(logging.ERROR)
# fix missing SSLv3
try:
from gevent.ssl import PROTOCOL_SSLv3
except ImportError:
import gevent.ssl
gevent.ssl.PROTOCOL_SSLv3 = gevent.ssl.PROTOCOL_TLSv1
class XAE(LoggerMixin):
""" Generic OpenMTC application class.
Implements functionality common to all typical OpenMTC applications.
"""
# TODO(rst): add more features
# support several AEs using the same App-ID and appName
name = None
containers = ()
labels = ()
# default_access_right = True
default_lifetime = 3600
max_nr_of_instances = 3
resume_registration = remove_registration = True
notification_handlers = {}
mapper = None
notification_manager = None
__app = None
def __init__(self, name=None, cse_base=None, expiration_time=None, announce_to=None, poas=None,
originator_pre=None, ca_certs=None, cert_file=None, key_file=None, *args, **kw):
super(XAE, self).__init__(*args, **kw)
self.__subscriptions = []
self.name = name or type(self).__name__
self.cse_base = cse_base or "onem2m"
ae_id = "C" + self.name
self.originator = (originator_pre + '/' + ae_id) if originator_pre else ae_id
self.ca_certs = ca_certs
self.cert_file = cert_file
self.key_file = key_file
if expiration_time is not None:
if isinstance(expiration_time, (str, unicode)):
expiration_time = parse_date(expiration_time)
elif isinstance(expiration_time, (int, float)):
expiration_time = datetime.fromtimestamp(expiration_time, UTC)
if not isinstance(expiration_time, datetime):
raise ValueError(expiration_time)
self.default_lifetime = (expiration_time - datetime_now()).total_seconds()
self.announceTo = announce_to
self.__resumed_registration = False
self.__known_containers = set()
self.__shutdown = False
self.allow_duplicate = None
self.runner = None
self.poas = poas or []
self.fmt_json_regex = re.compile(r'^application/(?:[^+]+\+)?json$', re.IGNORECASE)
self.fmt_xml_regex = re.compile(r'^application/(?:[^+]+\+)?xml$', re.IGNORECASE)
def get_expiration_time(self):
if self.default_lifetime is None:
return None
return datetime_the_future(self.default_lifetime)
@property
def application(self):
return self.__app
def run(self, runner, cse, allow_duplicate=True):
self.mapper = OneM2MMapper(cse, originator=self.originator, ca_certs=self.ca_certs,
cert_file=self.cert_file, key_file=self.key_file)
self.notification_manager = NotificationManager(self.poas, cse, self.mapper,
ca_certs=self.ca_certs,
cert_file=self.cert_file,
key_file=self.key_file)
self.allow_duplicate = allow_duplicate
self.runner = runner
self.register()
def shutdown(self):
""" Graceful shutdown.
Deletes all Applications and Subscriptions.
"""
try:
self._on_shutdown()
except:
self.logger.exception("Error in shutdown handler")
self.logger.debug("shutdown handler is finished")
self.__shutdown = True
self.notification_manager.shutdown()
self._remove_apps()
def _remove_apps(self):
if self.remove_registration:
try:
if self.__app:
self.mapper.delete(self.__app)
except:
pass
self.logger.debug("app deleted")
@staticmethod
def run_forever(period=1000, func=None, *args, **kw):
""" executes a given function repeatingly at a given interval
:param period: (optional) frequency of repeated execution (in Hz)
:param func: (optional) function to be executed
"""
if func is None:
def func(*_):
pass
def run_periodically():
func(*args, **kw)
spawn_later(period, run_periodically)
return spawn(run_periodically)
def periodic_discover(self, path, fc, interval, cb, err_cb=None):
""" starts periodic discovery at a given frequency
:param path: start directory inside cse for discovery
:param fc: filter criteria (what to discover)
:param interval: frequency of repeated discovery (in Hz)
:param cb: callback function to return the result of the discovery to
:param err_cb: (optional) callback function for errors to return the error of the discovery to
"""
if not isinstance(fc, dict):
fc = {}
def run_discovery(o):
try:
cb(self.discover(path, o))
except OneM2MErrorResponse as error_response:
if err_cb:
return err_cb(error_response)
else:
o['createdAfter'] = datetime_now()
spawn_later(interval, run_discovery, o)
return spawn(run_discovery, fc)
def register(self):
""" Registers the Application with the CSE. """
self.logger.info("Registering application as %s." % (self.name,))
try:
poa = self.notification_manager.endpoints
except AttributeError:
poa = []
app = AE(resourceName=self.name, labels=list(self.labels),
pointOfAccess=poa)
app.announceTo = self.announceTo
app.requestReachability = bool(poa)
try:
registration = self.create_application(app)
except OneM2MErrorResponse as error_response:
if error_response.response_status_code is STATUS_CONFLICT:
registration = self._handle_registration_conflict(app)
if not registration:
raise
else:
self.logger.error('Error at start up')
self.logger.error(error_response.response_status_code)
raise SystemExit
self.__app = registration
assert registration.path
try:
self._on_register()
except (KeyboardInterrupt, SystemExit):
raise
except:
self.logger.exception("Error on initialization")
raise
def _handle_registration_conflict(self, app):
if not self.resume_registration:
return None
# TODO(rst): update app here for expiration_time and poas
app = self.get_application(app)
self.__start_refresher(app)
self.__resumed_registration = True
return app
def emit(self, event, message=None):
""" Websocket emit. """
if not isinstance(self.runner, FlaskRunner):
raise RuntimeError('Runner is not supporting emit!')
self.runner.emit(event, message)
def _on_register(self):
pass
def _on_shutdown(self):
pass
def get_application(self, application, path=None):
""" Retrieves an Application resource.
:param application: old app instance or appId
:param path: (optional) path in the resource tree
"""
if path is None:
# FIXME(rst): use app path and not cse base path
path = self.cse_base
if not isinstance(application, AE):
application = AE(resourceName=application)
name = application.resourceName
path = "%s/%s" % (path, name) if path else name
app = self.mapper.get(path)
self.logger.debug("retrieved app: %s" % app)
return app
def create_application(self, application, path=None):
""" Creates an Application resource.
:param application: Application instance or appId as str
:param path: (optional) path in the resource tree
"""
# TODO(rst): set app_id via config
# TODO(rst): set requestReachability based on used runner
if path is None:
path = self.cse_base
def restore_app(app):
self.logger.warn("Restoring app: %s", app.path)
app.expirationTime = None
self.create_application(app, path=path)
if not isinstance(application, AE):
application = AE(resourceName=application, App_ID='dummy', requestReachability=False)
else:
if not application.App_ID:
application.App_ID = 'dummy'
if not application.requestReachability:
application.requestReachability = False
application.expirationTime = application.expirationTime or self.get_expiration_time()
app = self.mapper.create(path, application)
self.logger.debug("Created application at %s", app.path)
app = self.get_application(application, path)
assert app.path
self.__start_refresher(app, restore=restore_app)
self.logger.info("Registration successful: %s." % (app.path,))
# TODO(rst): handle when ACP is reimplemented
# if accessRight:
# if not isinstance(accessRight, AccessRight):
# accessRight = AccessRight(
# id="ar",
# selfPermissions={"permission": [{
# "id": "perm",
# "permissionFlags": {
# "flag": ["READ", "WRITE", "CREATE", "DELETE"]
# },
# "permissionHolders": {
# "all": "all"
# }
# }]},
# permissions={"permission": [{
# "id": "perm",
# "permissionFlags": {
# "flag": ["READ", "WRITE", "CREATE", "DELETE"]
# },
# "permissionHolders": {
# "all": "all"
# }
# }]}
# )
# accessRight = self.create_accessRight(app, accessRight)
#
# app.accessRightID = accessRight.path
#
# self.mapper.update(app, ("accessRightID",))
return app
# TODO(rst): use FilterCriteria from model and convert
def discover(self, path=None, filter_criteria=None, unstructured=False):
""" Discovers Container resources.
:param path: (optional) the target path to start the discovery
:param filter_criteria: (optional) FilterCriteria for the for the discovery
:param unstructured: (optional) set discovery_result_type
"""
if path is None:
path = self.cse_base
# TODO(rst): use filter_criteria from model
if not filter_criteria:
filter_criteria = {}
path += "?fu=1"
if filter_criteria:
path += "&" + urlencode(
{
get_short_member_name(k): v for k, v in filter_criteria.iteritems()
},
True
)
path += '&drt' + str(1 if unstructured else 2)
discovery = self.mapper.get(path)
return discovery.CONTENT
def create_container(self, target, container, labels=None, max_nr_of_instances=None):
""" Creates a Container resource.
:param target: the target resource/path parenting the Container
:param container: the Container resource or a valid container ID
:param labels: (optional) the container's labels
:param max_nr_of_instances: (optional) the container's maximum number
of instances (0=unlimited)
"""
def restore_container(c):
self.logger.warn("Restoring container: %s", c.path)
c.expirationTime = None
self.__known_containers.remove(c.path)
self.create_container(target, c, labels=labels)
if target is None:
target = self.__app
if not isinstance(container, Container):
container = Container(resourceName=container)
# if we got max instances..set them
if max_nr_of_instances:
container.maxNrOfInstances = max_nr_of_instances
# if we did not set max instances yet, set them
else:
container.maxNrOfInstances = self.max_nr_of_instances
if container.expirationTime is None:
container.expirationTime = self.get_expiration_time()
if labels:
container.labels = labels
path = getattr(target, "path", target)
try:
container = self.mapper.create(path, container)
except OneM2MErrorResponse as error_response:
if error_response.response_status_code is STATUS_CONFLICT:
c_path = path + '/' + container.resourceName
container.path = c_path
if (self.__resumed_registration and
c_path not in self.__known_containers):
container = self.mapper.update(container)
else:
raise error_response
else:
raise error_response
self.__known_containers.add(container.path)
self.__start_refresher(container, restore=restore_container)
self.logger.info("Container created: %s." % (container.path,))
return container
# TODO(rst): handle when ACP is reimplemented
# def create_access_right(self, application, accessRight):
# """ Creates an AccessRight resource.
#
# :param application: the Application which will contain the AR
# :param accessRight: the AccessRight instance
# """
# self.logger.debug("Creating accessRight for %s", application)
#
# if application is None:
# application = self.__app
# assert application.path
#
# path = getattr(application, "path", application)
#
# if not path.endswith("/accessRights"):
# path += "/accessRights"
#
# accessRight = self.mapper.create(path, accessRight)
# accessRight = self.mapper.get(accessRight.path)
# self.__start_refresher(accessRight, extra_fields=["selfPermissions"])
# self.logger.info("accessRight created: %s." % (accessRight.path,))
# return accessRight
#
# create_accessRight = create_access_right
def get_resource(self, path, app_local=False):
if app_local:
path = self.__app.path + '/' + path
if not path:
return None
try:
return self.mapper.get(path)
except OneM2MErrorResponse:
return None
def push_content(self, container, content, fmt=None, text=None):
""" Creates a ContentInstance resource in the given container,
wrapping the content.
Defaults to serialising the content as JSON and base64 encodes it.
NOTE: Will attempt to create the container, if not found.
:param container: Container object or container path string
:param content: the content data
:param fmt:
:param text:
"""
path = getattr(container, "path", container)
if isinstance(content, (str, unicode)):
fmt = 'text/plain' if fmt is None else fmt
text = True if text is None else text
elif isinstance(content, (dict, list)):
fmt = 'application/json' if fmt is None else fmt
text = False if text is None else text
else:
raise CSENotImplemented("Only dict, list and str are supported!")
if re.search(self.fmt_json_regex, fmt):
if text:
# TODO(rst): check if it should be with masked quotation marks
con = json_dumps(content)
cnf = fmt + ':' + str(EncodingTypeE.plain.value)
# raise CSENotImplemented("Only json as b64 is supported!")
else:
con = b64encode(json_dumps(content))
cnf = fmt + ':' + str(EncodingTypeE.base64String.value)
elif fmt == 'text/plain':
if text:
con = content
cnf = fmt + ':' + str(EncodingTypeE.plain.value)
else:
con = b64encode(content)
cnf = fmt + ':' + str(EncodingTypeE.base64String.value)
else:
# TODO(rst): add handling of other formats or raise not implemented
raise CSENotImplemented("Only json and text are supported!")
return self.mapper.create(path, ContentInstance(
content=con,
contentInfo=cnf,
))
@staticmethod
def _get_content_from_cin(cin):
if isinstance(cin, ContentInstance):
# TODO(rst): handle contentInfo and decode
# resource.contentInfo -> application/json:1
# media, encodingType = split(':')
# encodingType = 1 -> base64.decodeString(resource.content)
# encodingType = 2 -> not supported
media_type, encoding_type = cin.contentInfo.split(':')
content = cin.content
try:
if int(encoding_type) == EncodingTypeE.base64String:
content = b64decode(content)
if media_type == 'application/json':
content = json_loads(content)
except ValueError:
pass
return content
return cin
def get_content(self, container):
""" Retrieve the latest ContentInstance of a Container.
:param container: Container object or container path string
"""
return self._get_content_from_cin(
self.mapper.get(
getattr(container, 'path', container) + '/latest'
)
)
def _get_notification_data(self, data, content_type):
try:
return get_onem2m_decoder(content_type).\
decode(data).\
notificationEvent.\
representation
# serializer = get_onem2m_decoder(content_type)
# notification = serializer.decode(data)
# resource = notification.notificationEvent.representation
# return resource
except (KeyError, TypeError, ValueError, IndexError):
self.logger.error("Failed to get notification data from %s" % data)
return None
def _remove_route(self, route):
self.logger.debug("removing route: %s", route)
self.runner.flask_app.url_map._rules = filter(
lambda x: x.rule != route,
self.runner.flask_app.url_map._rules
)
def _add_subscription(self, path, _, handler, delete_handler, filter_criteria=None, expiration_time=None):
params = {
'filter_criteria': filter_criteria,
'expiration_time': expiration_time,
}
self.add_subscription_handler(path, handler, **params)
# self.notification_manager.subscribe(path, handler, **params)
if delete_handler:
params['types'] = (NotificationEventTypeE.deleteOfResource,)
self.add_subscription_handler(path, delete_handler, **params)
def add_subscription(self, path, handler, delete_handler=None):
""" Creates a subscription resource at path.
And registers handler to receive notification data.
:param path: path to subscribe to
:param handler: notification handler
:param delete_handler: reference to delete handling function
"""
self._add_subscription(path, None, handler, delete_handler)
def add_subscription_handler(self, path, handler, types=(NotificationEventTypeE.updateOfResource, ),
filter_criteria=None, expiration_time=None):
"""
:param path:
:param handler:
:param types:
:param filter_criteria:
:param expiration_time:
:return:
"""
def subscribe():
return self.notification_manager.subscribe(
path,
handler,
notification_types=types,
filter_criteria=filter_criteria,
expiration_time=expiration_time
)
subscription = subscribe()
def restore_subscription():
# called to recreate the subscription
# for some reason subscription is not assigned here,
# so we make it a parameter
self.logger.warn("Restoring subscription: %s", subscription.name)
self.notification_manager.unsubscribe(subscription.subscriberURI or subscription.path)
subscribe()
# refresh expirationTime regularly
# TODO(sho): This should rather be handled through the notification manager itself
self.__start_refresher(subscription, restore=restore_subscription)
return subscription
def add_container_subscription(self, container, handler,
delete_handler=None, filter_criteria=None):
""" Creates a Subscription to the ContentInstances of the given
Container.
:param container: Container object or container path string
:param handler: reference of the notification handling function
:param delete_handler: reference to delete handling function
:param filter_criteria: (optional) FilterCriteria for the subscription
"""
path = getattr(container, "path", container)
# check if target is container
if not isinstance(self.mapper.get(path), Container):
raise RuntimeError('Target is not a container.')
# event notification criteria
filter_criteria = filter_criteria or EventNotificationCriteria()
filter_criteria.notificationEventType = list([
NotificationEventTypeE.createOfDirectChildResource,
])
def content_handler(cin):
handler(path, self._get_content_from_cin(cin))
self._add_subscription(
path,
None,
content_handler,
delete_handler,
filter_criteria
)
def __start_refresher(self, instance, extra_fields=(), restore=None):
""" Starts a threading.Timer chain,
to repeatedly update a resource instance's expirationTime.
NOTE: instance.expirationTime should already be set and the instance
created.
:param instance: resource instance
:param extra_fields: additional fields, needed in the update request
:param restore: function that will restore the instance, if it has
expired accidentally. Has to restart the refresher.
"""
if not instance.expirationTime:
return
interval = time.mktime(instance.expirationTime.timetuple()) - (time.time() + time.timezone)
if interval > 120:
interval -= 60
else:
interval = max(1, interval * 0.75)
self.logger.debug("Will update expiration time of %s in %s seconds", instance, interval)
self.runner.set_timer(interval, self.__update_exp_time, instance=instance, extra_fields=extra_fields, restore=restore)
def start_refresher(self, instance, extra_fields=(), restore=None):
self.__start_refresher(instance, extra_fields=extra_fields, restore=restore)
def __update_exp_time(self, instance=None, the_future=None, extra_fields=(),
interval=None, offset=None, restore=None):
""" Updates a resource instance's expirationTime to the_future
or a default value sometime in the future.
:note: If instance is not provided or None or False, self.__app is
updated.
:note: Starts a new Timer.
:param instance: resource instance to update
:param the_future: new expirationTime value
:param extra_fields: additional fields, needed in the update request
:param interval: update interval
:param offset: expirationTime offset (should be >0)
:param restore: function that will restore the instance, if it has
expired accidentally. Has to restart the refresher.
:raise CSENotFound: If the instance could not be found and no restore
was provided.
"""
self.logger.debug("updating ExpirationTime of %s", instance)
if self.__shutdown:
# not sure this ever happens.
return
interval = interval or 60 * 10 # TODO make configurable
offset = offset or 60 * 10 # 10min default
if not the_future:
the_future = datetime.utcfromtimestamp(time.time() + interval + offset)
fields = ["expirationTime"]
fields.extend(extra_fields)
if not instance:
# does this happen if the instance was deleted?
instance = self.__app
instance.expirationTime = the_future
try:
self.mapper.update(instance, fields)
except CSENotFound as e:
self.logger.warn("ExpirationTime update of %s failed: %s", instance, e)
# subscription disappeared?
# missed the expirationTime?
# mb sync issue?; mb congestion?
if restore:
restore(instance)
return
else:
raise
# NOTE: expirationTime might have been changed by CSE at this point.
# update could/should return the updated instance in this case, but
# doesnt. => additional GET to confirm expirationTime ?
self.logger.debug("Will update expiration time in %s seconds", interval)
self.runner.set_timer(
interval,
self.__update_exp_time,
instance=instance,
extra_fields=extra_fields,
restore=restore,
)

View File

@ -1,51 +0,0 @@
from gevent import spawn_later, wait
from futile.logging import LoggerMixin
class AppRunner(LoggerMixin):
def __init__(self, m2m_app, *args, **kw):
super(AppRunner, self).__init__(*args, **kw)
self._timers = set()
self.m2m_app = m2m_app
self.m2m_ep = None
def run(self, m2m_ep):
self.m2m_ep = m2m_ep
try:
self._run()
except (KeyboardInterrupt, SystemExit):
self.logger.info("Exiting...")
except Exception:
self.logger.exception("Error")
raise
finally:
self.logger.debug("Shutting down")
self._shutdown_app()
for timer in self._timers:
timer.kill()
def _run(self):
self.m2m_app.run(self, self.m2m_ep)
wait()
def _shutdown_app(self):
self.m2m_app.shutdown()
def set_timer(self, t, f, *args, **kw):
timer = None
def wrapper():
self._timers.discard(timer)
f(*args, **kw)
timer = spawn_later(t, wrapper)
self._timers.add(timer)
return timer
def cancel_timer(self, timer):
self._timers.discard(timer)
timer.kill()

View File

@ -1,75 +0,0 @@
import sys
from json import load as json_load
from operator import getitem
import futile
def prepare_app(parser, loader, name, default_config_file):
parser.add_argument("-v", "--verbose", action="count", default=None,
help="Increase verbosity in output. This option can be"
" specified multiple times.")
args = parser.parse_args()
module_ = loader.fullname.split("." + name).pop(0)
futile.logging.set_default_level(futile.logging.DEBUG)
logger = futile.logging.get_logger(name)
config_locations = (".", "/etc/openmtc/" + module_)
try:
import os.path
for d in config_locations:
config_file = os.path.join(os.path.abspath(d),
default_config_file)
logger.debug("Trying config file location: %s", config_file)
if os.path.isfile(config_file):
break
else:
raise Exception("Configuration file %s not found in any of these "
"locations: %s" % default_config_file,
config_locations)
except Exception as e:
sys.stderr.write(str(e) + "\n")
sys.exit(2)
try:
with open(config_file) as f:
logger.info("Reading configuration file %s.", config_file)
config = json_load(f)
except IOError as e:
logger.warning("Failed to read configuration file %s: %s",
config_file, e)
config = {}
except Exception as e:
logger.critical("Error reading configuration file %s: %s",
config_file, e)
sys.exit(2)
if "logging" in config: # TODO init logging
log_conf = config["logging"]
if args.verbose is None:
futile.logging.set_default_level(log_conf.get("level") or
futile.logging.WARNING)
elif args.verbose >= 2:
futile.logging.set_default_level(futile.logging.DEBUG)
else:
futile.logging.set_default_level(futile.logging.INFO)
logfile = log_conf.get("file")
if logfile:
futile.logging.add_log_file(logfile)
else:
futile.logging.set_default_level(futile.logging.DEBUG)
return args, config
def get_value(name, value_type, default_value, args, config):
try:
value = (getattr(args, name.replace(".", "_"), None) or
reduce(getitem, name.split("."), config))
except KeyError:
value = None
value = value if isinstance(value, value_type) else default_value
return value

View File

@ -1,2 +0,0 @@
from openmtc_onem2m.transport import AdditionalInformation, MetaInformation, \
OneM2MRequest, OneM2MResponse

View File

@ -1,23 +0,0 @@
from abc import abstractmethod
from futile import LoggerMixin
def normalize_path(path):
if not path:
return ''
if path.startswith('//'):
# abs CSE
return '/_' + path[1:]
elif path.startswith('/'):
# sp rel CSE
return '/~' + path
return path
class OneM2MClient(LoggerMixin):
def __init__(self):
super(OneM2MClient, self).__init__()
@abstractmethod
def send_onem2m_request(self, onem2m_request):
pass

View File

@ -1,217 +0,0 @@
import urllib
import ssl
from socket import (
gaierror,
error as socket_error,
)
from time import time
from urlparse import urlparse
from aplus import Promise
from futile.caching import LRUCache
from geventhttpclient.client import HTTPClient
from geventhttpclient.response import HTTPResponse
from openmtc.exc import (
OpenMTCNetworkError,
ConnectionFailed,
)
from openmtc_onem2m.exc import (
get_error_class,
get_response_status,
ERROR_MIN,
)
from openmtc_onem2m.model import (
ResourceTypeE,
get_short_attribute_name,
get_short_member_name,
)
from openmtc_onem2m.serializer.util import (
decode_onem2m_content,
encode_onem2m_content,
)
from openmtc_onem2m.transport import (
OneM2MOperation,
OneM2MResponse,
OneM2MErrorResponse,
)
from . import (
OneM2MClient,
normalize_path,
)
_method_map_to_http = {
OneM2MOperation.create: 'POST',
OneM2MOperation.retrieve: 'GET',
OneM2MOperation.update: 'PUT',
OneM2MOperation.delete: 'DELETE',
OneM2MOperation.notify: 'POST',
}
_clients = LRUCache(threadsafe=False)
_query_params = frozenset(['rt', 'rp', 'rcn', 'da', 'drt'])
_header_to_field_map = {
'X-M2M-ORIGIN': 'originator',
'X-M2M-RI': 'rqi',
'X-M2M-GID': 'gid',
'X-M2M-OT': 'ot',
'X-M2M-RST': 'rset',
'X-M2M-RET': 'rqet',
'X-M2M-OET': 'oet',
'X-M2M-EC': 'ec',
}
def get_client(m2m_ep, use_xml=False, ca_certs=None, cert_file=None, key_file=None,
insecure=False):
try:
return _clients[(m2m_ep, use_xml)]
except KeyError:
# TODO: make connection_timeout and concurrency configurable
client = _clients[(m2m_ep, use_xml)] = OneM2MHTTPClient(
m2m_ep, use_xml, ca_certs, cert_file, key_file, insecure)
return client
class OneM2MHTTPClient(OneM2MClient):
# defaults
DEF_SSL_VERSION = ssl.PROTOCOL_TLSv1_2
def __init__(self, m2m_ep, use_xml, ca_certs=None, cert_file=None, key_file=None,
insecure=False):
super(OneM2MHTTPClient, self).__init__()
self.parsed_url = urlparse(m2m_ep)
is_https = self.parsed_url.scheme[-1].lower() == "s"
port = self.parsed_url.port or (is_https and 443 or 80)
host = self.parsed_url.hostname
self.path = self.parsed_url.path.rstrip('/')
if self.path and not self.path.endswith('/'):
self.path += '/'
# TODO(rst): handle IPv6 host here
# geventhttpclient sets incorrect host header
# i.e "host: ::1:8000" instead of "host: [::1]:8000
if (is_https and ca_certs is not None and cert_file is not None and
key_file is not None):
ssl_options = {
"ca_certs": ca_certs,
"certfile": cert_file,
"keyfile": key_file,
"ssl_version": self.DEF_SSL_VERSION
}
else:
ssl_options = None
client = HTTPClient(host, port, connection_timeout=120.0,
concurrency=50, ssl=is_https,
ssl_options=ssl_options, insecure=insecure)
self.request = client.request
self.content_type = 'application/' + ('xml' if use_xml else 'json')
def _handle_network_error(self, exc, p, http_request, t,
exc_class=OpenMTCNetworkError):
error_str = str(exc)
if error_str in ("", "''"):
error_str = repr(exc)
method = http_request["method"]
path = http_request["request_uri"]
log_path = "%s://%s/%s" % (self.parsed_url.scheme, self.parsed_url.netloc, path)
error_msg = "Error during HTTP request: %s. " \
"Request was: %s %s (%.4fs)" % (error_str, method, log_path, time() - t)
p.reject(exc_class(error_msg))
def map_onem2m_request_to_http_request(self, onem2m_request):
"""
Maps a OneM2M request to a HTTP request
:param onem2m_request: OneM2M request to be mapped
:return: request: the resulting HTTP request
"""
self.logger.debug("Mapping OneM2M request to generic request: %s", onem2m_request)
params = {
param: getattr(onem2m_request, param) for param in _query_params
if getattr(onem2m_request, param) is not None
}
if onem2m_request.fc is not None:
filter_criteria = onem2m_request.fc
params.update({
(get_short_attribute_name(name) or get_short_member_name(name)): val
for name, val in filter_criteria.get_values(True).iteritems()
})
path = normalize_path(onem2m_request.to)
if params:
path += '?' + urllib.urlencode(params, True)
content_type, data = encode_onem2m_content(onem2m_request.content, self.content_type, path=path)
# TODO(rst): check again
# set resource type
if onem2m_request.operation == OneM2MOperation.create:
content_type += '; ty=' + str(ResourceTypeE[onem2m_request.resource_type.typename])
headers = {
header: getattr(onem2m_request, field) for header, field in _header_to_field_map.iteritems()
if getattr(onem2m_request, field) is not None
}
headers['content-type'] = content_type
self.logger.debug("Added request params: %s", params)
return {
'method': _method_map_to_http[onem2m_request.operation],
'request_uri': self.path + path,
'body': data,
'headers': headers,
}
def map_http_response_to_onem2m_response(self, onem2m_request, response):
"""
Maps HTTP response to OneM2M response
:param onem2m_request: the OneM2M request that created the response
:param response: the HTTP response
:return: resulting OneM2MResponse or OneM2MErrorResponse
"""
if not isinstance(response, HTTPResponse):
self.logger.error("Not a valid response: %s", response)
# return OneM2MErrorResponse(STATUS_INTERNAL_SERVER_ERROR)
self.logger.debug("Mapping HTTP response for OneM2M response: %s", response)
rsc = response.get("x-m2m-rsc", 5000)
if int(rsc) >= ERROR_MIN:
return OneM2MErrorResponse(
get_error_class(rsc).response_status_code, onem2m_request)
return OneM2MResponse(
get_response_status(rsc),
request=onem2m_request,
rsc=rsc,
pc=decode_onem2m_content(response.read(), response.get("content-type"))
)
def send_onem2m_request(self, onem2m_request):
with Promise() as p:
http_request = self.map_onem2m_request_to_http_request(onem2m_request)
t = time()
try:
response = self.request(**http_request)
except (socket_error, gaierror) as exc:
self._handle_network_error(exc, p, http_request, t, ConnectionFailed)
except Exception as exc:
self.logger.exception("Error in HTTP request")
self._handle_network_error(exc, p, http_request, t)
else:
try:
onem2m_response = self.map_http_response_to_onem2m_response(onem2m_request, response)
if isinstance(onem2m_response, OneM2MErrorResponse):
p.reject(onem2m_response)
else:
p.fulfill(onem2m_response)
finally:
response.release()
return p

View File

@ -1,431 +0,0 @@
from aplus import (
Promise,
)
from collections import deque
from futile.caching import LRUCache
import gevent
from gevent import monkey; monkey.patch_all()
from . import OneM2MClient
from openmtc.exc import ConnectionFailed, OpenMTCNetworkError
from ..exc import (
ERROR_MIN,
CSEValueError,
CSEError,
)
from ..serializer.util import (
decode_onem2m_content,
encode_onem2m_content,
)
from ..transport import (
OneM2MRequest,
OneM2MResponse,
OneM2MErrorResponse,
OneM2MOperation,
)
from ..model import ResourceTypeE
import paho.mqtt.client as mqtt
from simplejson import (
JSONDecoder,
JSONEncoder,
JSONDecodeError,
)
from socket import error as SocketError
from urlparse import urlparse
#: Dictionary mapping supported schemes to port numbers
portmap = {
'mqtt': 1883,
'mqtts': 8883,
# NB: The correct (i.e. registered with IANA) service-name for SSL/TLS-wrapped MQTT is 'secure-mqtt' in an effort to
# prevent confusion with MQTT-S/N. But as the entire world seems to insist on using 'mqtts' (including TS 0010,
# sec. 6.6) ... We are supporting both names here for maximum compliance and robustness.
'secure-mqtt': 8883,
}
MQTT_QOS_LEVEL = 1
_clients = LRUCache(threadsafe=False)
def get_client(m2m_ep, use_xml=False, client_id=None, handle_request_func=None):
"""
:param string m2m_ep:
:param boolean use_xml:
:param string client_id:
:param fun handle_request_func:
:return OneM2MMQTTClient:
"""
try:
return _clients[(m2m_ep, use_xml)]
except KeyError:
_clients[(m2m_ep, use_xml)] = OneM2MMQTTClient(m2m_ep, use_xml, client_id, handle_request_func)
return _clients[(m2m_ep, use_xml)]
class OneM2MMQTTClient(OneM2MClient):
"""
This class provides for a transport over the MQTT protocol as described in TS 0010
"""
__request_fields = frozenset([
'op',
'fr',
'rqi',
'ty',
'pc',
'rol',
'ot',
'rqet',
'rset',
'oet',
'rt',
'rp',
'rcn',
'ec',
'da',
'gid',
'drt',
'to',
])
__response_fields = frozenset([
'rsc',
'rqi',
'pc',
'fr',
'to',
])
@staticmethod
def _mqtt_mask(id):
return id.lstrip('/').replace('/', ':')
@staticmethod
def _build_topic(originator='+', receiver='+', type='req'):
"""
Helper function to create topic strings
:param string originator:
:param string receiver:
:param string type:
:return string:
"""
return '/'.join([
'/oneM2M',
type,
OneM2MMQTTClient._mqtt_mask(originator),
OneM2MMQTTClient._mqtt_mask(receiver),
])
def attach_callback(self):
"""
Wrapper function to attach callback handlers to the MQTT client. Functions attached in this manner are expected
to have the same name as the handler they seek to implement.
:return fun:
"""
def decorator(func):
def wrapper(_self, *args, **kwargs):
func(_self, *args, **kwargs)
setattr(self._client, func.__name__, func)
return wrapper
return decorator
def __init__(self, m2m_ep, _, client_id, handle_request_func=None, subscribe_sys_topics=False):
"""
:param str m2m_ep:
:param bool _:
:param str client_id:
:param call handle_request_func:
:param bool subscribe_sys_topics: Whether to subscribe to $SYS topics or not
(cf <https://github.com/mqtt/mqtt.github.io/wiki/SYS-Topics>)
"""
super(OneM2MMQTTClient, self).__init__()
parsed_url = urlparse(m2m_ep)
self._target_id = parsed_url.fragment
self._encode = JSONEncoder().encode
self._decode = JSONDecoder().decode
self._handle_request_func = handle_request_func
self._processed_request_ids = deque([], maxlen=200)
self._request_promises = LRUCache(threadsafe=False, max_items=200)
if client_id is None:
import random
import string
client_id = ''.join(random.sample(string.letters, 16))
self._client = mqtt.Client(
clean_session=False,
client_id='::'.join([
'C' if client_id[0].lower() in ['c', 'm'] else 'A',
self._mqtt_mask(client_id),
]),
)
@self.attach_callback()
def on_connect(client, _, rc):
"""
:param mqtt.Client client:
:param All userdata:
:param integer rc:
:return void:
"""
if not rc == mqtt.CONNACK_ACCEPTED:
raise ConnectionFailed(mqtt.connack_string(rc))
def request_callback(client, _, message):
"""
Catch requests and
:param mqtt.Client client:
:param All _:
:param mqtt.MQTTMessage message:
:return void:
"""
originator = message.topic.split('/')[3]
try:
request = self._decode(message.payload)
except JSONDecodeError as e:
self.logger.warn(
'Got rubbish request from client %s: %s'
% (originator, e.message, )
)
return
try:
if request['rqi'] in self._processed_request_ids:
self.logger.info('Request %s already processed; discarding duplicate.' % (request['rqi'], ))
return
else:
rqi = request['rqi']
except KeyError:
self.logger.warn(
'Special treatment for special request w/o request id from %s.'
% (originator, )
)
return
try:
request['pc'] = decode_onem2m_content(self._encode(request['pc']), 'application/json')
request['ty'] = type(request['pc'])
except KeyError:
# No content, eh?
request['ty'] = None
self.logger.debug('Decoded JSON request: %s' % (request, ))
op = OneM2MOperation._member_map_.values()[request['op'] - 1]
to = request['to']
del request['op'], request['to']
try:
response = self._handle_request_func(
OneM2MRequest(op, to, **request)
).get()
except OneM2MErrorResponse as response:
self.logger.error('OneM2MError: %s' % (response.message, ))
except CSEError as e:
response = OneM2MErrorResponse(status_code=e.response_status_code, rqi=rqi)
if not response.rqi:
# This really should not happen. No, really, it shouldn't.
self.logger.debug(
'FIXUP! FIXUP! FIXUP! Adding missing request identifier to response: %s'
% (rqi, )
)
response.rqi = rqi
if response.content:
response.content = self._decode(
encode_onem2m_content(response.content, 'application/json', path=response.to)[1]
)
self._publish_message(
self._encode({
k: getattr(response, k) for k in self.__response_fields if getattr(response, k) is not None
}),
self._build_topic(originator, client_id, type='resp'),
)
self._processed_request_ids.append(rqi)
def response_callback(client, _, message):
"""
:param mqtt.Client client:
:param All _:
:param mqtt.MQTTMessage message:
:return:
"""
try:
response = self._decode(message.payload)
except JSONDecodeError as e:
self.logger.error('Discarding response w/ damaged payload: %s', (e.message, ))
return
promise_key = (message.topic.split('/')[4], response['rqi'])
try:
p = self._request_promises[promise_key]
except KeyError:
self.logger.debug(
'Response %s could not be mapped to a request. Discarding.'
% (response['rqi'], )
)
return
try:
response['pc'] = decode_onem2m_content(self._encode(response['pc']), 'application/json')
except KeyError:
pass
except CSEValueError as e:
self.logger.error(
'Content of response %s could not be parsed, throwing on the trash heap: %s'
% (response['rqi'], e.message)
)
p.reject(e)
status_code = response['rsc']
del response['rsc']
if status_code >= ERROR_MIN:
p.reject(OneM2MErrorResponse(status_code, **response))
else:
p.fulfill(OneM2MResponse(status_code, **response))
topics = [
self._build_topic(originator=client_id, receiver='#', type='resp'),
]
client.message_callback_add(topics[0], response_callback)
if self._handle_request_func is not None:
topics.append(self._build_topic(receiver=client_id) + '/+')
client.message_callback_add(topics[1], request_callback)
if subscribe_sys_topics:
topics.append('$SYS/#')
self.logger.debug('Subscribing to topic(s) %s ...' % (', '.join(topics), ))
client.subscribe([
(str(topic), MQTT_QOS_LEVEL) for topic in topics
])
@self.attach_callback()
def on_disconnect(client, userdata, rc):
"""
:param mqtt.Client client:
:param All userdata:
:param int rc:
:return void:
"""
if not rc == mqtt.MQTT_ERR_SUCCESS:
self.logger.error(
'Involuntary connection loss: %s (code %d). Waiting for reconnect ...'
% (mqtt.error_string(rc), rc)
)
@self.attach_callback()
def on_message(client, userdata, message):
"""
:param mqtt.Client client:
:param All userdata:
:param mqtt.MQTTMessage message:
:return void:
"""
self.logger.debug('message received on topic %s' % (message.topic, ))
@self.attach_callback()
def on_log(client, userdata, level, buf):
"""
:param mqtt.Client client:
:param All userdata:
:param integer level:
:param string buf:
:return void:
"""
self.logger.debug('pahomqtt-%d: %s' % (level, buf))
if parsed_url.username:
self._client.username_pw_set(parsed_url.username, parsed_url.password)
try:
self._client.connect(
parsed_url.hostname,
parsed_url.port or portmap[parsed_url.scheme]
)
except SocketError as e:
raise ConnectionFailed(e.message)
def loop():
try:
while self._client.loop(timeout=0.1) != mqtt.mqtt_cs_disconnecting:
gevent.sleep()
except (KeyboardInterrupt, SystemExit):
pass
gevent.spawn(loop)
def _publish_message(self, payload, topic):
(rc, mid) = self._client.publish(topic, payload, MQTT_QOS_LEVEL)
if not rc == mqtt.MQTT_ERR_SUCCESS:
self.logger.info('Code %d while sending message %d: %s' % (rc, mid, mqtt.error_string(rc)))
def send_onem2m_request(self, request):
"""
:param openmtc_onem2m.transport.OneM2MRequest request:
:return Promise:
"""
p = Promise()
try:
client_id = request.originator.split('/')[-1]
except (KeyError, AttributeError):
# TODO: make this configurable
client_id = 'ae0'
request.op = 1 + OneM2MOperation._member_map_.keys().index(OneM2MOperation[request.op].name)
if request.pc:
request.pc = self._decode(
encode_onem2m_content(request.pc, 'application/json', path=request.to)[1]
)
try:
if request.to.startswith('//'): # abs CSE
request.to = '/_' + request.to[1:]
elif request.to.startswith('/'): # sp rel CSE
request.to = '/~' + request.to
except AttributeError:
self.logger.error('Could not resolve target id; defaulting to preset')
request.to = '/' + self._target_id
if request.ty:
request.ty = ResourceTypeE[request.resource_type.typename].value
self.logger.debug('Preparing request for transit: %s' % (request, ))
promises_key = (self._target_id, request.rqi)
def cleanup(_):
self.logger.debug('Clearing request id %s ...' % (promises_key, ))
del self._request_promises[promises_key]
p.addCallback(cleanup)
p.addErrback(cleanup)
self._request_promises[promises_key] = p
self._publish_message(
self._encode({
str(k): getattr(request, k) for k in self.__request_fields if getattr(request, k) is not None
}),
self._build_topic(client_id, self._target_id) + '/json',
)
return p
def stop(self):
self._client.disconnect()
# TODO(sho): this is abominable. But for the time being, there seems to be no elegant solution to this.
self._client._clean_session = True
# TS 0010, sec. 6.3 mandates a reconnect in order to leave a clean state with the MQTT broker
self._client.reconnect()
self._client.disconnect()

View File

@ -1,183 +0,0 @@
"""
Created on 26.05.2013
@author: kca
"""
from openmtc.exc import OpenMTCError
from collections import namedtuple
STATUS = namedtuple("STATUS", "numeric_code description http_status_code")
STATUS_ACCEPTED = STATUS(
1000, "ACCEPTED", 202)
STATUS_OK = STATUS(
2000, "OK", 200)
STATUS_CREATED = STATUS(
2001, "CREATED", 201)
STATUS_BAD_REQUEST = STATUS(
4000, "BAD_REQUEST", 400)
STATUS_NOT_FOUND = STATUS(
4004, "NOT_FOUND", 404)
STATUS_OPERATION_NOT_ALLOWED = STATUS(
4005, "OPERATION_NOT_ALLOWED", 405)
STATUS_REQUEST_TIMEOUT = STATUS(
4008, "REQUEST_TIMEOUT", 408)
STATUS_SUBSCRIPTION_CREATOR_HAS_NO_PRIVILEGE = STATUS(
4101, ",_SUBSCRIPTION_CREATOR_HAS_NO_PRIVILEGE", 403)
STATUS_CONTENTS_UNACCEPTABLE = STATUS(
4102, "CONTENTS_UNACCEPTABLE", 400)
STATUS_ORIGINATOR_HAS_NO_PRIVILEGE = STATUS(
4103, "ORIGINATOR_HAS_NO_PRIVILEGE", 403)
STATUS_GROUP_REQUEST_IDENTIFIER_EXISTS = STATUS(
4104, "GROUP_REQUEST_IDENTIFIER_EXISTS", 409)
STATUS_CONFLICT = STATUS(
4105, "CONFLICT", 409)
STATUS_INTERNAL_SERVER_ERROR = STATUS(
5000, "INTERNAL_SERVER_ERROR", 500)
STATUS_NOT_IMPLEMENTED = STATUS(
5001, "NOT_IMPLEMENTED", 501)
STATUS_TARGET_NOT_REACHABLE = STATUS(
5103, "TARGET_NOT_REACHABLE", 404)
STATUS_NO_PRIVILEGE = STATUS(
5105, "NO_PRIVILEGE", 403)
STATUS_ALREADY_EXISTS = STATUS(
5106, "ALREADY_EXISTS", 403)
STATUS_TARGET_NOT_SUBSCRIBABLE = STATUS(
5203, "TARGET_NOT_SUBSCRIBABLE", 403)
STATUS_SUBSCRIPTION_VERIFICATION_INITIATION_FAILED = STATUS(
5204, "SUBSCRIPTION_VERIFICATION_INITIATION_FAILED", 500)
STATUS_SUBSCRIPTION_HOST_HAS_NO_PRIVILEGE = STATUS(
5205, "SUBSCRIPTION_HOST_HAS_NO_PRIVILEGE", 403)
STATUS_NON_BLOCKING_REQUEST_NOT_SUPPORTED = STATUS(
5206, "NON_BLOCKING_REQUEST_NOT_SUPPORTED", 501)
STATUS_EXTERNAL_OBJECT_NOT_REACHABLE = STATUS(
6003, "EXTERNAL_OBJECT_NOT_REACHABLE", 404)
STATUS_EXTERNAL_OBJECT_NOT_FOUND = STATUS(
6005, "EXTERNAL_OBJECT_NOT_FOUND", 404)
STATUS_MAX_NUMBER_OF_MEMBER_EXCEEDED = STATUS(
6010, "MAX_NUMBER_OF_MEMBER_EXCEEDED", 400)
STATUS_MEMBER_TYPE_INCONSISTENT = STATUS(
6011, "MEMBER_TYPE_INCONSISTENT", 400)
STATUS_MANAGEMENT_SESSION_CANNOT_BE_ESTABLISHED = STATUS(
6020, "MANAGEMENT_SESSION_CANNOT_BE_ESTABLISHED", 500)
STATUS_MANAGEMENT_SESSION_ESTABLISHMENT_TIMEOUT = STATUS(
6021, "MANAGEMENT_SESSION_ESTABLISHMENT_TIMEOUT", 500)
STATUS_INVALID_CMDTYPE = STATUS(
6022, "INVALID_CMDTYPE", 400)
STATUS_INVALID_ARGUMENTS = STATUS(
6023, "INVALID_ARGUMENTS", 400)
STATUS_INSUFFICIENT_ARGUMENT = STATUS(
6024, "INSUFFICIENT_ARGUMENT", 400)
STATUS_MGMT_CONVERSION_ERROR = STATUS(
6025, "MGMT_CONVERSION_ERROR", 500)
STATUS_CANCELLATION_FAILED = STATUS(
6026, "CANCELLATION_FAILED", 500)
STATUS_ALREADY_COMPLETE = STATUS(
6028, "ALREADY_COMPLETE", 400)
STATUS_COMMAND_NOT_CANCELLABLE = STATUS(
6029, "COMMAND_NOT_CANCELLABLE", 400)
STATUS_IMPERSONATION_ERROR = STATUS(
6101, "IMPERSONATION_ERROR", 400)
_status_map = {v.numeric_code: v for v in globals().values()
if isinstance(v, STATUS)}
ERROR_MIN = STATUS_BAD_REQUEST.numeric_code
class OneM2MError(OpenMTCError):
pass
class CSEError(OneM2MError):
response_status_code = STATUS_INTERNAL_SERVER_ERROR
@property
def status_code(self):
return self.response_status_code.http_status_code
@property
def rsc(self):
return self.response_status_code.numeric_code
class CSENotFound(CSEError):
response_status_code = STATUS_NOT_FOUND
class CSEOperationNotAllowed(CSEError):
response_status_code = STATUS_OPERATION_NOT_ALLOWED
class CSENotImplemented(CSEError):
response_status_code = STATUS_NOT_IMPLEMENTED
class CSETargetNotReachable(CSEError):
response_status_code = STATUS_TARGET_NOT_REACHABLE
class CSEConflict(CSEError):
response_status_code = STATUS_CONFLICT
class CSEBadRequest(CSEError):
response_status_code = STATUS_BAD_REQUEST
class CSESyntaxError(CSEBadRequest):
response_status_code = STATUS_BAD_REQUEST
class CSEPermissionDenied(CSEError):
response_status_code = STATUS_ORIGINATOR_HAS_NO_PRIVILEGE
class CSEImpersonationError(CSEBadRequest):
response_status_code = STATUS_IMPERSONATION_ERROR
class CSEValueError(CSESyntaxError, ValueError):
pass
class CSETypeError(CSESyntaxError, TypeError):
pass
class CSEMissingValue(CSESyntaxError):
pass
class CSEContentsUnacceptable(CSEError):
response_status_code = STATUS_CONTENTS_UNACCEPTABLE
_error_map = {
STATUS_INTERNAL_SERVER_ERROR.numeric_code: CSEError
}
def get_error_class(rsc):
return _error_map.get(int(rsc), CSEError)
def get_response_status(rsc):
return _status_map.get(int(rsc), STATUS_INTERNAL_SERVER_ERROR)
def all_subclasses(cls):
return cls.__subclasses__() + [g for s in cls.__subclasses__()
for g in all_subclasses(s)]
for c in all_subclasses(CSEError):
try:
code = vars(c)["response_status_code"].numeric_code
except KeyError:
continue
_error_map[code] = c
del c, code

View File

@ -1,118 +0,0 @@
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
from openmtc.mapper import BasicMapper, MapperError
from openmtc_onem2m import OneM2MRequest
from openmtc_onem2m.transport import OneM2MOperation
def _is_persistent(instance):
return bool(instance.path)
class OneM2MMapper(BasicMapper):
def __init__(self, cse, originator=None, ca_certs=None, cert_file=None, key_file=None, *args, **kw):
super(OneM2MMapper, self).__init__(*args, **kw)
scheme = urlparse(cse).scheme.lower()
if scheme in ("", "https", "http"):
from openmtc_onem2m.client.http import get_client
self._send_request = get_client(cse, use_xml=False, ca_certs=ca_certs, cert_file=cert_file, key_file=key_file).send_onem2m_request
elif scheme in ("mqtt", "mqtts", "secure-mqtt"):
from openmtc_onem2m.client.mqtt import get_client
self._send_request = get_client(cse, use_xml=False, client_id=originator).send_onem2m_request
elif scheme == "coap":
raise NotImplementedError
else:
raise ValueError(
"Unsupported URL scheme: %s" % (scheme,)
)
self.originator = originator
def create(self, path, instance):
instance.__dict__.update({
attribute.name: None for attribute in type(instance).attributes if attribute.accesstype == attribute.RO
})
# TODO(rst): add resource_type
response = self._send_request(OneM2MRequest(
OneM2MOperation.create,
path,
self.originator,
ty=type(instance),
pc=instance
)).get()
try:
instance.__dict__.update(response.content.values)
instance.path = path + '/' + response.content.resourceName
except (AttributeError, ):
instance.path = path
self.logger.debug("Set instance path: %s" % (instance.path, ))
instance._synced = False
return instance
def update(self, instance, fields=None):
if not _is_persistent(instance):
raise MapperError("Instance is not yet stored")
return self._do_update(instance, fields)
def _do_update(self, instance, fields=None):
attributes = type(instance).attributes
fields_to_be_cleared = [a.name for a in attributes if a.accesstype in (a.WO, a.RO)]
if fields:
fields_to_be_cleared.extend([a.name for a in attributes if a.name not in fields])
instance.childResource = []
# remove NP attributes
instance.__dict__.update({
a: None for a in fields_to_be_cleared
})
response = self._send_request(OneM2MRequest(
OneM2MOperation.update,
instance.path,
self.originator,
pc=instance
)).get()
try:
response.content.path = instance.path
except AttributeError:
pass
return response.content
def get(self, path):
response = self._get_data(path)
response.content.path = path
self.logger.debug("Received response: %s", response.content)
return response.content
def delete(self, instance):
self._send_request(OneM2MRequest(
OneM2MOperation.delete,
getattr(instance, "path", instance),
self.originator
))
def _get_data(self, path):
return self._send_request(OneM2MRequest(
OneM2MOperation.retrieve,
path,
self.originator
)).get()
# TODO(rst): check if this can be removed in parent class
@classmethod
def _patch_model(cls):
pass
def _fill_resource(self, res, data):
pass
def _map(self, path, typename, data):
pass

File diff suppressed because it is too large Load Diff

View File

@ -1,93 +0,0 @@
from .json import OneM2MJsonSerializer
from openmtc_onem2m.exc import CSEBadRequest, CSEContentsUnacceptable
from werkzeug import Accept, parse_accept_header
from futile.logging import get_logger
from openmtc.exc import OpenMTCError
_factories = {"application/json": OneM2MJsonSerializer,
"application/vnd.onem2m-res+json": OneM2MJsonSerializer,
"application/vnd.onem2m-ntfy+json": OneM2MJsonSerializer,
"application/vnd.onem2m-attrs+json": OneM2MJsonSerializer,
"text/plain": OneM2MJsonSerializer}
_serializers = {}
def create_onem2m_serializer(content_type):
try:
factory = _factories[content_type]
except KeyError:
raise CSEBadRequest("Unsupported content type: %s. Try one of %s" %
(content_type, ', '.join(_factories.keys())))
return factory()
def get_onem2m_supported_content_types():
return _factories.keys()
def get_onem2m_decoder(content_type):
# TODO: Check if this is faster than split
content_type, _, _ = content_type.partition(";")
content_type = content_type.strip().lower()
try:
return _serializers[content_type]
except KeyError:
serializer = create_onem2m_serializer(content_type)
_serializers[content_type] = serializer
return serializer
get_serializer = get_onem2m_decoder
def get_onem2m_encoder(accept):
# TODO: optimize
if accept:
parsed_accept_header = parse_accept_header(accept, Accept)
""":type : Accept"""
supported = get_onem2m_supported_content_types()
accepted_type = parsed_accept_header.best_match(supported)
if not accepted_type:
raise CSEContentsUnacceptable("%s is not supported. "
"Supported content types are: %s" %
(accept, ', '.join(supported)))
else:
# TODO: use config["default_content_type"]
accepted_type = "application/json"
# TODO: optimize
return get_serializer(accepted_type)
def register_onem2m_serializer(content_type, factory):
set_value = _factories.setdefault(content_type, factory)
if set_value is not factory:
raise OpenMTCError("Content type is already registered: %s" %
(content_type, ))
################################################################################
# import other serializers at serializers
################################################################################
# import impl
# import pkgutil
#
# logger = get_logger(__name__)
#
# for _importer, modname, ispkg in pkgutil.iter_modules(impl.__path__):
# modname = impl.__name__ + "." + modname
# logger.debug("Found onem2m serializer module %s (is a package: %s)" %
# (modname, ispkg))
# try:
# __import__(modname)
# except:
# logger.error("Failed to import serializer %s", modname)
# raise
# del _importer
# del modname
# del ispkg
#
# del impl
# del pkgutil
# del logger

View File

@ -1,203 +0,0 @@
from abc import ABCMeta, abstractmethod
from datetime import datetime
from re import compile as re_compile
from futile.logging import LoggerMixin
from openmtc_onem2m.exc import CSESyntaxError, CSEBadRequest, CSEValueError
from openmtc_onem2m.model import (get_onem2m_type, ContentInstance,
ResourceTypeE, Notification,
get_onem2m_resource_type,
get_short_attribute_name,
get_short_member_name, get_long_member_name,
get_short_resource_name,
get_long_attribute_name,
OneM2MEntity, OneM2MResource, Container,
get_long_resource_name, OneM2MContentResource,
URIList, OneM2MIntEnum)
_typename_matcher = re_compile(r'^m2m:([a-z]+)$')
def get_typename(tn):
return _typename_matcher.findall(tn).pop()
class OneM2MSerializer(LoggerMixin):
__metaclass__ = ABCMeta
@abstractmethod
def encode_resource(self, resource, response, pretty=False,
encoding="utf-8", fields=None):
raise NotImplementedError()
@abstractmethod
def decode_resource_values(self, s):
pass
def decode(self, s):
resource_type, data = self.decode_resource_values(s)
if issubclass(resource_type, OneM2MContentResource):
return resource_type(data)
child_resource = data.pop("childResource", None)
if child_resource:
try:
def map_child_resource(v):
res_type = ResourceTypeE(v["type"])
res_cls = get_onem2m_resource_type(res_type.name)
return res_cls(v["name"], resourceID=v["value"], resourceType=res_type)
child_resource = map(map_child_resource, child_resource)
except (TypeError, AttributeError, KeyError, ValueError):
raise CSEValueError("Invalid entry in child resources: %s",
child_resource)
if resource_type is Notification and "notificationEvent" in data:
representation = data["notificationEvent"]["representation"]
representation = self.decode(self.dumps(representation))
data["notificationEvent"]["representation"] = representation
resource = resource_type(**data)
if child_resource:
resource.childResource = child_resource
return resource
class OneM2MDictSerializer(OneM2MSerializer):
def encode_resource(self, resource, pretty=False, path=None, encoding="utf-8", fields=None,
encapsulated=False):
representation = resource.values
self.logger.debug("Encoding representation: %s", representation)
if isinstance(resource, Notification):
# handle notifications
try:
event = representation["notificationEvent"]
if event:
e = event.values
e['representation'] = self.encode_resource(
event.representation, pretty, path, encoding, fields, True
)
representation["notificationEvent"] = {
get_short_attribute_name(k) or get_short_member_name(k): v
for k, v in e.iteritems()
}
except (AttributeError, KeyError):
self.logger.exception("failed to encode notify")
def make_val(val_path, resource_id):
try:
if val_path:
val_path += '/' if not val_path.endswith('/') else ''
except AttributeError:
val_path = ''
if resource_id.startswith(val_path):
return resource_id
return val_path + resource_id
if isinstance(resource, OneM2MResource):
def get_child_rep(c):
return {
"val": make_val(path, c.resourceID),
"nm": c.basename,
"typ": c.resourceType
}
representation["childResource"] = map(get_child_rep, representation["childResource"])
if isinstance(resource, URIList):
representation = [make_val(path, x) for x in representation]
if isinstance(resource, Container):
if isinstance(resource.latest, ContentInstance):
representation['latest'] = resource.latest.resourceID
if isinstance(resource.oldest, ContentInstance):
representation['oldest'] = resource.oldest.resourceID
# cleans representation
def clean_representation(o):
try:
# removes empty attributes
empty_keys = []
for k, v in o.items():
if v is None:
empty_keys.append(k)
elif isinstance(v, OneM2MEntity):
o[k] = self.encode_resource(v, pretty, path, encoding, fields)
elif isinstance(v, list):
def encode_list_item(item):
if isinstance(item, OneM2MEntity):
return self.encode_resource(item, pretty, path, encoding, fields)
return item
o[k] = map(encode_list_item, v)
else:
try:
if len(v) == 0:
empty_keys.append(k)
except TypeError:
pass
for k in empty_keys:
del o[k]
for k, v in o.items():
if not isinstance(v, (unicode, str, bool, datetime,
OneM2MIntEnum)):
clean_representation(v)
except AttributeError:
if isinstance(o, list):
for p in o:
clean_representation(p)
if not isinstance(resource, OneM2MContentResource):
representation = {
get_short_resource_name(k) or get_short_attribute_name(k) or
get_short_member_name(k): v for
k, v in representation.items()}
clean_representation(representation)
if (not isinstance(resource, OneM2MResource) and
not isinstance(resource, Notification) and
not isinstance(resource, OneM2MContentResource)):
return representation
typename = 'm2m:' + (get_short_resource_name(resource.typename) or
get_short_member_name(resource.typename))
if encapsulated:
return {typename: representation}
if pretty:
return self.pretty_dumps({typename: representation})
return self.dumps({typename: representation})
def _handle_partial_addressing(self, resource, pretty):
for k, v in resource.iteritems():
if k in ('latest', 'oldest') and isinstance(v, ContentInstance):
resource[k] = v.resourceID
if pretty:
return self.pretty_dumps(resource)
return self.dumps(resource)
def decode_resource_values(self, s):
def convert_to_long_keys(d):
return {get_long_resource_name(k) or get_long_attribute_name(k) or
get_long_member_name(k) or k: v for k, v in d.iteritems()}
try:
if hasattr(s, "read"):
data = self.load(s, object_hook=convert_to_long_keys)
else:
data = self.loads(s, object_hook=convert_to_long_keys)
except (ValueError, TypeError) as exc:
raise CSEBadRequest("Failed to parse input: %s" % (exc, ))
self.logger.debug("Read data: %s", data)
try:
typename, data = data.items()[0]
return get_onem2m_type(get_typename(typename)), data
except (AttributeError, IndexError, TypeError):
raise CSESyntaxError("Not a valid resource representation")

View File

@ -1 +0,0 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@ -1,62 +0,0 @@
from openmtc_onem2m.serializer.base import OneM2MDictSerializer
from json import JSONEncoder
from futile.logging import get_logger
from datetime import datetime
from openmtc_onem2m.model import ContentInstance
logger = get_logger(__name__)
# rst: ujson and yajl are not supporting object_hooks, but conversion is needed
# rst: some measurements are necessary what is better
# try:
# from ujson import load, loads
# logger.debug("using ujson for decoding JSON")
# except ImportError:
# try:
# from yajl import load, loads
# logger.debug("using yajl for decoding JSON")
# except ImportError:
try:
# simplejson is faster on decoding, tiny bit slower on encoding
from simplejson import load, loads
logger.debug("using simplejson for decoding JSON")
except ImportError:
logger.debug("using builtin json for decoding JSON")
from json import load, loads
del logger
def _default(x):
if isinstance(x, datetime):
try:
isoformat = x.isoformat
except AttributeError:
raise TypeError("%s (%s)" % (x, type(x)))
return isoformat()
elif isinstance(x, ContentInstance):
return x.resourceID
else:
try: # handle model classes
return x.values
except AttributeError:
raise TypeError("%s (%s)" % (x, type(x)))
_simple_encoder = JSONEncoder(check_circular=False, separators=(',', ':'),
default=_default)
_pretty_encoder = JSONEncoder(default=_default, indent=2,
separators=(',', ':'),
check_circular=False)
class OneM2MJsonSerializer(OneM2MDictSerializer):
def __init__(self, *args, **kw):
self.loads = loads
self.load = load
self.dumps = _simple_encoder.encode
self.pretty_dumps = _pretty_encoder.encode

View File

@ -1,38 +0,0 @@
from futile.logging import get_logger
from openmtc_onem2m.exc import CSEValueError
from openmtc_onem2m.serializer import get_onem2m_encoder, get_onem2m_decoder
logger = get_logger(__name__)
def decode_onem2m_content(content, content_type):
if content == "":
content = None
if content_type and content is not None:
serializer = get_onem2m_decoder(content_type)
try:
data = serializer.decode(content)
except CSEValueError as e:
logger.exception("Error reading input")
raise e
return data
return None
def encode_onem2m_content(content, content_type, pretty=False, path=None,
fields=None):
logger.debug("Encoding result: %s - %s", content, content_type)
if content is None:
return None, None
fields = fields # TODO(rst): maybe necessary
#fields = ["resourceID"]
serializer = get_onem2m_encoder(content_type)
data = serializer.encode_resource(content, pretty=pretty, path=path,
fields=fields)
return content_type + "; charset=utf-8", data

View File

@ -1,444 +0,0 @@
import random
import string
from enum import Enum, unique
from futile.logging import get_logger
from openmtc.model import StrEnum
from openmtc_onem2m.exc import OneM2MError
@unique
class RequestMethod(Enum):
create = "create"
retrieve = "retrieve"
update = "update"
delete = "delete"
notify = "notify"
execute = "execute"
observe = "observe"
_logger = get_logger(__name__)
class MetaInformation(object):
def __init__(self, ri=None, ot=None, rqet=None, rset=None, rt=None, rd=None,
rc=None, rp=None, oet=None, ls=None, ec=None, da=None,
gid=None, role=None):
"""Meta info about request, contains:
ri (Request Identifier),
ot (optional originating timestamp),
rqet (optional request expiration timestamp),
rset (optional result expiration timestamp),
rt (optional response type),
rd (optional result destination),
rc (optional result content),
rp (optional response persistence),
oet (optional operational execution time),
ls (optional lifespan),
ec (optional event category),
da (optional delivery aggregation),
gid (optional group request identifier)
role ()
"""
@property
def ri(self):
return self.identifier
@ri.setter
def ri(self, ri):
self.identifier = ri
@property
def ot(self):
return self.originating_timestamp
@ot.setter
def ot(self, ot):
self.originating_timestamp = ot
@property
def rqet(self):
return self.request_expiration_timestamp
@rqet.setter
def rqet(self, rqet):
self.request_expiration_timestamp = rqet
@property
def rset(self):
return self.result_expiration_timestamp
@rset.setter
def rset(self, rset):
self.result_expiration_timestamp = rset
@property
def rt(self):
return self.response_type
@rt.setter
def rt(self, rt):
self.response_type = rt
@property
def rd(self):
return self.result_destination
@rd.setter
def rd(self, rd):
self.result_destination = rd
@property
def rc(self):
return self.result_content
@rc.setter
def rc(self, rc):
self.result_content = rc
@property
def rp(self):
return self.response_persistence
@rp.setter
def rp(self, rp):
self.response_persistence = rp
@property
def oet(self):
return self.operational_execution_time
@oet.setter
def oet(self, oet):
self.operational_execution_time = oet
@property
def ec(self):
return self.event_category
@ec.setter
def ec(self, ec):
self.event_category = ec
@property
def ls(self):
return self.lifespan
@ls.setter
def ls(self, ls):
self.lifespan = ls
@property
def da(self):
return self.delivery_aggregation
@da.setter
def da(self, da):
self.delivery_aggregation = da
@property
def gid(self):
return self.group_request_identifier
@gid.setter
def gid(self, gid):
self.group_request_identifier = gid
@property
def ro(self):
return self.role
@ro.setter
def ro(self, ro):
self.role = ro
def __str__(self):
s = ''
for k in self.__dict__:
if getattr(self, k):
s = s + ' | mi.' + str(k) + ': ' + str(self.__dict__[k])
return s
MI = MetaInformation
class AdditionalInformation(object):
def __init__(self, cs=None, ra=None):
"""Optional additional information about the request, contains:
cs (optional, status codes),
ra (optional, address for the temporary storage of end node Responses)
"""
self.cs = cs
self.ra = ra
def __str__(self):
s = ''
for k in self.__dict__:
if getattr(self, k):
s = s + ' | ai.' + str(k) + ': ' + str(self.__dict__[k])
return s
AI = AdditionalInformation
class OneM2MOperation(StrEnum):
create = "create"
retrieve = "retrieve"
update = "update"
delete = "delete"
notify = "notify"
class OneM2MRequest(object):
internal = False
cascading = False
"""Class representing a OneM2M request"""
def __init__(self, op, to, fr=None, rqi=None, ty=None, pc=None, rol=None,
ot=None, rqet=None, rset=None, oet=None, rt=None, rp=None,
rcn=None, ec=None, da=None, gid=None, filter_criteria=None,
drt=None):
# Operation
self.operation = op
# Target uri
self.to = to
# Originator ID
self.originator = fr # original long name is from
self.request_identifier = rqi or ''.join(random.sample(string.letters + string.digits, 16))
# Type of a created resource
self.resource_type = ty
# Resource content to be transferred.
self.content = pc
self.role = rol
self.originating_timestamp = ot
self.request_expiration_timestamp = rqet
self.result_expiration_timestamp = rset
self.operation_execution_time = oet
self.response_type = rt
self.result_persistence = rp
self.result_content = rcn
self.event_category = ec
self.delivery_aggregation = da
self.group_request_identifier = gid
self.filter_criteria = filter_criteria
# Optional Discovery result type
self.discovery_result_type = drt
@property
def op(self):
return self.operation
@op.setter
def op(self, op):
self.operation = op
@property
def fr(self):
return self.originator
@fr.setter
def fr(self, fr):
self.originator = fr
@property
def rqi(self):
return self.request_identifier
@rqi.setter
def rqi(self, rqi):
self.request_identifier = rqi
@property
def ty(self):
return self.resource_type
@ty.setter
def ty(self, ty):
self.resource_type = ty
@property
def pc(self):
return self.content
@pc.setter
def pc(self, pc):
self.content = pc
@property
def rol(self):
return self.role
@rol.setter
def rol(self, rol):
self.role = rol
@property
def ot(self):
return self.originating_timestamp
@ot.setter
def ot(self, ot):
self.originating_timestamp = ot
@property
def rqet(self):
return self.request_expiration_timestamp
@rqet.setter
def rqet(self, rqet):
self.request_expiration_timestamp = rqet
@property
def rset(self):
return self.result_expiration_timestamp
@rset.setter
def rset(self, rset):
self.result_expiration_timestamp = rset
@property
def oet(self):
return self.operation_execution_time
@oet.setter
def oet(self, oet):
self.operation_execution_time = oet
@property
def rt(self):
return self.response_type
@rt.setter
def rt(self, rt):
self.response_type = rt
@property
def rp(self):
return self.result_persistence
@rp.setter
def rp(self, rp):
self.result_persistence = rp
@property
def rcn(self):
return self.result_content
@rcn.setter
def rcn(self, rcn):
self.result_content = rcn
@property
def ec(self):
return self.event_category
@ec.setter
def ec(self, ec):
self.event_category = ec
@property
def da(self):
return self.delivery_aggregation
@da.setter
def da(self, da):
self.delivery_aggregation = da
@property
def gid(self):
return self.group_request_identifier
@gid.setter
def gid(self, gid):
self.group_request_identifier = gid
@property
def fc(self):
return self.filter_criteria
@fc.setter
def fc(self, fc):
self.filter_criteria = fc
@property
def drt(self):
return self.discovery_result_type
@drt.setter
def drt(self, drt):
self.discovery_result_type = drt
def __str__(self):
return '%s: %s' % (self.__class__.__name__, ' | '.join([
'%s: %s' % (str(k), str(v)) for k, v in self.__dict__.iteritems()
]))
class OneM2MResponse(object):
"""Class representing a OneM2M response"""
def __init__(self, status_code, request=None, rqi=None, pc=None, to=None,
fr=None, rsc=None):
# Operation result
self.response_status_code = status_code
if request:
self.request_identifier = request.rqi
# Target uri
self.to = request.to
# Originator ID
self.originator = request.fr
else:
self.request_identifier = rqi
# Target uri
self.to = to
# Originator ID
self.originator = fr
# Resource content to be transferred.
self.content = pc
@property
def status_code(self):
return self.response_status_code.http_status_code
@property
def rsc(self):
return self.response_status_code.numeric_code
@property
def rqi(self):
return self.request_identifier
@rqi.setter
def rqi(self, rqi):
self.request_identifier = rqi
@property
def pc(self):
return self.content
@pc.setter
def pc(self, pc):
self.content = pc
@property
def fr(self):
return self.originator
@fr.setter
def fr(self, fr):
self.originator = fr
def __str__(self):
return '%s: %s' % (self.__class__.__name__, ' | '.join([
'%s: %s' % (str(k), str(v)) for k, v in self.__dict__.iteritems()
]))
class OneM2MErrorResponse(OneM2MResponse, OneM2MError):
pass

View File

@ -1,37 +0,0 @@
from re import compile as re_compile
def _get_regex_path_component():
# see http://tools.ietf.org/html/rfc3986#section-3.3
# path-abempty = *( "/" segment )
# segment = *pchar
# pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
# pct-encoded = "%" HEXDIG HEXDIG
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
# sub-delims = "!" / "$" / "&" / """ / "(" / ")" / "*" / "+" / "," / ";" /
# "="
unreserved = r"[\w\.\-~]"
pct_encoded = "%[A-Fa-f0-9][A-Fa-f0-9]"
sub_delims = r"[!$&'()\*\+,;=]"
pchar = "(?:" + unreserved + "|" + pct_encoded + "|" + sub_delims + "|:|@)"
segment = pchar + "+"
return segment
_sp_id = r'(//%s)?' % _get_regex_path_component()
_cse_id = r'(/%s)?' % _get_regex_path_component()
_path_suffix = r'(?:/?(%s(?:/%s)*))?' % (_get_regex_path_component(), _get_regex_path_component())
_onem2m_address_splitter = re_compile(r'^%s%s%s' % (_sp_id, _cse_id, _path_suffix))
def split_onem2m_address(onem2m_address):
"""
:param str onem2m_address:
:return: sp_id, cse_id, cse-relative rest
"""
return _onem2m_address_splitter.findall(onem2m_address).pop()

View File

@ -1 +0,0 @@
from io import *

View File

@ -1,16 +0,0 @@
#!/bin/bash
VERSION=${1}
if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
echo "Wrong version number! Only x.y or x.y.z is allowed."
exit 1
fi
SETUPS=( gevent-all sdk gevent-all-with-abs-gip arduinogip cul868gip roomui
testgip zigbeegip )
for setup in "${SETUPS[@]}"; do
sed -i -re 's/(^\W*SETUP_VERSION\W*=\W*")[0-9]+\.[0-9]+(\.[0-9]+)?"/\1'${VERSION}'"/' setup-${setup}.py
done

View File

@ -1,24 +0,0 @@
Metadata-Version: 1.1
Name: openmtc-sdk
Version: 4.99.0
Summary: The OpenMTC Python SDK
Home-page: http://www.openmtc.org
Author: Konrad Campowsky
Author-email: konrad.campowsky@fraunhofer.fokus.de
License: Fraunhofer FOKUS proprietary
Description: UNKNOWN
Platform: UNKNOWN
Requires: urllib3
Requires: gevent (>=1.0)
Requires: iso8601 (>=0.1.5)
Requires: werkzeug (>=0.9)
Requires: blist
Requires: simplejson
Requires: ujson
Requires: python_socketio
Requires: gevent_websocket
Requires: flask
Requires: pyxb (==1.2.3)
Requires: enum34
Requires: dtls
Requires: geventhttpclient

View File

@ -1,96 +0,0 @@
MANIFEST.in
setup-sdk.py
utils.py
common/openmtc-onem2m/src/openmtc_onem2m/__init__.py
common/openmtc-onem2m/src/openmtc_onem2m/exc.py
common/openmtc-onem2m/src/openmtc_onem2m/model.py
common/openmtc-onem2m/src/openmtc_onem2m/transport.py
common/openmtc-onem2m/src/openmtc_onem2m/util.py
common/openmtc-onem2m/src/openmtc_onem2m/client/__init__.py
common/openmtc-onem2m/src/openmtc_onem2m/client/http.py
common/openmtc-onem2m/src/openmtc_onem2m/client/mqtt.py
common/openmtc-onem2m/src/openmtc_onem2m/mapper/__init__.py
common/openmtc-onem2m/src/openmtc_onem2m/serializer/__init__.py
common/openmtc-onem2m/src/openmtc_onem2m/serializer/base.py
common/openmtc-onem2m/src/openmtc_onem2m/serializer/util.py
common/openmtc-onem2m/src/openmtc_onem2m/serializer/impl/__init__.py
common/openmtc-onem2m/src/openmtc_onem2m/serializer/json/__init__.py
common/openmtc/lib/pyio.py
common/openmtc/lib/aplus/__init__.py
common/openmtc/lib/openmtc_sdk.egg-info/PKG-INFO
common/openmtc/lib/openmtc_sdk.egg-info/SOURCES.txt
common/openmtc/lib/openmtc_sdk.egg-info/dependency_links.txt
common/openmtc/lib/openmtc_sdk.egg-info/requires.txt
common/openmtc/lib/openmtc_sdk.egg-info/top_level.txt
common/openmtc/src/openmtc/__init__.py
common/openmtc/src/openmtc/configuration.py
common/openmtc/src/openmtc/exc.py
common/openmtc/src/openmtc/util.py
common/openmtc/src/openmtc/version.py
common/openmtc/src/openmtc/mapper/__init__.py
common/openmtc/src/openmtc/mapper/exc.py
common/openmtc/src/openmtc/model/__init__.py
common/openmtc/src/openmtc/model/exc.py
futile/src/futile/__init__.py
futile/src/futile/abchelper.py
futile/src/futile/basictypes.py
futile/src/futile/contextlib.py
futile/src/futile/etree.py
futile/src/futile/exc.py
futile/src/futile/singleton.py
futile/src/futile/StringIO/__init__.py
futile/src/futile/caching/__init__.py
futile/src/futile/collections/OrderedSet.py
futile/src/futile/collections/__init__.py
futile/src/futile/collections/ordereddict.py
futile/src/futile/collections/sortedlist.py
futile/src/futile/logging/__init__.py
futile/src/futile/logging/handlers.py
futile/src/futile/logging/logbook.py
futile/src/futile/logging/logtap.py
futile/src/futile/multiprocess/RWLock.py
futile/src/futile/multiprocess/__init__.py
futile/src/futile/net/PortTester.py
futile/src/futile/net/__init__.py
futile/src/futile/net/exc.py
futile/src/futile/net/sockethelper.py
futile/src/futile/net/wsgi.py
futile/src/futile/net/xmlrpc.py
futile/src/futile/net/http/__init__.py
futile/src/futile/net/http/exc.py
futile/src/futile/net/http/client/ConnectionPoolManager.py
futile/src/futile/net/http/client/RestClient.py
futile/src/futile/net/http/client/RestClientAsync.py
futile/src/futile/net/http/client/SimpleConnectionManager.py
futile/src/futile/net/http/client/__init__.py
futile/src/futile/net/http/server/__init__.py
futile/src/futile/net/http/server/ssl/__init__.py
futile/src/futile/net/http/server/wsgi/__init__.py
futile/src/futile/net/http/server/wsgi/ssl.py
futile/src/futile/operator/__init__.py
futile/src/futile/os/__init__.py
futile/src/futile/os/mount.py
futile/src/futile/path/__init__.py
futile/src/futile/profile/__init__.py
futile/src/futile/serializer/__init__.py
futile/src/futile/serializer/exc.py
futile/src/futile/serializer/xml.py
futile/src/futile/signal/__init__.py
futile/src/futile/signal/timeout.py
futile/src/futile/string/__init__.py
futile/src/futile/subprocess/__init__.py
futile/src/futile/subprocess/daemon.py
futile/src/futile/tempfile/__init__.py
futile/src/futile/threading/RWLock.py
futile/src/futile/threading/__init__.py
futile/src/futile/threading/synchronized.py
futile/src/futile/traceback/__init__.py
futile/src/futile/types/TypeManager.py
futile/src/futile/types/__init__.py
openmtc-app/src/openmtc_app/__init__.py
openmtc-app/src/openmtc_app/exc.py
openmtc-app/src/openmtc_app/onem2m.py
openmtc-app/src/openmtc_app/util.py
openmtc-app/src/openmtc_app/flask_runner/__init__.py
openmtc-app/src/openmtc_app/notification/__init__.py
openmtc-app/src/openmtc_app/runner/__init__.py

View File

@ -1,14 +0,0 @@
urllib3
gevent >= 1.0
iso8601 >= 0.1.5
werkzeug >= 0.9
blist
simplejson
ujson
python_socketio
gevent_websocket
flask
pyxb == 1.2.3
enum34
dtls
geventhttpclient

View File

@ -1,6 +0,0 @@
aplus
futile
openmtc
openmtc_app
openmtc_onem2m
pyio

Binary file not shown.