Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Python 2.4 #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions crontab/_crontab.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

'''
crontab.py

Expand All @@ -10,7 +9,36 @@

'''

from collections import namedtuple
# namedtuple is not supported in python 2.4.
# If not included in this project, download the 'Named tuples' python recipe from
# http://code.activestate.com/recipes/500261/
# save the recipe as namedtuple24.py

try:
from collections import namedtuple
except:
from namedtuple24 import namedtuple

# define 'any'. It was introduced in python 2.5
try:
all
except NameError:
def all(iterable):
for i in iterable:
if not i:
return False
return True

# define 'all'. It was introduced in python 2.5
try:
any
except NameError:
def any(s):
for v in s:
if v:
return True
return False

import datetime
import sys

Expand Down Expand Up @@ -82,6 +110,20 @@ def _year_incr(dt, m):
return YEAR + DAY
return YEAR

# lambda dt,x: dt.replace(day=1) if x > DAY else dt,
def _month_rollover(dt, x):
if x > DAY:
return dt.replace(day=1)
else:
return dt

# lambda dt,x: dt.replace(month=1) if x > DAY else dt,
def _year_rollover(dt, x):
if x > DAY:
return dt.replace(month=1)
else:
return dt

_increments = [
lambda *a: MINUTE,
lambda *a: HOUR,
Expand All @@ -91,8 +133,8 @@ def _year_incr(dt, m):
_year_incr,
lambda dt,x: dt.replace(minute=0),
lambda dt,x: dt.replace(hour=0),
lambda dt,x: dt.replace(day=1) if x > DAY else dt,
lambda dt,x: dt.replace(month=1) if x > DAY else dt,
_month_rollover,
_year_rollover,
lambda dt,x: dt,
]

Expand Down Expand Up @@ -129,6 +171,13 @@ def _day_decr_reset(dt, x):
dt += DAY
return dt - DAY

# lambda dt,x: dt.replace(month=12)if x < -DAY else dt,
def _year_rollunder(dt, x):
if x < -DAY:
return dt.replace(month=12)
else:
return dt

_decrements = [
lambda *a: -MINUTE,
lambda *a: -HOUR,
Expand All @@ -139,7 +188,7 @@ def _day_decr_reset(dt, x):
lambda dt,x: dt.replace(minute=59),
lambda dt,x: dt.replace(hour=23),
_day_decr_reset,
lambda dt,x: dt.replace(month=12)if x < -DAY else dt,
_year_rollunder,
lambda dt,x: dt,
]

Expand Down
152 changes: 152 additions & 0 deletions crontab/namedtuple24.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Recipe URL: http://code.activestate.com/recipes/500261/
# Recipe Author: Raymond Hettinger
# Recipe License: PSF ( http://docs.python.org/2/license.html )
# Recipe Version: Recipe 500261 revision 15

from operator import itemgetter as _itemgetter
from keyword import iskeyword as _iskeyword
import sys as _sys

def namedtuple(typename, field_names, verbose=False, rename=False):
"""Returns a new subclass of tuple with named fields.

>>> Point = namedtuple('Point', 'x y')
>>> Point.__doc__ # docstring for the new class
'Point(x, y)'
>>> p = Point(11, y=22) # instantiate with positional args or keywords
>>> p[0] + p[1] # indexable like a plain tuple
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessable by name
33
>>> d = p._asdict() # convert to a dictionary
>>> d['x']
11
>>> Point(**d) # convert from a dictionary
Point(x=11, y=22)
>>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
Point(x=100, y=22)

"""

# Parse and validate the field names. Validation serves two purposes,
# generating informative error messages and preventing template injection attacks.
if isinstance(field_names, basestring):
field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
field_names = tuple(map(str, field_names))
if rename:
names = list(field_names)
seen = set()
for i, name in enumerate(names):
if (not min(c.isalnum() or c=='_' for c in name) or _iskeyword(name)
or not name or name[0].isdigit() or name.startswith('_')
or name in seen):
names[i] = '_%d' % i
seen.add(name)
field_names = tuple(names)
for name in (typename,) + field_names:
if not min(c.isalnum() or c=='_' for c in name):
raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
if _iskeyword(name):
raise ValueError('Type names and field names cannot be a keyword: %r' % name)
if name[0].isdigit():
raise ValueError('Type names and field names cannot start with a number: %r' % name)
seen_names = set()
for name in field_names:
if name.startswith('_') and not rename:
raise ValueError('Field names cannot start with an underscore: %r' % name)
if name in seen_names:
raise ValueError('Encountered duplicate field name: %r' % name)
seen_names.add(name)

# Create and fill-in the class template
numfields = len(field_names)
argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes
reprtxt = ', '.join('%s=%%r' % name for name in field_names)
template = '''class %(typename)s(tuple):
'%(typename)s(%(argtxt)s)' \n
__slots__ = () \n
_fields = %(field_names)r \n
def __new__(_cls, %(argtxt)s):
return _tuple.__new__(_cls, (%(argtxt)s)) \n
@classmethod
def _make(cls, iterable, new=tuple.__new__, len=len):
'Make a new %(typename)s object from a sequence or iterable'
result = new(cls, iterable)
if len(result) != %(numfields)d:
raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
return result \n
def __repr__(self):
return '%(typename)s(%(reprtxt)s)' %% self \n
def _asdict(self):
'Return a new dict which maps field names to their values'
return dict(zip(self._fields, self)) \n
def _replace(_self, **kwds):
'Return a new %(typename)s object replacing specified fields with new values'
result = _self._make(map(kwds.pop, %(field_names)r, _self))
if kwds:
raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
return result \n
def __getnewargs__(self):
return tuple(self) \n\n''' % locals()
for i, name in enumerate(field_names):
template += ' %s = _property(_itemgetter(%d))\n' % (name, i)
if verbose:
print template

# Execute the template string in a temporary namespace
namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
_property=property, _tuple=tuple)
try:
exec template in namespace
except SyntaxError, e:
raise SyntaxError(e.message + ':\n' + template)
result = namespace[typename]

# For pickling to work, the __module__ variable needs to be set to the frame
# where the named tuple is created. Bypass this step in enviroments where
# sys._getframe is not defined (Jython for example) or sys._getframe is not
# defined for arguments greater than 0 (IronPython).
try:
result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass

return result






if __name__ == '__main__':
# verify that instances can be pickled
from cPickle import loads, dumps
Point = namedtuple('Point', 'x, y', True)
p = Point(x=10, y=20)
assert p == loads(dumps(p, -1))

# test and demonstrate ability to override methods
class Point(namedtuple('Point', 'x y')):
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)

for p in Point(3,4), Point(14,5), Point(9./7,6):
print p

class Point(namedtuple('Point', 'x y')):
'Point class with optimized _make() and _replace() without error-checking'
_make = classmethod(tuple.__new__)
def _replace(self, _map=map, **kwds):
return self._make(_map(kwds.get, ('x', 'y'), self))

print Point(11, 22)._replace(x=100)

import doctest
TestResults = namedtuple('TestResults', 'failed attempted')
print TestResults(*doctest.testmod())