From 4d8933abdf4a190493f2762d423f469f6d8b30a9 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 20:20:22 -0700 Subject: Actually, we can work around needing dateutil. If dateutil is present, we'll use that. If not, we'll use some regexes from the fixed_datetime module. --- LICENSE | 31 ++++ README | 3 - sleekxmpp/plugins/__init__.py | 20 +-- sleekxmpp/plugins/xep_0082.py | 13 +- sleekxmpp/plugins/xep_0202/__init__.py | 22 +-- sleekxmpp/plugins/xep_0202/stanza.py | 7 +- sleekxmpp/thirdparty/__init__.py | 1 + sleekxmpp/thirdparty/mini_dateutil.py | 267 +++++++++++++++++++++++++++++++++ 8 files changed, 310 insertions(+), 54 deletions(-) create mode 100644 sleekxmpp/thirdparty/mini_dateutil.py diff --git a/LICENSE b/LICENSE index b981b0ad..df302d00 100644 --- a/LICENSE +++ b/LICENSE @@ -25,6 +25,7 @@ Licences of Bundled Third Pary Code ----------------------------------- dateutil - Extensions to the standard python 2.3+ datetime module. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Copyright (c) 2003-2011 - Gustavo Niemeyer @@ -55,9 +56,38 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +fixed_datetime +~~~~~~~~~~~~~~ + +Copyright (c) 2008, Red Innovation Ltd., Finland +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Red Innovation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + OrderedDict - A port of the Python 2.7+ OrderedDict to Python 2.6 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Copyright (c) 2009 Raymond Hettinger @@ -85,6 +115,7 @@ subject to the following conditions: SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This software is subject to "The MIT License" diff --git a/README b/README index aab5ae8c..8a85365c 100644 --- a/README +++ b/README @@ -9,9 +9,6 @@ We try to keep requirements to a minimum, but we suggest that you install http:/ If you do not install this library, you may need to specify the server/port for services that use SRV records (like GTalk). "sudo pip install dnspython" on a *nix system with pip installed. -Two time related plugins (XEP-0082 and XEP-0202) also require the dateutil package, but that is not a hard requirement if you -don't need those plugins. - SleekXMPP has several design goals/philosophies: - Low number of dependencies. - Every XEP as a plugin. diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 21a05fe0..3f90f059 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -6,20 +6,6 @@ See the file LICENSE for copying permission. """ __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', - 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0085', - 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', 'xep_0203', - 'xep_0224', 'xep_0249', 'gmail_notify'] - -# Some plugins may require external dependencies beyond what the -# core SleekXMPP installation requires. Thus they should only by -# imported automatically if those dependecies are met. - -HAVE_DATEUTIL = True -try: - import dateutil -except: - HAVE_DATEUTIL = False - -if HAVE_DATEUTIL: - __all__.append('xep_0082') - __all__.append('xep_0202') + 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', + 'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', + 'xep_0082', 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] diff --git a/sleekxmpp/plugins/xep_0082.py b/sleekxmpp/plugins/xep_0082.py index e78a50ad..d3c4cc56 100644 --- a/sleekxmpp/plugins/xep_0082.py +++ b/sleekxmpp/plugins/xep_0082.py @@ -10,13 +10,7 @@ import logging import datetime as dt from sleekxmpp.plugins.base import base_plugin - -try: - from dateutil import parser - from dateutil.tz import tzoffset, tzutc -except e: - log = logging.getLogger(__name__) - log.warning("XEP-0082 plugin requires dateutil") +from sleekxmpp.thirdparty import tzutc, tzoffset, parse_iso # ===================================================================== @@ -31,7 +25,8 @@ def parse(time_str): Arguments: time_str -- A formatted timestamp string. """ - return parser.parse(time_str) + return parse_iso(time_str) + def format_date(time_obj): """ @@ -52,7 +47,7 @@ def format_time(time_obj): Return a formatted string version of a time object. format: - hh:mm:ss[.sss][TZD + hh:mm:ss[.sss][TZD] arguments: time_obj -- A time or datetime object. diff --git a/sleekxmpp/plugins/xep_0202/__init__.py b/sleekxmpp/plugins/xep_0202/__init__.py index 3fb4744d..a34b2376 100644 --- a/sleekxmpp/plugins/xep_0202/__init__.py +++ b/sleekxmpp/plugins/xep_0202/__init__.py @@ -6,23 +6,7 @@ See the file LICENSE for copying permission. """ -import logging -import sleekxmpp - -log = logging.getLogger(__name__) - - -HAVE_DATEUTIL = True -try: - import dateutil -except: - HAVE_DATEUTIL = False - - -if HAVE_DATEUTIL: - from sleekxmpp.plugins.xep_0202 import stanza - from sleekxmpp.plugins.xep_0202.stanza import EntityTime - from sleekxmpp.plugins.xep_0202.time import xep_0202 -else: - log.warning("XEP-0202 requires the dateutil package") +from sleekxmpp.plugins.xep_0202 import stanza +from sleekxmpp.plugins.xep_0202.stanza import EntityTime +from sleekxmpp.plugins.xep_0202.time import xep_0202 diff --git a/sleekxmpp/plugins/xep_0202/stanza.py b/sleekxmpp/plugins/xep_0202/stanza.py index 72ab403d..b6ccc960 100644 --- a/sleekxmpp/plugins/xep_0202/stanza.py +++ b/sleekxmpp/plugins/xep_0202/stanza.py @@ -11,12 +11,7 @@ import datetime as dt from sleekxmpp.xmlstream import ElementBase from sleekxmpp.plugins import xep_0082 - -try: - from dateutil.tz import tzutc -except: - log = logging.getLogger(__name__) - log.warning("XEP-0202 plugin requies dateutil package") +from sleekxmpp.thirdparty import tzutc, tzoffset class EntityTime(ElementBase): diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py index 3eb6ad73..1c7bf651 100644 --- a/sleekxmpp/thirdparty/__init__.py +++ b/sleekxmpp/thirdparty/__init__.py @@ -4,3 +4,4 @@ except: from sleekxmpp.thirdparty.ordereddict import OrderedDict from sleekxmpp.thirdparty import suelta +from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso diff --git a/sleekxmpp/thirdparty/mini_dateutil.py b/sleekxmpp/thirdparty/mini_dateutil.py new file mode 100644 index 00000000..bfb86305 --- /dev/null +++ b/sleekxmpp/thirdparty/mini_dateutil.py @@ -0,0 +1,267 @@ +# This module is a very stripped down version of the dateutil +# package for when dateutil has not been installed. As a replacement +# for dateutil.parser.parse, the parsing methods from +# http://blog.mfabrik.com/2008/06/30/relativity-of-time-shortcomings-in-python-datetime-and-workaround/ + +#As such, the following copyrights and licenses applies: + + +# dateutil - Extensions to the standard python 2.3+ datetime module. +# +# Copyright (c) 2003-2011 - Gustavo Niemeyer +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +# fixed_dateime +# +# Copyright (c) 2008, Red Innovation Ltd., Finland +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Red Innovation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +import re +import datetime + + +ZERO = datetime.timedelta(0) + + +try: + from dateutil.parser import parse as parse_iso + from dateutil.tz import tzoffset, tzutc +except: + # As a stopgap, define the two timezones here based + # on the dateutil code. + + class tzutc(datetime.tzinfo): + + def utcoffset(self, dt): + return ZERO + + def dst(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def __eq__(self, other): + return (isinstance(other, tzutc) or + (isinstance(other, tzoffset) and other._offset == ZERO)) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + class tzoffset(datetime.tzinfo): + + def __init__(self, name, offset): + self._name = name + self._offset = datetime.timedelta(seconds=offset) + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return ZERO + + def tzname(self, dt): + return self._name + + def __eq__(self, other): + return (isinstance(other, tzoffset) and + self._offset == other._offset) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__.__name__, + repr(self._name), + self._offset.days*86400+self._offset.seconds) + + __reduce__ = object.__reduce__ + + + _fixed_offset_tzs = { } + UTC = tzutc() + + def _get_fixed_offset_tz(offsetmins): + """For internal use only: Returns a tzinfo with + the given fixed offset. This creates only one instance + for each offset; the zones are kept in a dictionary""" + + if offsetmins == 0: + return UTC + + if not _fixed_offset_tzs.has_key(offsetmins): + if offsetmins < 0: + sign = '-' + absoff = -offsetmins + else: + sign = '+' + absoff = offsetmins + + name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60) + inst = tzoffset(offsetmins, name) + _fixed_offset_tzs[offsetmins] = inst + + return _fixed_offset_tzs[offsetmins] + + + _iso8601_parser = re.compile(""" + ^ + (?P [0-9]{4})?(?P-?)? + (?P[0-9]{2})?(?P=ymdsep)? + (?P [0-9]{2})? + + (?: # time part... optional... at least hour must be specified + (?:T|\s+)? + (?P[0-9]{2}) + (?: + # minutes, separated with :, or none, from hours + (?P[:]?) + (?P[0-9]{2}) + (?: + # same for seconds, separated with :, or none, from hours + (?P=hmssep) + (?P[0-9]{2}) + )? + )? + + # fractions + (?: [,.] (?P[0-9]{1,10}))? + + # timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there. + ( + (?PZ) + | + (?P[+-][0-9]{2}) + (?: :? # optional separator + (?P[0-9]{2}) + )? + )? + )? + $ + """, re.X) # """ + + def parse_iso(timestamp): + """Internal function for parsing a timestamp in + ISO 8601 format""" + + timestamp = timestamp.strip() + + m = _iso8601_parser.match(timestamp) + if not m: + raise ValueError("Not a proper ISO 8601 timestamp!: %s" % timestamp) + + vals = m.groupdict() + def_vals = {'year': 1970, 'month': 1, 'day': 1} + for key in vals: + if vals[key] is None: + vals[key] = def_vals.get(key, 0) + elif key not in ['ymdsep', 'hmssep', 'tzempty']: + vals[key] = int(vals[key]) + + year = vals['year'] + month = vals['month'] + day = vals['day'] + + h, min, s, us = None, None, None, 0 + frac = 0 + if m.group('tzempty') == None and m.group('tzh') == None: + raise ValueError("Not a proper ISO 8601 timestamp: " + + "missing timezone (Z or +hh[:mm])!") + + if m.group('frac'): + frac = m.group('frac') + power = len(frac) + frac = long(frac) / 10.0 ** power + + if m.group('hour'): + h = vals['hour'] + + if m.group('minute'): + min = vals['minute'] + + if m.group('second'): + s = vals['second'] + + if frac != None: + # ok, fractions of hour? + if min == None: + frac, min = _math.modf(frac * 60.0) + min = int(min) + + # fractions of second? + if s == None: + frac, s = _math.modf(frac * 60.0) + s = int(s) + + # and extract microseconds... + us = int(frac * 1000000) + + if m.group('tzempty') == 'Z': + offsetmins = 0 + else: + # timezone: hour diff with sign + offsetmins = vals['tzh'] * 60 + tzm = m.group('tzm') + + # add optional minutes + if tzm != None: + tzm = long(tzm) + offsetmins += tzm if offsetmins > 0 else -tzm + + tz = _get_fixed_offset_tz(offsetmins) + return datetime.datetime(year, month, day, h, min, s, us, tz) -- cgit v1.2.3