Add PEP8 check and fix related issues

- Add PEP8 section to tox.ini
- Add hacking to requirements to enforce OpenStack style requirements
- Change setup.py to use PBR
- Add setup.cfg
- Fix formatting issues flagged by flake8 check
- Add copyright notices to all remaining files
- Update .gitignore file
- Fix bug in expression.py where a variable was set incorrectly

Change-Id: I634adba3a44b2bcebb4d8c5620cbade28c6c489a
This commit is contained in:
Levi Blackstone 2015-05-05 10:19:53 -05:00
parent 686f7063e4
commit fb16776f9d
11 changed files with 243 additions and 162 deletions

5
.gitignore vendored
View File

@ -28,3 +28,8 @@ nosetests.xml
# Translations
*.mo
# IDE Project Files
*.project
*.pydev*
*.idea

View File

@ -1 +1,3 @@
-e .
hacking>=0.10.0,<0.11
ply
six>=1.5.2

View File

@ -1,3 +1,25 @@
[metadata]
description-file = README.md
name = timex
version = 0.21
author = Monsyne Dragon
author_email = mdragon@rackspace.com
summary = A time expressions library implementing a mini-language for manipulating datetimes
license = Apache-2
keywords =
datetime
manipulation
transformation
DSL
classifiers =
Development Status :: 3 - Alpha
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
home-page = https://github.com/stackforge/stacktach-timex
[files]
packages =
timex

View File

@ -1,49 +1,8 @@
import os
from setuptools import setup, find_packages
#!/usr/bin/env python
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
desc = """timex
=====
A time expressions library implementing a mini-language for manipulating
datetimes.
Much like regular expressions provide a mini-language for performing certain
operation on strings, Timex's time expressions provide a convenient way of
expressing datetime and date-range operations. These expressions are strings,
and can be safely read from a config file or user input.
Read README.md for syntax and examples.
"""
from setuptools import setup
setup(
name='timex',
version='0.20.0',
author='Monsyne Dragon',
author_email='mdragon@rackspace.com',
description=("A time expressions library implementing a mini-language "
"for manipulating datetimes"),
license='Apache License (2.0)',
keywords='datetime manipulation transformation DSL',
packages=find_packages(exclude=['tests']),
classifiers=[
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
],
url='https://github.com/stackforge/stacktach-timex',
scripts=[],
long_description=desc,
install_requires=[
"ply",
"six >= 1.5.2",
],
zip_safe=False
setup_requires=['pbr'],
pbr=True,
)

View File

@ -1,15 +1,29 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# 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 datetime
# for Python2.6 compatability.
import unittest2 as unittest
import mock
import six
from timex import expression
class TestTimestamp(unittest.TestCase):
def setUp(self):
super(TestTimestamp, self).setUp()
self.dt = datetime.datetime(2014, 8, 1, 2, 10, 23, 550)
@ -101,14 +115,18 @@ class TestTimestamp(unittest.TestCase):
self.assertEqual(res.timestamp, expected)
expected = datetime.datetime(2014, 8, 1, 0, 0, 0, 0)
res = self.timestamp % expression.Duration(hour=0, minute=0, second=0, microsecond=0)
res = self.timestamp % expression.Duration(hour=0, minute=0, second=0,
microsecond=0)
self.assertEqual(res.timestamp, expected)
def test_handle_ambig_duration(self):
d = expression.Duration(hour=10, unknown=2)
self.assertRaises(expression.TimexExpressionError, self.timestamp.__add__, d)
self.assertRaises(expression.TimexExpressionError, self.timestamp.__sub__, d)
self.assertRaises(expression.TimexExpressionError, self.timestamp.__mod__, d)
self.assertRaises(expression.TimexExpressionError,
self.timestamp.__add__, d)
self.assertRaises(expression.TimexExpressionError,
self.timestamp.__sub__, d)
self.assertRaises(expression.TimexExpressionError,
self.timestamp.__mod__, d)
def test_total_seconds(self):
self.assertFalse(self.timestamp.is_range)
@ -116,7 +134,6 @@ class TestTimestamp(unittest.TestCase):
class TestTimeRange(unittest.TestCase):
def setUp(self):
super(TestTimeRange, self).setUp()
self.begin_dt = datetime.datetime(2014, 8, 1, 2, 10, 23, 550)
@ -234,7 +251,6 @@ class TestTimeRange(unittest.TestCase):
self.assertEqual(res.begin, expected_begin)
self.assertEqual(res.end, expected_end)
def test_replace(self):
expected_begin = datetime.datetime(2014, 8, 1, 6, 10, 23, 550)
expected_end = datetime.datetime(2014, 8, 2, 6, 10, 23, 550)
@ -256,7 +272,8 @@ class TestTimeRange(unittest.TestCase):
expected_begin = datetime.datetime(2014, 8, 1, 0, 0, 0, 0)
expected_end = datetime.datetime(2014, 8, 2, 0, 0, 0, 0)
res = self.timerange % expression.Duration(hour=0, minute=0, second=0, microsecond=0)
res = self.timerange % expression.Duration(hour=0, minute=0, second=0,
microsecond=0)
self.assertEqual(res.begin, expected_begin)
self.assertEqual(res.end, expected_end)
@ -287,7 +304,6 @@ class TestTimeRange(unittest.TestCase):
class TestPinnedTimeRange(unittest.TestCase):
def setUp(self):
super(TestPinnedTimeRange, self).setUp()
self.begin_dt = datetime.datetime(2014, 8, 1, 1, 0, 0, 0)
@ -340,7 +356,6 @@ class TestPinnedTimeRange(unittest.TestCase):
class TestDuration(unittest.TestCase):
def setUp(self):
super(TestDuration, self).setUp()
self.second = expression.Duration(second=1)
@ -377,4 +392,3 @@ class TestDuration(unittest.TestCase):
self.assertEqual(dd[unit], 1)
for unit in ('microsecond', 'minute', 'month', 'year', 'unknown'):
self.assertNotIn(unit, dd)

