3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
23 A library for working with CronInfo records, describing cron entries for an
24 application. Supports loading the records from yaml.
43 from google
.appengine
.cron
import groc
44 from google
.appengine
.cron
import groctimespecification
45 from google
.appengine
.api
import appinfo
46 from google
.appengine
.api
import validation
47 from google
.appengine
.api
import yaml_builder
48 from google
.appengine
.api
import yaml_listener
49 from google
.appengine
.api
import yaml_object
52 _TIMEZONE_REGEX
= r
'^.{0,100}$'
53 _DESCRIPTION_REGEX
= ur
'^.{0,499}$'
56 SERVER_ID_RE_STRING
= r
'(?!-)[a-z\d\-]{1,63}'
59 SERVER_VERSION_RE_STRING
= r
'(?!-)[a-z\d\-]{1,100}'
60 _VERSION_REGEX
= r
'^(?:(?:(%s):)?)(%s)$' % (SERVER_ID_RE_STRING
,
61 SERVER_VERSION_RE_STRING
)
66 class GrocValidator(validation
.Validator
):
67 """Checks that a schedule is in valid groc format."""
69 def Validate(self
, value
, key
=None):
70 """Validates a schedule."""
72 raise validation
.MissingAttribute('schedule must be specified')
73 if not isinstance(value
, basestring
):
74 raise TypeError('schedule must be a string, not \'%r\''%type(value
))
76 groctimespecification
.GrocTimeSpecification(value
)
77 except groc
.GrocException
, e
:
78 raise validation
.ValidationError('schedule \'%s\' failed to parse: %s'%(
83 class TimezoneValidator(validation
.Validator
):
84 """Checks that a timezone can be correctly parsed and is known."""
86 def Validate(self
, value
, key
=None):
87 """Validates a timezone."""
91 if not isinstance(value
, basestring
):
92 raise TypeError('timezone must be a string, not \'%r\'' % type(value
))
98 except pytz
.UnknownTimeZoneError
:
99 raise validation
.ValidationError('timezone \'%s\' is unknown' % value
)
107 unused_e
, v
, t
= sys
.exc_info()
108 logging
.warning('pytz raised an unexpected error: %s.\n' % (v
) +
109 'Traceback:\n' + '\n'.join(traceback
.format_tb(t
)))
117 SCHEDULE
= 'schedule'
118 TIMEZONE
= 'timezone'
119 DESCRIPTION
= 'description'
123 class MalformedCronfigurationFile(Exception):
124 """Configuration file for Cron is malformed."""
128 class CronEntry(validation
.Validated
):
129 """A cron entry describes a single cron job."""
132 SCHEDULE
: GrocValidator(),
133 TIMEZONE
: TimezoneValidator(),
134 DESCRIPTION
: validation
.Optional(_DESCRIPTION_REGEX
),
135 TARGET
: validation
.Optional(_VERSION_REGEX
),
139 class CronInfoExternal(validation
.Validated
):
140 """CronInfoExternal describes all cron entries for an application."""
142 appinfo
.APPLICATION
: validation
.Optional(appinfo
.APPLICATION_RE_STRING
),
143 CRON
: validation
.Optional(validation
.Repeated(CronEntry
))
147 def LoadSingleCron(cron_info
, open_fn
=None):
148 """Load a cron.yaml file or string and return a CronInfoExternal object."""
149 builder
= yaml_object
.ObjectBuilder(CronInfoExternal
)
150 handler
= yaml_builder
.BuilderHandler(builder
)
151 listener
= yaml_listener
.EventListener(handler
)
152 listener
.Parse(cron_info
)
154 cron_info
= handler
.GetResults()
155 if len(cron_info
) < 1:
156 raise MalformedCronfigurationFile('Empty cron configuration.')
157 if len(cron_info
) > 1:
158 raise MalformedCronfigurationFile('Multiple cron sections '