Skip to content

Commit

Permalink
Merge pull request #29 from willkg/add-ooid
Browse files Browse the repository at this point in the history
Add ooid module and tests from socorrolib
  • Loading branch information
willkg committed May 25, 2016
2 parents e837e1b + 0126fe2 commit 6772024
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 0 deletions.
3 changes: 3 additions & 0 deletions antenna/lib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
33 changes: 33 additions & 0 deletions antenna/lib/datetimeutil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# This is based on socorrolib's socorrolib/lib/datetimeutil.py module.

import datetime

import isodate

UTC = isodate.UTC


def utc_now():
"""Return a timezone aware datetime instance in UTC timezone
This funciton is mainly for convenience. Compare:
>>> from datetimeutil import utc_now
>>> utc_now()
datetime.datetime(2012, 1, 5, 16, 42, 13, 639834,
tzinfo=<isodate.tzinfo.Utc object at 0x101475210>)
Versus:
>>> import datetime
>>> from datetimeutil import UTC
>>> datetime.datetime.now(UTC)
datetime.datetime(2012, 1, 5, 16, 42, 13, 639834,
tzinfo=<isodate.tzinfo.Utc object at 0x101475210>)
"""
return datetime.datetime.now(UTC)
112 changes: 112 additions & 0 deletions antenna/lib/ooid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# This is based on socorrolib's socorrolib/lib/ooid.py module.

# OOID is "Our opaque ID"
import uuid
from datetime import datetime

from antenna.lib.datetimeutil import utc_now, UTC


DEFAULT_DEPTH = 2
OLD_HARD_DEPTH = 4


def create_new_ooid(timestamp=None, depth=None):
"""Create a new Ooid for a given time, to be stored at a given depth
:arg timestamp: the year-month-day is encoded in the ooid. If none, use
current day.
:arg depth: the expected storage depth is encoded in the ooid. If non, use
the DEFAULT_DEPTH returns a new opaque id string holding 24 random hex
digits and encoded date and depth info
"""
if not timestamp:
timestamp = utc_now().date()
if not depth:
depth = DEFAULT_DEPTH
assert depth <= 4 and depth >= 1
id_ = str(uuid.uuid4())
return "%s%d%02d%02d%02d" % (
id_[:-7], depth, timestamp.year % 100, timestamp.month, timestamp.day
)


def uuid_to_ooid(uuid, timestamp=None, depth=None):
"""Create an ooid from a 32-hex-digit string in regular uuid format.
:arg uuid: must be uuid in expected format:
``xxxxxxxx-xxxx-xxxx-xxxx-xxxxx7777777``
:arg timestamp: the year-month-day is encoded in the ooid. If none, use
current day
:arg depth: the expected storage depth is encoded in the ooid. If non, use
the DEFAULT_DEPTH returns a new opaque id string holding the first 24
digits of the provided uuid and encoded date and depth info
"""
if not timestamp:
timestamp = utc_now().date()
if not depth:
depth = DEFAULT_DEPTH
assert depth <= 4 and depth >= 1
return "%s%d%02d%02d%02d" % (
uuid[:-7], depth, timestamp.year % 100, timestamp.month, timestamp.day
)


def date_and_depth_from_ooid(ooid):
"""Extract the encoded date and expected storage depth from an ooid.
:arg ooid: The ooid from which to extract the info
:returns: ``(datetime(yyyy, mm, dd), depth)`` if the ooid is in expected
format else ``(None, None)``
"""
year = month = day = None
try:
day = int(ooid[-2:])
except Exception:
return None, None
try:
month = int(ooid[-4:-2])
except Exception:
return None, None
try:
year = 2000 + int(ooid[-6:-4])
depth = int(ooid[-7])
if not depth:
depth = OLD_HARD_DEPTH
return (datetime(year, month, day, tzinfo=UTC), depth)
except Exception:
return None, None
return None, None


def depth_from_ooid(ooid):
"""Extract the encoded expected storage depth from an ooid.
:arg ooid: The ooid from which to extract the info
:returns: expected depth if the ooid is in expected format else None
"""
return date_and_depth_from_ooid(ooid)[1]


def date_from_ooid(ooid):
"""Extract the encoded date from an ooid.
:arg ooid: The ooid from which to extract the info
:returns: encoded date if the ooid is in expected format else None
"""
return date_and_depth_from_ooid(ooid)[0]
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ falcon==1.0.0 --hash=sha256:0145eb7fc001fb943b8b8f797832ce45302cf7c1edc769e28e5b
gevent==1.1.1 --hash=sha256:6ee5b9851b2acde08df7ab9b9a2903f58b4b0e555405c444f4b1dd16f71caeea
greenlet==0.4.9 --hash=sha256:58b2f3a2e7075c655616bf95e82868db4980f3bb6661db70ad02a51e4ddd2252
gunicorn==19.5.0 --hash=sha256:44cda4183586467493deaa616ac16f42b2f33bfaa57d4d5c07999d6db78c40ec
isodate==0.5.4 --hash=sha256:42105c41d037246dc1987e36d96f3752ffd5c0c24834dd12e4fdbe1e79544e31
python-mimeparse==1.5.2 --hash=sha256:bef134a59598cc6aa598f84553162aa7a0c01f3f431588225bb9a208964b1827
six==1.10.0 --hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1
17 changes: 17 additions & 0 deletions tests/test_datetimeutil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# This is based on socorrolib's socorrolib/unittest/lib/test_datetimeutil.py.

