From a03305431d1cb48ec82ad550c2704ad0548a9988 Mon Sep 17 00:00:00 2001 From: Pawel Solyga Date: Sun, 12 Apr 2009 13:22:43 +0000 Subject: [PATCH] Load /Users/solydzajs/Downloads/google_appengine into trunk/thirdparty/google_appengine. --- thirdparty/google_appengine/RELEASE_NOTES | 10 ++ thirdparty/google_appengine/VERSION | 4 +- .../google/appengine/cron/groctimespecification.py | 16 ++-- .../google/appengine/ext/admin/__init__.py | 67 ++++++++++++- .../google/appengine/ext/admin/templates/base.html | 3 + .../google/appengine/ext/admin/templates/cron.html | 85 +++++++++++++++++ .../appengine/ext/admin/templates/css/ae.css | 105 +++++++++++++++++++++ .../appengine/ext/admin/templates/css/cron.css | 26 +++++ .../google/appengine/tools/appcfg.py | 36 ++++--- .../google/appengine/tools/dev_appserver.py | 51 +++++++++- .../google/appengine/tools/dev_appserver_main.py | 27 ++++-- 11 files changed, 388 insertions(+), 42 deletions(-) create mode 100644 thirdparty/google_appengine/google/appengine/ext/admin/templates/cron.html create mode 100644 thirdparty/google_appengine/google/appengine/ext/admin/templates/css/cron.css diff --git a/thirdparty/google_appengine/RELEASE_NOTES b/thirdparty/google_appengine/RELEASE_NOTES index 1d09a016..56460a67 100644 --- a/thirdparty/google_appengine/RELEASE_NOTES +++ b/thirdparty/google_appengine/RELEASE_NOTES @@ -3,6 +3,16 @@ All rights reserved. App Engine SDK - Release Notes +Version 1.2.0 - March 24, 2009 +============================== + - Cron support. Appcfg.py will upload the schedule to App Engine. + The dev_appserver console at /_ah/admin describes your schedule but does + not automatically run scheduled jobs. Learn more at + http://code.google.com/appengine/docs/python/config/cron.html + - New allow_skipped_files flag in dev_appserver to allow it to read files + which are not available in App Engine. + http://code.google.com/p/googleappengine/issues/detail?id=550 + Version 1.1.9 - February 2, 2009 ================================ diff --git a/thirdparty/google_appengine/VERSION b/thirdparty/google_appengine/VERSION index 33b135af..ec039ff8 100644 --- a/thirdparty/google_appengine/VERSION +++ b/thirdparty/google_appengine/VERSION @@ -1,3 +1,3 @@ -release: "1.1.9" -timestamp: 1232676672 +release: "1.2.0" +timestamp: 1236791960 api_versions: ['1'] diff --git a/thirdparty/google_appengine/google/appengine/cron/groctimespecification.py b/thirdparty/google_appengine/google/appengine/cron/groctimespecification.py index 442ee3d6..46252704 100755 --- a/thirdparty/google_appengine/google/appengine/cron/groctimespecification.py +++ b/thirdparty/google_appengine/google/appengine/cron/groctimespecification.py @@ -67,10 +67,13 @@ def GrocTimeSpecification(schedule): parser.timespec() if parser.interval_mins: - return IntervalTimeSpecification(parser.interval_mins, parser.period_string) + return IntervalTimeSpecification(parser.interval_mins, + parser.period_string) else: return SpecificTimeSpecification(parser.ordinal_set, parser.weekday_set, - parser.month_set, None, parser.time_string) + parser.month_set, + None, + parser.time_string) class TimeSpecification(object): @@ -111,12 +114,11 @@ class IntervalTimeSpecification(TimeSpecification): An Interval type spec runs at the given fixed interval. It has two attributes: - period - the type of interval, either "hours" or "minutes" + period - the type of interval, either "hours" or "minutes" interval - the number of units of type period. - timezone - the timezone for this specification. Not used in this class. """ - def __init__(self, interval, period, timezone=None): + def __init__(self, interval, period): super(IntervalTimeSpecification, self).__init__(self) self.interval = interval self.period = period @@ -186,7 +188,9 @@ class SpecificTimeSpecification(TimeSpecification): self.monthdays = set(monthdays) hourstr, minutestr = timestr.split(':') self.time = datetime.time(int(hourstr), int(minutestr)) - if timezone and pytz is not None: + if timezone: + if pytz is None: + raise ValueError("need pytz in order to specify a timezone") self.timezone = pytz.timezone(timezone) def _MatchingDays(self, year, month): diff --git a/thirdparty/google_appengine/google/appengine/ext/admin/__init__.py b/thirdparty/google_appengine/google/appengine/ext/admin/__init__.py index 58376b58..2930d8c5 100755 --- a/thirdparty/google_appengine/google/appengine/ext/admin/__init__.py +++ b/thirdparty/google_appengine/google/appengine/ext/admin/__init__.py @@ -41,6 +41,14 @@ import urllib import urlparse import wsgiref.handlers +try: + from google.appengine.cron import groctimespecification + from google.appengine.api import croninfo +except ImportError: + HAVE_CRON = False +else: + HAVE_CRON = True + from google.appengine.api import datastore from google.appengine.api import datastore_admin from google.appengine.api import datastore_types @@ -109,6 +117,9 @@ class BaseRequestHandler(webapp.RequestHandler): 'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH, 'memcache_path': base_path + MemcachePageHandler.PATH, } + if HAVE_CRON: + values['cron_path'] = base_path + CronPageHandler.PATH + values.update(template_values) directory = os.path.dirname(__file__) path = os.path.join(directory, os.path.join('templates', template_name)) @@ -201,6 +212,39 @@ class InteractiveExecuteHandler(BaseRequestHandler): self.generate('interactive-output.html', {'output': results}) +class CronPageHandler(BaseRequestHandler): + """Shows information about configured cron jobs in this application.""" + PATH = '/cron' + + def get(self, now=None): + """Shows template displaying the configured cron jobs.""" + if not now: + now = datetime.datetime.now() + values = {'request': self.request} + cron_info = _ParseCronYaml() + values['cronjobs'] = [] + values['now'] = str(now) + if cron_info: + for entry in cron_info.cron: + job = {} + values['cronjobs'].append(job) + if entry.description: + job['description'] = entry.description + else: + job['description'] = '(no description)' + if entry.timezone: + job['timezone'] = entry.timezone + job['url'] = entry.url + job['schedule'] = entry.schedule + schedule = groctimespecification.GrocTimeSpecification(entry.schedule) + matches = schedule.GetMatches(now, 3) + job['times'] = [] + for match in matches: + job['times'].append({'runtime': match.strftime("%Y-%m-%d %H:%M:%SZ"), + 'difference': str(match - now)}) + self.generate('cron.html', values) + + class MemcachePageHandler(BaseRequestHandler): """Shows stats about memcache and query form to get values.""" PATH = '/memcache' @@ -1089,8 +1133,24 @@ for data_type in _DATA_TYPES.values(): _NAMED_DATA_TYPES[data_type.name()] = data_type +def _ParseCronYaml(): + """Load the cron.yaml file and parse it.""" + cronyaml_files = 'cron.yaml', 'cron.yml' + for cronyaml in cronyaml_files: + try: + fh = open(cronyaml, "r") + except IOError: + continue + try: + cron_info = croninfo.LoadSingleCron(fh) + return cron_info + finally: + fh.close() + return None + + def main(): - application = webapp.WSGIApplication([ + handlers = [ ('.*' + DatastoreQueryHandler.PATH, DatastoreQueryHandler), ('.*' + DatastoreEditHandler.PATH, DatastoreEditHandler), ('.*' + DatastoreBatchEditHandler.PATH, DatastoreBatchEditHandler), @@ -1099,7 +1159,10 @@ def main(): ('.*' + MemcachePageHandler.PATH, MemcachePageHandler), ('.*' + ImageHandler.PATH, ImageHandler), ('.*', DefaultPageHandler), - ], debug=_DEBUG) + ] + if HAVE_CRON: + handlers.insert(0, ('.*' + CronPageHandler.PATH, CronPageHandler)) + application = webapp.WSGIApplication(handlers, debug=_DEBUG) wsgiref.handlers.CGIHandler().run(application) diff --git a/thirdparty/google_appengine/google/appengine/ext/admin/templates/base.html b/thirdparty/google_appengine/google/appengine/ext/admin/templates/base.html index bbc6da48..a53ac764 100644 --- a/thirdparty/google_appengine/google/appengine/ext/admin/templates/base.html +++ b/thirdparty/google_appengine/google/appengine/ext/admin/templates/base.html @@ -37,6 +37,9 @@
  • Datastore Viewer
  • Interactive Console
  • Memcache Viewer
  • + {% if cron_path %} +
  • Cron Jobs
  • + {% endif %} diff --git a/thirdparty/google_appengine/google/appengine/ext/admin/templates/cron.html b/thirdparty/google_appengine/google/appengine/ext/admin/templates/cron.html new file mode 100644 index 00000000..c692ae89 --- /dev/null +++ b/thirdparty/google_appengine/google/appengine/ext/admin/templates/cron.html @@ -0,0 +1,85 @@ +{% extends "base.html" %} + +{% block title %} +{{ application_name }} Development Console - Cron Viewer{% endblock %} + +{% block head %} + +{% endblock %} + +{% block breadcrumbs %} + Cron Viewer +{% endblock %} + +{% block body %} +

    Cron Jobs

    + +{% if message %} +
    +{{ message|escape }} +
    +{% endif %} + +{% if cronjobs %} + + + + + + + + + + + + + {% for job in cronjobs %} + + + + + {% endfor %} + +
    Cron JobSchedule
    +

    {{ job.url|escape }}

    +

    + {{ job.description|escape }} +

    +
    + + + + + +
    + {{ job.schedule|escape }} + + Test this job +
    + + {% if job.timezone %} + Timezone: {{ job.timezone }} +
    + Schedules with timezones won't be calculated correctly here. Use the + appcfg.py cron_info command to view the next run times for this schedule, + after installing the pytz package. +
    + {% endif %} +
    + In production, this would run at these times: +
      + {% for run in job.times %} +
    1. + {{ run.runtime }} {{ run.difference }} from now +
    2. + {% endfor %} +
    +
    +
    +{% else %} + This application doesn't define any cron jobs. See the documentation for more. +{% endif %} + + +{% endblock %} + diff --git a/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/ae.css b/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/ae.css index 2ee7a30a..1c8373c3 100755 --- a/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/ae.css +++ b/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/ae.css @@ -43,3 +43,108 @@ h1 { padding-left: 1em; border-left: 3px solid #e5ecf9; } + +/* Tables */ +.ae-table-plain { + border-collapse: collapse; + width: 100%; +} +.ae-table { + border: 1px solid #c5d7ef; + border-collapse: collapse; + width: 100%; +} + +#bd h2.ae-table-title { + background: #e5ecf9; + margin: 0; + color: #000; + font-size: 1em; + padding: 3px 0 3px 5px; + border-left: 1px solid #c5d7ef; + border-right: 1px solid #c5d7ef; + border-top: 1px solid #c5d7ef; +} +.ae-table-caption, +.ae-table caption { + border: 1px solid #c5d7ef; + background: #e5ecf9; + /** + * Fixes the caption margin ff display bug. + * see www.aurora-il.org/table_test.htm + * this is a slight variation to specifically target FF since Safari + * was shifting the caption over in an ugly fashion with margin-left: -1px + */ + -moz-margin-start: -1px; +} +.ae-table caption { + padding: 3px 5px; + text-align: left; +} +.ae-table th, +.ae-table td { + background-color: #fff; + padding: .35em 1em .25em .35em; + margin: 0; +} +.ae-table thead th { + font-weight: bold; + text-align: left; + background: #c5d7ef; + vertical-align: bottom; +} +.ae-table tfoot tr td { + border-top: 1px solid #c5d7ef; + background-color: #e5ecf9; +} +.ae-table td { + border-top: 1px solid #c5d7ef; + border-bottom: 1px solid #c5d7ef; +} +.ae-even td, +.ae-even th, +.ae-even-top td, +.ae-even-tween td, +.ae-even-bottom td, +ol.ae-even { + background-color: #e9e9e9; + border-top: 1px solid #c5d7ef; + border-bottom: 1px solid #c5d7ef; +} +.ae-even-top td { + border-bottom: 0; +} +.ae-even-bottom td { + border-top: 0; +} +.ae-even-tween td { + border: 0; +} +.ae-table .ae-tween td { + border: 0; +} +.ae-table .ae-tween-top td { + border-bottom: 0; +} +.ae-table .ae-tween-bottom td { + border-top: 0; +} +.ae-table #ae-live td { + background-color: #ffeac0; +} +.ae-table-fixed { + table-layout: fixed; +} +.ae-table-fixed td, +.ae-table-nowrap { + overflow: hidden; + white-space: nowrap; +} +.ae-new-usr td { + border-top: 1px solid #ccccce; + background-color: #ffe; +} +.ae-error-td td { + border: 2px solid #f00; + background-color: #fee; +} diff --git a/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/cron.css b/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/cron.css new file mode 100644 index 00000000..679d358f --- /dev/null +++ b/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/cron.css @@ -0,0 +1,26 @@ +.ah-cron-message { + color: red; + margin-bottom: 1em; +} + +#ah-cron-jobs .ah-cron-message { + margin: 1em; +} + +.ah-cron-times { + margin-top: 1em; +} +#ah-cron-jobs .ae-table, +#ah-cron-jobs .ae-table td { + border: 0; + padding: 0; +} +#ah-cron-jobs ol { + list-style: none; +} +#ah-cron-jobs li { + padding: .2em 0; +} +.ah-cron-test { + text-align: right; +} diff --git a/thirdparty/google_appengine/google/appengine/tools/appcfg.py b/thirdparty/google_appengine/google/appengine/tools/appcfg.py index ff1f536d..552ffe06 100755 --- a/thirdparty/google_appengine/google/appengine/tools/appcfg.py +++ b/thirdparty/google_appengine/google/appengine/tools/appcfg.py @@ -1828,8 +1828,8 @@ class AppCfgApp(object): if not description: description = "" print >>output, "\n%s:\nURL: %s\nSchedule: %s" % (description, - entry.schedule, - entry.url) + entry.url, + entry.schedule) schedule = groctimespecification.GrocTimeSpecification(entry.schedule) matches = schedule.GetMatches(now, self.options.num_runs) for match in matches: @@ -1897,14 +1897,13 @@ in the app.yaml file at the top level of that directory. appcfg.py will follow symlinks and recursively upload all files to the server. Temporary or source control files (e.g. foo~, .svn/*) will be skipped."""), - - - - - - - - + "update_cron": Action( + function="UpdateCron", + usage="%prog [options] update_cron ", + short_desc="Update application cron definitions.", + long_desc=""" +The 'update_cron' command will update any new, removed or changed cron +definitions from the cron.yaml file."""), "update_indexes": Action( function="UpdateIndexes", @@ -1945,15 +1944,14 @@ The 'request_logs' command exports the request logs from your application to a file. It will write Apache common log format records ordered chronologically. If output file is '-' stdout will be written."""), - - - - - - - - - + "cron_info": Action( + function="CronInfo", + usage="%prog [options] cron_info ", + options=_CronInfoOptions, + short_desc="Display information about cron jobs.", + long_desc=""" +The 'cron_info' command will display the next 'number' runs (default 5) for +each cron job defined in the cron.yaml file."""), diff --git a/thirdparty/google_appengine/google/appengine/tools/dev_appserver.py b/thirdparty/google_appengine/google/appengine/tools/dev_appserver.py index 2f6bb70a..20e60ac2 100755 --- a/thirdparty/google_appengine/google/appengine/tools/dev_appserver.py +++ b/thirdparty/google_appengine/google/appengine/tools/dev_appserver.py @@ -74,6 +74,7 @@ from google.pyglib import gexcept from google.appengine.api import apiproxy_stub_map from google.appengine.api import appinfo +from google.appengine.api import croninfo from google.appengine.api import datastore_admin from google.appengine.api import datastore_file_stub from google.appengine.api import mail_stub @@ -777,6 +778,8 @@ class FakeFile(file): _skip_files = None _static_file_config_matcher = None + _allow_skipped_files = True + _availability_cache = {} @staticmethod @@ -803,6 +806,16 @@ class FakeFile(file): FakeFile._availability_cache = {} @staticmethod + def SetAllowSkippedFiles(allow_skipped_files): + """Configures access to files matching FakeFile._skip_files + + Args: + allow_skipped_files: Boolean whether to allow access to skipped files + """ + FakeFile._allow_skipped_files = allow_skipped_files + FakeFile._availability_cache = {} + + @staticmethod def SetSkippedFiles(skip_files): """Sets which files in the application directory are to be ignored. @@ -877,7 +890,8 @@ class FakeFile(file): normcase=normcase): relative_filename = logical_filename[len(FakeFile._root_path):] - if FakeFile._skip_files.match(relative_filename): + if (not FakeFile._allow_skipped_files and + FakeFile._skip_files.match(relative_filename)): logging.warning('Blocking access to skipped file "%s"', logical_filename) return False @@ -2789,13 +2803,13 @@ def ReadAppConfig(appinfo_path, parse_app_config=appinfo.LoadSingleAppInfo): """ try: appinfo_file = file(appinfo_path, 'r') - try: - return parse_app_config(appinfo_file) - finally: - appinfo_file.close() except IOError, e: raise InvalidAppConfigError( 'Application configuration could not be read from "%s"' % appinfo_path) + try: + return parse_app_config(appinfo_file) + finally: + appinfo_file.close() def CreateURLMatcherFromMaps(root_path, @@ -2956,6 +2970,31 @@ def LoadAppConfig(root_path, raise AppConfigNotFoundError +def ReadCronConfig(croninfo_path, parse_cron_config=croninfo.LoadSingleCron): + """Reads cron.yaml file and returns a list of CronEntry instances. + + Args: + croninfo_path: String containing the path to the cron.yaml file. + parse_cron_config: Used for dependency injection. + + Returns: + A CronInfoExternal object. + + Raises: + If the config file is unreadable, empty or invalid, this function will + raise an InvalidAppConfigError or a MalformedCronConfiguration exception. + """ + try: + croninfo_file = file(croninfo_path, 'r') + except IOError, e: + raise InvalidAppConfigError( + 'Cron configuration could not be read from "%s"' % croninfo_path) + try: + return parse_cron_config(croninfo_file) + finally: + croninfo_file.close() + + def SetupStubs(app_id, **config): """Sets up testing stubs of APIs. @@ -3124,6 +3163,7 @@ def CreateServer(root_path, template_dir, serve_address='', require_indexes=False, + allow_skipped_files=False, static_caching=True, python_path_list=sys.path, sdk_dir=os.path.dirname(os.path.dirname(google.__file__))): @@ -3156,6 +3196,7 @@ def CreateServer(root_path, FakeFile.SetAllowedPaths(absolute_root_path, [sdk_dir, template_dir]) + FakeFile.SetAllowSkippedFiles(allow_skipped_files) handler_class = CreateRequestHandler(absolute_root_path, login_url, diff --git a/thirdparty/google_appengine/google/appengine/tools/dev_appserver_main.py b/thirdparty/google_appengine/google/appengine/tools/dev_appserver_main.py index 55f22d11..4b4d57d1 100755 --- a/thirdparty/google_appengine/google/appengine/tools/dev_appserver_main.py +++ b/thirdparty/google_appengine/google/appengine/tools/dev_appserver_main.py @@ -54,6 +54,8 @@ Options: --debug_imports Enables debug logging for module imports, showing search paths used for finding modules and any errors encountered during the import process. + --allow_skipped_files Allow access to files matched by app.yaml's + skipped_files (default False) --disable_static_caching Never allow the browser to cache static files. (Default enable if expiration set in app.yaml) """ @@ -101,6 +103,7 @@ ARG_LOGIN_URL = 'login_url' ARG_LOG_LEVEL = 'log_level' ARG_PORT = 'port' ARG_REQUIRE_INDEXES = 'require_indexes' +ARG_ALLOW_SKIPPED_FILES = 'allow_skipped_files' ARG_SMTP_HOST = 'smtp_host' ARG_SMTP_PASSWORD = 'smtp_password' ARG_SMTP_PORT = 'smtp_port' @@ -137,6 +140,7 @@ DEFAULT_ARGS = { ARG_ADDRESS: 'localhost', ARG_ADMIN_CONSOLE_SERVER: DEFAULT_ADMIN_CONSOLE_SERVER, ARG_ADMIN_CONSOLE_HOST: None, + ARG_ALLOW_SKIPPED_FILES: False, ARG_STATIC_CACHING: True, } @@ -245,6 +249,7 @@ def ParseArguments(argv): [ 'address=', 'admin_console_server=', 'admin_console_host=', + 'allow_skipped_files', 'auth_domain=', 'clear_datastore', 'datastore_path=', @@ -337,6 +342,9 @@ def ParseArguments(argv): if option == '--admin_console_host': option_dict[ARG_ADMIN_CONSOLE_HOST] = value + if option == '--allow_skipped_files': + option_dict[ARG_ALLOW_SKIPPED_FILES] = True + if option == '--disable_static_caching': option_dict[ARG_STATIC_CACHING] = False @@ -399,6 +407,7 @@ def main(argv): template_dir = option_dict[ARG_TEMPLATE_DIR] serve_address = option_dict[ARG_ADDRESS] require_indexes = option_dict[ARG_REQUIRE_INDEXES] + allow_skipped_files = option_dict[ARG_ALLOW_SKIPPED_FILES] static_caching = option_dict[ARG_STATIC_CACHING] logging.basicConfig( @@ -432,14 +441,16 @@ def main(argv): exc_type, exc_value, exc_traceback))) return 1 - http_server = dev_appserver.CreateServer(root_path, - login_url, - port, - template_dir, - sdk_dir=SDK_PATH, - serve_address=serve_address, - require_indexes=require_indexes, - static_caching=static_caching) + http_server = dev_appserver.CreateServer( + root_path, + login_url, + port, + template_dir, + sdk_dir=SDK_PATH, + serve_address=serve_address, + require_indexes=require_indexes, + allow_skipped_files=allow_skipped_files, + static_caching=static_caching) logging.info('Running application %s on port %d: http://%s:%d', config.application, port, serve_address, port) -- 2.11.4.GIT