From a2b97938deeb5f9d771e01044a7c49d6478369af Mon Sep 17 00:00:00 2001 From: markcox Date: Wed, 13 Mar 2013 17:18:22 +1100 Subject: [PATCH 1/3] Update _crontab.py Changes to support python 2.4 --- crontab/_crontab.py | 56 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/crontab/_crontab.py b/crontab/_crontab.py index 5266fad..597d374 100644 --- a/crontab/_crontab.py +++ b/crontab/_crontab.py @@ -10,7 +10,34 @@ ''' -from collections import namedtuple +# namedtuple is not supported in python 2.4. +# download the 'Named tuples' python recipe from http://code.activestate.com/recipes/500261/ +# save the reciple as namedtuple.py and it should work. +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 @@ -82,6 +109,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, @@ -91,8 +132,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, ] @@ -129,6 +170,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, @@ -139,7 +187,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, ] From a65720e63e1ff13e339ba61b0324d65b2423c8b9 Mon Sep 17 00:00:00 2001 From: markcox Date: Wed, 13 Mar 2013 17:22:13 +1100 Subject: [PATCH 2/3] Update _crontab.py fix comment so it states the correct filename for the 'namedtuple' recipe. --- crontab/_crontab.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crontab/_crontab.py b/crontab/_crontab.py index 597d374..f184d6a 100644 --- a/crontab/_crontab.py +++ b/crontab/_crontab.py @@ -1,4 +1,3 @@ - ''' crontab.py @@ -11,8 +10,10 @@ ''' # namedtuple is not supported in python 2.4. -# download the 'Named tuples' python recipe from http://code.activestate.com/recipes/500261/ -# save the reciple as namedtuple.py and it should work. +# 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 094ea229d0416812b7019fc85825ae33120182b2 Mon Sep 17 00:00:00 2001 From: markcox Date: Wed, 13 Mar 2013 17:24:32 +1100 Subject: [PATCH 3/3] Create namedtuple24.py Add 'namedtuple' recipe from http://code.activestate.com/recipes/500261/ --- crontab/namedtuple24.py | 152 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 crontab/namedtuple24.py diff --git a/crontab/namedtuple24.py b/crontab/namedtuple24.py new file mode 100644 index 0000000..0f1a85e --- /dev/null +++ b/crontab/namedtuple24.py @@ -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())