from antenna.lib import datetimeutil


UTC = datetimeutil.UTC


def test_utc_now():
res = datetimeutil.utc_now()
assert res.strftime('%Z') == 'UTC'
assert res.strftime('%z') == '+0000'
assert res.tzinfo
125 changes: 125 additions & 0 deletions tests/test_ooid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# This is based on socorrolib's socorrolib/unittest/lib/testOoid.py module
# with a heavy flake8/pytest pass.

import uuid
from datetime import datetime
from unittest import TestCase

from antenna.lib.datetimeutil import utc_now, UTC
from antenna.lib.ooid import (
create_new_ooid,
date_from_ooid,
date_and_depth_from_ooid,
depth_from_ooid,
DEFAULT_DEPTH,
uuid_to_ooid,
)


class Test_ooid(TestCase):
def setUp(self):
self.base_date = datetime(2008, 12, 25, tzinfo=UTC)
self.rawuuids = []
self.yyyyoids = []
self.dyyoids = []
self.depths = [4, 4, 3, 3, 3, 2, 2, 2, 1, 1]
self.badooid0 = "%s%s" % (str(uuid.uuid4())[:-8], 'ffeea1b2')
self.badooid1 = "%s%s" % (str(uuid.uuid4())[:-8], 'f3eea1b2')

for i in range(10):
self.rawuuids.append(str(uuid.uuid4()))
assert len(self.depths) == len(self.rawuuids)

for i in self.rawuuids:
self.yyyyoids.append("%s%4d%02d%02d" % (
i[:-8],
self.base_date.year,
self.base_date.month,
self.base_date.day
))

for i in range(len(self.rawuuids)):
self.dyyoids.append("%s%d%02d%02d%02d" % (
self.rawuuids[i][:-7],
self.depths[i],
self.base_date.year % 100,
self.base_date.month,
self.base_date.day
))

today = utc_now()
self.nowstamp = datetime(
today.year, today.month, today.day, tzinfo=UTC
)
self.xmas05 = datetime(2005, 12, 25, tzinfo=UTC)

def test_create_new_ooid(self):
ooid = create_new_ooid()
assert date_from_ooid(ooid) == self.nowstamp
assert depth_from_ooid(ooid) == DEFAULT_DEPTH

ooid = create_new_ooid(timestamp=self.xmas05)
assert date_from_ooid(ooid) == self.xmas05
assert depth_from_ooid(ooid) == DEFAULT_DEPTH

for d in range(1, 5):
ooid0 = create_new_ooid(depth=d)
ooid1 = create_new_ooid(timestamp=self.xmas05, depth=d)

ndepth0 = depth_from_ooid(ooid0)
ndepth1 = depth_from_ooid(ooid1)

assert date_from_ooid(ooid0) == self.nowstamp
assert date_from_ooid(ooid1) == self.xmas05
assert ndepth0 == ndepth1
assert ndepth0 == d

assert depth_from_ooid(self.badooid0) is None
assert depth_from_ooid(self.badooid1) is None

def test_uuid_to_ooid(self):
for i in range(len(self.rawuuids)):
u = self.rawuuids[i]

o0 = uuid_to_ooid(u)
assert date_and_depth_from_ooid(o0) == (
self.nowstamp, DEFAULT_DEPTH
)

o1 = uuid_to_ooid(u, timestamp=self.base_date)
assert date_and_depth_from_ooid(o1) == (
self.base_date, DEFAULT_DEPTH
)

o2 = uuid_to_ooid(u, depth=self.depths[i])
assert date_and_depth_from_ooid(o2) == (
self.nowstamp, self.depths[i]
)

o3 = uuid_to_ooid(
u, depth=self.depths[i], timestamp=self.xmas05
)
assert date_and_depth_from_ooid(o3) == (
self.xmas05, self.depths[i]
)

def test_date_from_ooid(self):
for ooid in self.yyyyoids:
assert date_from_ooid(ooid) == self.base_date
assert depth_from_ooid(ooid) == 4

assert date_from_ooid(self.badooid0) is None
assert date_from_ooid(self.badooid1) is None

def test_date_and_depth_from_ooid(self):
for i in range(len(self.dyyoids)):
date, depth = date_and_depth_from_ooid(self.dyyoids[i])
assert depth == self.depths[i]
assert date == self.base_date

assert date_and_depth_from_ooid(self.badooid0) == (None, None)
assert date_and_depth_from_ooid(self.badooid1) == (None, None)

0 comments on commit 6772024

Please sign in to comment.