Load /Users/solydzajs/Downloads/google_appengine into
trunk/thirdparty/google_appengine.
--- 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)