diff --git a/README.md b/README.md index bd6620d..3d87489 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,6 @@ notification_utils ================== Utilities for dealing with OpenStack Notifications + +Includes datetime <-> Decimal conversion and Json marshalling handlers +to name a few (for now) diff --git a/notification_utils/__init__.py b/notification_utils/__init__.py new file mode 100644 index 0000000..862ce44 --- /dev/null +++ b/notification_utils/__init__.py @@ -0,0 +1,65 @@ +# Copyright (c) 2014 Dark Secret Software Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import calendar +import collections +import datetime +import decimal +import json + + +def now(): + """Broken out for testing.""" + return datetime.datetime.utcnow() + + +def dt_to_decimal(utc): + decimal.getcontext().prec = 30 + return decimal.Decimal(str(calendar.timegm(utc.utctimetuple()))) + \ + (decimal.Decimal(str(utc.microsecond)) / + decimal.Decimal("1000000.0")) + + +def dt_from_decimal(dec): + if dec == None: + return "n/a" + integer = int(dec) + micro = (dec - decimal.Decimal(integer)) * decimal.Decimal(1000000) + + daittyme = datetime.datetime.utcfromtimestamp(integer) + return daittyme.replace(microsecond=micro) + + +class DateTimeEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, datetime.datetime): + if obj.utcoffset() is not None: + obj = obj - obj.utcoffset() + return str(dt_to_decimal(obj)) + return super(DateTimeEncoder, self).default(obj) + + +# This is a hack for comparing structures load'ed from json +# (which are always unicode) back to strings. It's used +# for assertEqual() in the tests and is very slow and expensive. +def unicode_to_string(data): + if isinstance(data, basestring): + return str(data) + elif isinstance(data, collections.Mapping): + return dict(map(unicode_to_string, data.iteritems())) + elif isinstance(data, collections.Iterable): + return type(data)(map(unicode_to_string, data)) + else: + return data diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..80ab0c7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +dateutils diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..549d312 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,25 @@ +[metadata] +name = notification_utils +author = Dark Secret Software Inc. +author-email = admin@darksecretsoftware.com +summary = Utilities for dealing with OpenStack Notifications +description-file = README.md +license = Apache-2 +classifier = + Development Status :: 2 - Pre-Alpha + Environment :: Console + Intended Audience :: Developers + Intended Audience :: Information Technology + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python + Topic :: Software Development :: Libraries :: Python Modules +home-page = https://github.com/StackTach/notification_utils +keywords = + openstack + notifications + events + utilities +[files] +packages = + notification_utils diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..aa2d8a0 --- /dev/null +++ b/setup.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup( + setup_requires=['pbr'], + pbr=True, +) diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 0000000..f3c7e8e --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1 @@ +nose diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..caab86e --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,43 @@ +import datetime +import decimal +import unittest + +import dateutil.tz + +import notification_utils + + +class TestUtils(unittest.TestCase): + def setUp(self): + self.handler = notification_utils.DateTimeEncoder() + + def test_handle_datetime_non_datetime(self): + self.assertRaises(TypeError, self.handler.default, "text") + + def test_handle_datetime(self): + now = datetime.datetime(day=1, month=2, year=2014, + hour=10, minute=11, second=12) + self.assertEqual("1391249472", self.handler.default(now)) + + def test_handle_datetime_offset(self): + now = datetime.datetime(day=1, month=2, year=2014, + hour=10, minute=11, second=12, + tzinfo=dateutil.tz.tzoffset(None, 4*60*60)) + self.assertEqual("1391220672", self.handler.default(now)) + + +class TestDatetimeToDecimal(unittest.TestCase): + def test_datetime_to_decimal(self): + expected_decimal = decimal.Decimal('1356093296.123') + utc_datetime = datetime.datetime.utcfromtimestamp(expected_decimal) + actual_decimal = notification_utils.dt_to_decimal(utc_datetime) + self.assertEqual(actual_decimal, expected_decimal) + + def test_decimal_to_datetime(self): + expected_decimal = decimal.Decimal('1356093296.123') + expected_datetime = datetime.datetime.utcfromtimestamp(expected_decimal) + actual_datetime = notification_utils.dt_from_decimal(expected_decimal) + self.assertEqual(actual_datetime, expected_datetime) + + def test_dt_from_decimal_none(self): + self.assertEqual("n/a",notification_utils.dt_from_decimal(None)) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..efc494f --- /dev/null +++ b/tox.ini @@ -0,0 +1,14 @@ +[tox] +envlist = py26,py27 + +[testenv] +deps = + -r{toxinidir}/requirements.txt + -r{toxinidir}/test_requirements.txt + +setenv = VIRTUAL_ENV={envdir} + +commands = + nosetests tests + +sitepackages = False