Load /Users/solydzajs/Downloads/google_appengine into
authorPawel Solyga <Pawel.Solyga@gmail.com>
Sun, 12 Apr 2009 13:22:43 +0000
changeset 2172 ac7bd3b467ff
parent 2171 83d96aadd228
child 2173 27731db8ab1e
Load /Users/solydzajs/Downloads/google_appengine into trunk/thirdparty/google_appengine.
thirdparty/google_appengine/RELEASE_NOTES
thirdparty/google_appengine/VERSION
thirdparty/google_appengine/google/appengine/cron/groctimespecification.py
thirdparty/google_appengine/google/appengine/ext/admin/__init__.py
thirdparty/google_appengine/google/appengine/ext/admin/templates/base.html
thirdparty/google_appengine/google/appengine/ext/admin/templates/cron.html
thirdparty/google_appengine/google/appengine/ext/admin/templates/css/ae.css
thirdparty/google_appengine/google/appengine/ext/admin/templates/css/cron.css
thirdparty/google_appengine/google/appengine/tools/appcfg.py
thirdparty/google_appengine/google/appengine/tools/dev_appserver.py
thirdparty/google_appengine/google/appengine/tools/dev_appserver_main.py
--- a/thirdparty/google_appengine/RELEASE_NOTES	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/RELEASE_NOTES	Sun Apr 12 13:22:43 2009 +0000
@@ -3,6 +3,16 @@
 
 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
 ================================
 
--- a/thirdparty/google_appengine/VERSION	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/VERSION	Sun Apr 12 13:22:43 2009 +0000
@@ -1,3 +1,3 @@
-release: "1.1.9"
-timestamp: 1232676672
+release: "1.2.0"
+timestamp: 1236791960
 api_versions: ['1']
--- a/thirdparty/google_appengine/google/appengine/cron/groctimespecification.py	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/cron/groctimespecification.py	Sun Apr 12 13:22:43 2009 +0000
@@ -67,10 +67,13 @@
   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 @@
 
   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 @@
       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):
--- a/thirdparty/google_appengine/google/appengine/ext/admin/__init__.py	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/ext/admin/__init__.py	Sun Apr 12 13:22:43 2009 +0000
@@ -41,6 +41,14 @@
 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 @@
       '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 @@
     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 @@
   _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 @@
     ('.*' + 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)
 
 
--- a/thirdparty/google_appengine/google/appengine/ext/admin/templates/base.html	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/ext/admin/templates/base.html	Sun Apr 12 13:22:43 2009 +0000
@@ -37,6 +37,9 @@
               <li><a href="{{ datastore_path }}">Datastore Viewer</a></li>
               <li><a href="{{ interactive_path }}">Interactive Console</a></li>
               <li><a href="{{ memcache_path }}">Memcache Viewer</a></li>
+              {% if cron_path %}
+              <li><a href="{{ cron_path }}">Cron Jobs</a></li>
+              {% endif %}
             </ul>
         
           </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/google/appengine/ext/admin/templates/cron.html	Sun Apr 12 13:22:43 2009 +0000
@@ -0,0 +1,85 @@
+{% extends "base.html" %}
+
+{% block title %}
+{{ application_name }} Development Console - Cron Viewer{% endblock %}
+
+{% block head %}
+  <style type="text/css">{% include "css/cron.css" %}</style>
+{% endblock %}
+
+{% block breadcrumbs %}
+  <span class="item"><a href="">Cron Viewer</a></span>
+{% endblock %}
+
+{% block body %}
+<h3>Cron Jobs</h3>
+
+{% if message %}
+<div class="ah-cron-message">
+{{ message|escape }}
+</div>
+{% endif %}
+
+{% if cronjobs %}
+  <table id="ah-cron-jobs" class="ae-table ae-table-striped">
+    <colgroup>
+      <col style="width:60%">
+      <col>
+    </colgroup>
+    <thead>
+      <tr>
+        <th>Cron Job</th>
+        <th>Schedule</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for job in cronjobs %}
+        <tr class="{% cycle ae-odd,ae-even %}">
+          <td valign="top">
+            <h3>{{ job.url|escape }}</h3>
+            <p>
+              {{ job.description|escape }}
+            </p>
+          </td>
+          <td valign="top">
+            <table class="ae-table">
+              <tr>
+                <td>
+                  <strong>{{ job.schedule|escape }}</strong>
+                </td>
+                <td class="ah-cron-test">
+                  <a href="{{ job.url }}">Test this job</a>
+                </td>
+              </tr>
+            </table>
+
+            {% if job.timezone %}
+              <strong>Timezone: {{ job.timezone }}</strong>
+              <div class="ah-cron-message">
+                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.
+              </div>
+            {% endif %}
+            <div class="ah-cron-times">
+              In production, this would run at these times:
+              <ol>
+                {% for run in job.times %}
+                  <li>
+                    {{ run.runtime }} <span class="ae-unimportant">{{ run.difference }} from now</span>
+                  </li>
+                {% endfor %}
+              </ol>
+            </div>
+          </td>
+        </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+{% else %}
+  This application doesn't define any cron jobs. See the documentation for more.
+{% endif %}
+
+
+{% endblock %}
+
--- a/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/ae.css	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/ae.css	Sun Apr 12 13:22:43 2009 +0000
@@ -43,3 +43,108 @@
   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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/google/appengine/ext/admin/templates/css/cron.css	Sun Apr 12 13:22:43 2009 +0000
@@ -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;
+}
--- a/thirdparty/google_appengine/google/appengine/tools/appcfg.py	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/tools/appcfg.py	Sun Apr 12 13:22:43 2009 +0000
@@ -1828,8 +1828,8 @@
         if not description:
           description = "<no 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 @@
 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 <directory>",
+          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 @@
 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 <directory>",
+          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."""),
 
 
 
--- a/thirdparty/google_appengine/google/appengine/tools/dev_appserver.py	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/tools/dev_appserver.py	Sun Apr 12 13:22:43 2009 +0000
@@ -74,6 +74,7 @@
 
 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 @@
   _skip_files = None
   _static_file_config_matcher = None
 
+  _allow_skipped_files = True
+
   _availability_cache = {}
 
   @staticmethod
@@ -803,6 +806,16 @@
     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 @@
                               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 @@
   """
   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 @@
   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 @@
                  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 @@
   FakeFile.SetAllowedPaths(absolute_root_path,
                            [sdk_dir,
                             template_dir])
+  FakeFile.SetAllowSkippedFiles(allow_skipped_files)
 
   handler_class = CreateRequestHandler(absolute_root_path,
                                        login_url,
--- a/thirdparty/google_appengine/google/appengine/tools/dev_appserver_main.py	Sun Apr 12 13:14:03 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/tools/dev_appserver_main.py	Sun Apr 12 13:22:43 2009 +0000
@@ -54,6 +54,8 @@
   --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_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 @@
   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 @@
       [ 'address=',
         'admin_console_server=',
         'admin_console_host=',
+        'allow_skipped_files',
         'auth_domain=',
         'clear_datastore',
         'datastore_path=',
@@ -337,6 +342,9 @@
     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 @@
   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 @@
           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)