View File

@ -1,15 +1,29 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# 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 datetime
# for Python2.6 compatability.
import unittest2 as unittest
import mock
import six
import timex
class TestParse(unittest.TestCase):
def setUp(self):
super(TestParse, self).setUp()
self.dt = datetime.datetime(2014, 8, 1, 2, 10, 23, 550)
@ -129,4 +143,3 @@ class TestParse(unittest.TestCase):
self.assertTrue(t.is_range)
self.assertEqual(t.begin, result_begin)
self.assertEqual(t.end, result_end)

View File

@ -1,7 +1,10 @@
from timex.expression import Duration
from timex.expression import PinnedTimeRange
from timex.expression import Timestamp
from timex.expression import TimeRange
from timex.expression import TimexExpressionError
from timex.expression import TimexParserError
from timex.expression import TimexLexerError
from timex.expression import TimexError
from timex.parser import parse
from timex.expression import TimexExpressionError, TimexParserError
from timex.expression import TimexLexerError, TimexError
from timex.expression import Timestamp, TimeRange, PinnedTimeRange, Duration
__version__ = '0.10.0'

View File

@ -1,6 +1,23 @@
import logging
import datetime
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# 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 abc
import datetime
import logging
import six
@ -25,7 +42,6 @@ class TimexExpressionError(TimexError):
@six.add_metaclass(abc.ABCMeta)
class TimeMatcher(object):
_allow_ambig_duration = False
@abc.abstractmethod
@ -64,7 +80,6 @@ class TimeMatcher(object):
return True
raise TimexExpressionError("Invalid duration for time operation")
def _dt_replace(self, dt, duration):
return dt.replace(**duration.as_dict)
@ -73,7 +88,8 @@ class TimeMatcher(object):
months = d.pop('month', 0)
years = d.pop('year', 0)
if d:
delta = datetime.timedelta(**dict((k+"s",val) for k, val in d.items()))
delta = datetime.timedelta(
**dict((k + "s", val) for k, val in d.items()))
dt = dt + delta
if months:
newmonth = dt.month + months
@ -89,7 +105,8 @@ class TimeMatcher(object):
months = d.pop('month', 0)
years = d.pop('year', 0)
if d:
delta = datetime.timedelta(**dict((k+"s",val) for k, val in d.items()))
delta = datetime.timedelta(
**dict((k + "s", val) for k, val in d.items()))
dt = dt - delta
if months:
newmonth = dt.month - months
@ -102,8 +119,7 @@ class TimeMatcher(object):
class Timestamp(TimeMatcher):
"""This is a wrapper on a datetime that has the same
interface as TimeRange"""
"""Wrapper on a datetime with same interface as TimeRange"""
def __init__(self, dt):
self.timestamp = dt
@ -136,7 +152,6 @@ class Timestamp(TimeMatcher):
class TimeRange(TimeMatcher):
_allow_ambig_duration = True
def __init__(self, begin, end):
@ -149,7 +164,8 @@ class TimeRange(TimeMatcher):
def total_seconds(self):
delta = self.end - self.begin
return delta.seconds + (delta.days * 24 * 3600) + (delta.microseconds * 1e-6)
return (delta.seconds + (delta.days * 24 * 3600) +
(delta.microseconds * 1e-6))
def __nonzero__(self):
return self.total_seconds() > 0
@ -159,7 +175,10 @@ class TimeRange(TimeMatcher):
return True
def match(self, dt):
"""TimeRanges match datetimes from begin (inclusive) to end (exclusive)"""
"""Match datetimes
TimeRanges match datetimes from begin (inclusive) to end (exclusive)
"""
return dt >= self.begin and dt < self.end
def __add__(self, other):
@ -236,11 +255,11 @@ class PinnedTimeRange(TimeRange):
return self.time_range
def __repr__(self):
return "PinnedTimeRange from %r to %r. Pinned to %s(%r)" % (self.begin, self.end, self.unit, self.pinned_to)
return ("PinnedTimeRange from %r to %r. Pinned to %s(%r)"
% (self.begin, self.end, self.unit, self.pinned_to))
class Environment(dict):
def time_func_hour(self, timestamp):
dt = timestamp.timestamp
begin = dt.replace(minute=0, second=0, microsecond=0)
@ -261,20 +280,20 @@ class Environment(dict):
def time_func_year(self, timestamp):
dt = timestamp.timestamp
begin = dt.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
begin = dt.replace(month=1, day=1, hour=0, minute=0, second=0,
microsecond=0)
end = Timestamp(begin) + Duration(year=1)
return PinnedTimeRange(begin, end.timestamp, dt, 'year')
@six.add_metaclass(abc.ABCMeta)
class TimeExpression(object):
@abc.abstractmethod
def apply(self, env):
"""Apply the expression to a given set of arguments.
:param env: a dictionary-like object. expression functions should be methods
on this object with names beginning with 'time_func_'
:param env: a dictionary-like object. expression functions should
be methods on this object with names beginning with 'time_func_'
returns: TimeMatcher instance
"""
@ -308,7 +327,8 @@ class TimeRangeFunction(TimeRangeExpression):
self.expr = expr
def __repr__(self):
return '%s %s(%r)' % (self.__class__.__name__, self.func_name, self.expr)
return ('%s %s(%r)'
% (self.__class__.__name__, self.func_name, self.expr))
def apply(self, env):
arg = self.expr.apply(env)
@ -341,7 +361,8 @@ class Operation(TimeExpression):
self.duration = duration
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.expr, self.duration)
return ('%s(%r, %r)'
% (self.__class__.__name__, self.expr, self.duration))
class Replace(Operation):
@ -366,7 +387,6 @@ class Minus(Operation):
class Duration(object):
UNIT_SIZES = {'year': 365 * 24 * 60 * 60,
'month': 28 * 24 * 60 * 60,
'day': 24 * 60 * 60,
@ -423,7 +443,7 @@ class Duration(object):
elif d >= self.UNIT_SIZES['minute']:
unit = 'second'
else:
unit = microsecond
unit = 'microsecond'
vals = self.as_dict
del vals['unknown']
if unit in vals:
@ -476,4 +496,3 @@ class Duration(object):
if our_val != other_val:
return False
return True

View File

@ -1,5 +1,22 @@
import sys
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# 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 logging
import sys
import ply.lex
@ -16,8 +33,8 @@ class TimexLexer(object):
self.debug = debug
if not self.__doc__:
raise TimexLexerError("Docstring information is missing. "
"Timex uses PLY which requires docstrings for "
"configuration.")
"Timex uses PLY which requires "
"docstrings for configuration.")
self.lexer = ply.lex.lex(module=self,
debug=self.debug,
errorlog=logger)
@ -36,7 +53,8 @@ class TimexLexer(object):
token.col = token.lexpos - self.latest_newline
return token
reserved_words = { 'to' : 'TO',
reserved_words = {
'to': 'TO',
'us': 'MICROSECOND',
's': 'SECOND',
'sec': 'SECOND',
@ -76,7 +94,6 @@ class TimexLexer(object):
t.value = int(t.value)
return t
def t_newline(self, t):
r'\n+'
t.lexer.lineno += len(t.value)
@ -99,4 +116,3 @@ if __name__ == '__main__':
while token:
print('%-20s%s' % (token.value, token.type))
token = lexer.token()

View File

@ -1,14 +1,34 @@
import sys, os
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# 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 logging
import os
import ply.yacc
from timex.lexer import TimexLexer
from timex.expression import Duration
from timex.expression import Minus
from timex.expression import Plus
from timex.expression import Replace
from timex.expression import TimeRangeExpression
from timex.expression import TimeRangeFunction
from timex.expression import TimexParserError
from timex.expression import Replace, Plus, Minus
from timex.expression import Duration, Variable
from timex.expression import TimeRangeFunction, TimeRangeExpression
from timex.expression import Variable
from timex.lexer import TimexLexer
"""
@ -53,7 +73,6 @@ unit : SECOND
"""
logger = logging.getLogger(__name__)
@ -70,15 +89,15 @@ class TimexParser(object):
self.start = start
if not self.__doc__:
raise TimexParserError("Docstring information is missing. "
"Timex uses PLY which requires docstrings for "
"configuration.")
"Timex uses PLY which requires "
"docstrings for configuration.")
self.lexer_class = lexer_class or TimexLexer
def _parse_table(self):
tabdir = os.path.dirname(__file__)
try:
module_name = os.path.splitext(os.path.split(__file__)[1])[0]
except:
except Exception:
module_name = __name__
table_module = '_'.join([module_name, self.start, 'parsetab'])
return (tabdir, table_module)
@ -189,7 +208,8 @@ class TimexParser(object):
| HOUR
| DAY
| MONTH
| YEAR"""
| YEAR
"""
unit = TimexLexer.reserved_words[p[1]]
unit = unit.lower()
p[0] = unit

10
tox.ini
View File

@ -1,5 +1,5 @@
[tox]
envlist = py26,py27
envlist = py26,py27,pep8
[testenv]
deps =
@ -13,3 +13,11 @@ commands =
sitepackages = False
[testenv:pep8]
commands =
flake8
[flake8]
ignore = H405
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,timex/__init__.py
show-source = True