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.
17 """Runs a development application server for an application.
19 %(script)s [options] <application root>
21 Application root must be the path to the application to run in this server.
22 Must contain a valid app.yaml or app.yml file.
25 --help, -h View this helpful message.
26 --debug, -d Use debug logging. (Default false)
27 --clear_datastore, -c Clear the Datastore on startup. (Default false)
28 --address=ADDRESS, -a ADDRESS
29 Address to which this server should bind. (Default
31 --port=PORT, -p PORT Port for the server to run on. (Default %(port)s)
32 --datastore_path=PATH Path to use for storing Datastore file stub data.
33 (Default %(datastore_path)s)
34 --history_path=PATH Path to use for storing Datastore history.
35 (Default %(history_path)s)
36 --require_indexes Disallows queries that require composite indexes
37 not defined in index.yaml.
38 --smtp_host=HOSTNAME SMTP host to send test mail to. Leaving this
39 unset will disable SMTP mail sending.
40 (Default '%(smtp_host)s')
41 --smtp_port=PORT SMTP port to send test mail to.
42 (Default %(smtp_port)s)
43 --smtp_user=USER SMTP user to connect as. Stub will only attempt
44 to login if this field is non-empty.
45 (Default '%(smtp_user)s').
46 --smtp_password=PASSWORD Password for SMTP server.
47 (Default '%(smtp_password)s')
48 --enable_sendmail Enable sendmail when SMTP not configured.
50 --show_mail_body Log the body of emails in mail stub.
52 --auth_domain Authorization domain that this app runs in.
54 --debug_imports Enables debug logging for module imports, showing
55 search paths used for finding modules and any
56 errors encountered during the import process.
57 --allow_skipped_files Allow access to files matched by app.yaml's
58 skipped_files (default False)
59 --disable_static_caching Never allow the browser to cache static files.
60 (Default enable if expiration set in app.yaml)
65 from google
.appengine
.tools
import os_compat
77 """Set various global variables involving the 'google' package.
79 This function should not be called until sys.path has been properly set.
81 global yaml_errors
, appcfg
, appengine_rpc
, dev_appserver
, os_compat
82 from google
.appengine
.api
import yaml_errors
83 from google
.appengine
.dist
import py_zipimport
84 from google
.appengine
.tools
import appcfg
85 from google
.appengine
.tools
import appengine_rpc
86 from google
.appengine
.tools
import dev_appserver
87 from google
.appengine
.tools
import os_compat
91 DEFAULT_ADMIN_CONSOLE_SERVER
= 'appengine.google.com'
93 ARG_ADDRESS
= 'address'
94 ARG_ADMIN_CONSOLE_SERVER
= 'admin_console_server'
95 ARG_ADMIN_CONSOLE_HOST
= 'admin_console_host'
96 ARG_AUTH_DOMAIN
= 'auth_domain'
97 ARG_CLEAR_DATASTORE
= 'clear_datastore'
98 ARG_DATASTORE_PATH
= 'datastore_path'
99 ARG_DEBUG_IMPORTS
= 'debug_imports'
100 ARG_ENABLE_SENDMAIL
= 'enable_sendmail'
101 ARG_SHOW_MAIL_BODY
= 'show_mail_body'
102 ARG_HISTORY_PATH
= 'history_path'
103 ARG_LOGIN_URL
= 'login_url'
104 ARG_LOG_LEVEL
= 'log_level'
106 ARG_REQUIRE_INDEXES
= 'require_indexes'
107 ARG_ALLOW_SKIPPED_FILES
= 'allow_skipped_files'
108 ARG_SMTP_HOST
= 'smtp_host'
109 ARG_SMTP_PASSWORD
= 'smtp_password'
110 ARG_SMTP_PORT
= 'smtp_port'
111 ARG_SMTP_USER
= 'smtp_user'
112 ARG_STATIC_CACHING
= 'static_caching'
113 ARG_TEMPLATE_DIR
= 'template_dir'
114 ARG_TRUSTED
= 'trusted'
116 SDK_PATH
= os
.path
.dirname(
119 os
.path
.dirname(os_compat
.__file
__)
126 ARG_LOG_LEVEL
: logging
.INFO
,
127 ARG_DATASTORE_PATH
: os
.path
.join(tempfile
.gettempdir(),
128 'dev_appserver.datastore'),
129 ARG_HISTORY_PATH
: os
.path
.join(tempfile
.gettempdir(),
130 'dev_appserver.datastore.history'),
131 ARG_LOGIN_URL
: '/_ah/login',
132 ARG_CLEAR_DATASTORE
: False,
133 ARG_REQUIRE_INDEXES
: False,
134 ARG_TEMPLATE_DIR
: os
.path
.join(SDK_PATH
, 'templates'),
138 ARG_SMTP_PASSWORD
: '',
139 ARG_ENABLE_SENDMAIL
: False,
140 ARG_SHOW_MAIL_BODY
: False,
141 ARG_AUTH_DOMAIN
: 'gmail.com',
142 ARG_ADDRESS
: 'localhost',
143 ARG_ADMIN_CONSOLE_SERVER
: DEFAULT_ADMIN_CONSOLE_SERVER
,
144 ARG_ADMIN_CONSOLE_HOST
: None,
145 ARG_ALLOW_SKIPPED_FILES
: False,
146 ARG_STATIC_CACHING
: True,
152 'antlr3': ('lib', 'antlr3'),
153 'django': ('lib', 'django'),
154 'webob': ('lib', 'webob'),
155 'yaml': ('lib', 'yaml', 'lib'),
159 DEFAULT_API_VERSION
= '1'
161 API_PATHS
['test'] = API_PATHS
[DEFAULT_API_VERSION
].copy()
162 API_PATHS
['test']['_test'] = ('nonexistent', 'test', 'path')
165 def SetPaths(app_config_path
):
166 """Set the interpreter to use the specified API version.
168 The app.yaml file is scanned for the api_version field and the value is
169 extracted. With that information, the paths in API_PATHS are added to the
170 front of sys.paths to make sure that they take precedent over any other paths
171 to older versions of a package. All modules for each package set are cleared
172 out of sys.modules to make sure only the newest version is used.
175 - app_config_path: Path to the app.yaml file.
177 api_version_re
= re
.compile(r
'api_version:\s*(?P<api_version>[\w.]{1,32})')
179 app_config_file
= open(app_config_path
, 'r')
181 for line
in app_config_file
:
182 re_match
= api_version_re
.match(line
)
184 api_version
= re_match
.group('api_version')
187 app_config_file
.close()
189 if api_version
is None:
190 logging
.error("Application configuration file missing an 'api_version' "
191 "value:\n%s" % app_config_path
)
193 if api_version
not in API_PATHS
:
194 logging
.error("Value of %r for 'api_version' from the application "
195 "configuration file is not valid:\n%s" %
196 (api_version
, app_config_path
))
199 if api_version
== DEFAULT_API_VERSION
:
200 return DEFAULT_API_VERSION
202 sdk_path
= os
.path
.dirname(
205 os
.path
.dirname(os_compat
.__file
__)
209 for pkg_name
, path_parts
in API_PATHS
[api_version
].iteritems():
210 for name
in sys
.modules
.keys():
211 if name
== pkg_name
or name
.startswith('%s.' % pkg_name
):
212 del sys
.modules
[name
]
213 pkg_path
= os
.path
.join(sdk_path
, *path_parts
)
214 sys
.path
.insert(0, pkg_path
)
219 def PrintUsageExit(code
):
220 """Prints usage information and exits with a status code.
223 code: Status code to pass to sys.exit() after displaying usage information.
225 render_dict
= DEFAULT_ARGS
.copy()
226 render_dict
['script'] = os
.path
.basename(sys
.argv
[0])
227 print sys
.modules
['__main__'].__doc
__ % render_dict
232 def ParseArguments(argv
):
233 """Parses command-line arguments.
236 argv: Command-line arguments, including the executable name, used to
237 execute this application.
240 Tuple (args, option_dict) where:
241 args: List of command-line arguments following the executable name.
242 option_dict: Dictionary of parsed flags that maps keys from DEFAULT_ARGS
243 to their values, which are either pulled from the defaults, or from
246 option_dict
= DEFAULT_ARGS
.copy()
249 opts
, args
= getopt
.gnu_getopt(
253 'admin_console_server=',
254 'admin_console_host=',
255 'allow_skipped_files',
262 'disable_static_caching',
275 except getopt
.GetoptError
, e
:
276 print >>sys
.stderr
, 'Error: %s' % e
279 for option
, value
in opts
:
280 if option
in ('-h', '--help'):
283 if option
in ('-d', '--debug'):
284 option_dict
[ARG_LOG_LEVEL
] = logging
.DEBUG
286 if option
in ('-p', '--port'):
288 option_dict
[ARG_PORT
] = int(value
)
289 if not (65535 > option_dict
[ARG_PORT
] > 0):
292 print >>sys
.stderr
, 'Invalid value supplied for port'
295 if option
in ('-a', '--address'):
296 option_dict
[ARG_ADDRESS
] = value
298 if option
== '--datastore_path':
299 option_dict
[ARG_DATASTORE_PATH
] = os
.path
.abspath(value
)
301 if option
== '--history_path':
302 option_dict
[ARG_HISTORY_PATH
] = os
.path
.abspath(value
)
304 if option
in ('-c', '--clear_datastore'):
305 option_dict
[ARG_CLEAR_DATASTORE
] = True
307 if option
== '--require_indexes':
308 option_dict
[ARG_REQUIRE_INDEXES
] = True
310 if option
== '--smtp_host':
311 option_dict
[ARG_SMTP_HOST
] = value
313 if option
== '--smtp_port':
315 option_dict
[ARG_SMTP_PORT
] = int(value
)
316 if not (65535 > option_dict
[ARG_SMTP_PORT
] > 0):
319 print >>sys
.stderr
, 'Invalid value supplied for SMTP port'
322 if option
== '--smtp_user':
323 option_dict
[ARG_SMTP_USER
] = value
325 if option
== '--smtp_password':
326 option_dict
[ARG_SMTP_PASSWORD
] = value
328 if option
== '--enable_sendmail':
329 option_dict
[ARG_ENABLE_SENDMAIL
] = True
331 if option
== '--show_mail_body':
332 option_dict
[ARG_SHOW_MAIL_BODY
] = True
334 if option
== '--auth_domain':
335 option_dict
['_DEFAULT_ENV_AUTH_DOMAIN'] = value
337 if option
== '--debug_imports':
338 option_dict
['_ENABLE_LOGGING'] = True
340 if option
== '--template_dir':
341 option_dict
[ARG_TEMPLATE_DIR
] = value
343 if option
== '--admin_console_server':
344 option_dict
[ARG_ADMIN_CONSOLE_SERVER
] = value
.strip()
346 if option
== '--admin_console_host':
347 option_dict
[ARG_ADMIN_CONSOLE_HOST
] = value
349 if option
== '--allow_skipped_files':
350 option_dict
[ARG_ALLOW_SKIPPED_FILES
] = True
352 if option
== '--disable_static_caching':
353 option_dict
[ARG_STATIC_CACHING
] = False
355 if option
== '--trusted':
356 option_dict
[ARG_TRUSTED
] = True
358 return args
, option_dict
361 def MakeRpcServer(option_dict
):
362 """Create a new HttpRpcServer.
364 Creates a new HttpRpcServer to check for updates to the SDK.
367 option_dict: The dict of command line options.
372 server
= appengine_rpc
.HttpRpcServer(
373 option_dict
[ARG_ADMIN_CONSOLE_SERVER
],
374 lambda: ('unused_email', 'unused_password'),
375 appcfg
.GetUserAgent(),
376 appcfg
.GetSourceName(),
377 host_override
=option_dict
[ARG_ADMIN_CONSOLE_HOST
])
378 server
.authenticated
= True
383 """Runs the development application server."""
384 args
, option_dict
= ParseArguments(argv
)
387 print >>sys
.stderr
, 'Invalid arguments'
391 for suffix
in ('yaml', 'yml'):
392 path
= os
.path
.join(root_path
, 'app.%s' % suffix
)
393 if os
.path
.exists(path
):
394 api_version
= SetPaths(path
)
397 logging
.error("Application configuration file not found in %s" % root_path
)
401 dev_appserver
.API_VERSION
= api_version
403 if '_DEFAULT_ENV_AUTH_DOMAIN' in option_dict
:
404 auth_domain
= option_dict
['_DEFAULT_ENV_AUTH_DOMAIN']
405 dev_appserver
.DEFAULT_ENV
['AUTH_DOMAIN'] = auth_domain
406 if '_ENABLE_LOGGING' in option_dict
:
407 enable_logging
= option_dict
['_ENABLE_LOGGING']
408 dev_appserver
.HardenedModulesHook
.ENABLE_LOGGING
= enable_logging
410 log_level
= option_dict
[ARG_LOG_LEVEL
]
411 port
= option_dict
[ARG_PORT
]
412 datastore_path
= option_dict
[ARG_DATASTORE_PATH
]
413 login_url
= option_dict
[ARG_LOGIN_URL
]
414 template_dir
= option_dict
[ARG_TEMPLATE_DIR
]
415 serve_address
= option_dict
[ARG_ADDRESS
]
416 require_indexes
= option_dict
[ARG_REQUIRE_INDEXES
]
417 allow_skipped_files
= option_dict
[ARG_ALLOW_SKIPPED_FILES
]
418 static_caching
= option_dict
[ARG_STATIC_CACHING
]
422 format
='%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s')
426 config
, matcher
= dev_appserver
.LoadAppConfig(root_path
, {})
427 except yaml_errors
.EventListenerError
, e
:
428 logging
.error('Fatal error when loading application configuration:\n' +
431 except dev_appserver
.InvalidAppConfigError
, e
:
432 logging
.error('Application configuration file invalid:\n%s', e
)
435 if option_dict
[ARG_ADMIN_CONSOLE_SERVER
] != '':
436 server
= MakeRpcServer(option_dict
)
437 update_check
= appcfg
.UpdateCheck(server
, config
)
438 update_check
.CheckSupportedVersion()
439 if update_check
.AllowedToCheckForUpdates():
440 update_check
.CheckForUpdates()
443 dev_appserver
.SetupStubs(config
.application
, **option_dict
)
445 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
446 logging
.error(str(exc_type
) + ': ' + str(exc_value
))
447 logging
.debug(''.join(traceback
.format_exception(
448 exc_type
, exc_value
, exc_traceback
)))
451 http_server
= dev_appserver
.CreateServer(
457 serve_address
=serve_address
,
458 require_indexes
=require_indexes
,
459 allow_skipped_files
=allow_skipped_files
,
460 static_caching
=static_caching
)
462 logging
.info('Running application %s on port %d: http://%s:%d',
463 config
.application
, port
, serve_address
, port
)
466 http_server
.serve_forever()
467 except KeyboardInterrupt:
468 logging
.info('Server interrupted by user, terminating')
470 exc_info
= sys
.exc_info()
471 info_string
= '\n'.join(traceback
.format_exception(*exc_info
))
472 logging
.error('Error encountered:\n%s\nNow terminating.', info_string
)
475 http_server
.server_close()
480 if __name__
== '__main__':
481 sys
.exit(main(sys
.argv
))