1.9.30 sync.
[gae.git] / python / google / appengine / tools / dev_appserver_main.py
blob850b998ba0cf847f6b7034154de7e15dbd6de368
1 #!/usr/bin/env python
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.
24 Options:
25 --address=ADDRESS, -a ADDRESS
26 Address to which this server should bind. (Default
27 %(address)s).
28 --clear_datastore, -c Clear the Datastore on startup. (Default false)
29 --debug, -d Use debug logging. (Default false)
30 --help, -h View this helpful message.
31 --port=PORT, -p PORT Port for the server to run on. (Default %(port)s)
33 --allow_skipped_files Allow access to files matched by app.yaml's
34 skipped_files (default False)
35 --auth_domain Authorization domain that this app runs in.
36 (Default gmail.com)
37 --auto_id_policy=POLICY Dictate how automatic IDs are assigned by the
38 datastore stub, "sequential" or "scattered".
39 (Default scattered)
40 --backends Run the dev_appserver with backends support
41 (multiprocess mode).
42 --blobstore_path=DIR Path to directory to use for storing Blobstore
43 file stub data.
44 --clear_prospective_search Clear the Prospective Search subscription index
45 (Default false).
46 --clear_search_indexes Clear the Full Text Search indexes (Default false).
47 --datastore_path=DS_FILE Path to file to use for storing Datastore file
48 stub data.
49 (Default %(datastore_path)s)
50 --debug_imports Enables debug logging for module imports, showing
51 search paths used for finding modules and any
52 errors encountered during the import process.
53 --default_partition Default partition to use in the APPLICATION_ID.
54 (Default dev)
55 --disable_static_caching Never allow the browser to cache static files.
56 (Default enable if expiration set in app.yaml)
57 --disable_task_running When supplied, tasks will not be automatically
58 run after submission and must be run manually
59 in the local admin console.
60 --enable_sendmail Enable sendmail when SMTP not configured.
61 (Default false)
62 --high_replication Use the high replication datastore consistency
63 model. (Default false).
64 --history_path=PATH Path to use for storing Datastore history.
65 (Default %(history_path)s)
66 --persist_logs Enables storage of all request and application
67 logs to enable later access. (Default false).
68 --logs_path=LOGS_FILE Path to use for storing request logs. If this is
69 set, logs will be persisted to the given path. If
70 this is not set and --persist_logs is true, logs
71 are stored in %(logs_path)s.
72 --multiprocess_min_port When running in multiprocess mode, specifies the
73 lowest port value to use when choosing ports. If
74 set to 0, select random ports.
75 (Default 9000)
76 --mysql_host=HOSTNAME MySQL database host.
77 Used by the Cloud SQL (rdbms) stub.
78 (Default '%(mysql_host)s')
79 --mysql_port=PORT MySQL port to connect to.
80 Used by the Cloud SQL (rdbms) stub.
81 (Default %(mysql_port)s)
82 --mysql_user=USER MySQL user to connect as.
83 Used by the Cloud SQL (rdbms) stub.
84 (Default %(mysql_user)s)
85 --mysql_password=PASSWORD MySQL password to use.
86 Used by the Cloud SQL (rdbms) stub.
87 (Default '%(mysql_password)s')
88 --mysql_socket=PATH MySQL Unix socket file path.
89 Used by the Cloud SQL (rdbms) stub.
90 (Default '%(mysql_socket)s')
91 --require_indexes Disallows queries that require composite indexes
92 not defined in index.yaml.
93 --search_indexes_path=PATH Path to file to use for storing Full Text Search
94 indexes (Default %(search_indexes_path)s).
95 --show_mail_body Log the body of emails in mail stub.
96 (Default false)
97 --skip_sdk_update_check Skip checking for SDK updates. If false, fall back
98 to opt_in setting specified in .appcfg_nag
99 (Default false)
100 --smtp_host=HOSTNAME SMTP host to send test mail to. Leaving this
101 unset will disable SMTP mail sending.
102 (Default '%(smtp_host)s')
103 --smtp_port=PORT SMTP port to send test mail to.
104 (Default %(smtp_port)s)
105 --smtp_user=USER SMTP user to connect as. Stub will only attempt
106 to login if this field is non-empty.
107 (Default '%(smtp_user)s').
108 --smtp_password=PASSWORD Password for SMTP server.
109 (Default '%(smtp_password)s')
110 --task_retry_seconds How long to wait in seconds before retrying a
111 task after it fails during execution.
112 (Default '%(task_retry_seconds)s')
113 --use_sqlite Use the new, SQLite based datastore stub.
114 (Default false)
115 --port_sqlite_data Converts the data from the file based datastore
116 stub to the new SQLite stub, one time use only.
117 (Default false)
118 --[enable|disable]_console Enables/disables the interactive console.
119 (Default enabled if --address is unset,
120 disabled if --address is set)
152 from google.appengine.tools import os_compat
154 import getopt
155 import logging
156 import os
157 import signal
158 import sys
159 import tempfile
160 import traceback
165 logging.basicConfig(
166 level=logging.INFO,
167 format='%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s')
169 from google.appengine.api import yaml_errors
170 from google.appengine.dist import py_zipimport
171 from google.appengine.tools import appcfg
172 from google.appengine.tools import appengine_rpc
173 from google.appengine.tools import old_dev_appserver
174 from google.appengine.tools import dev_appserver_multiprocess as multiprocess
175 from google.appengine.tools import sdk_update_checker
180 DEFAULT_ADMIN_CONSOLE_SERVER = 'appengine.google.com'
183 ARG_ADDRESS = 'address'
184 ARG_ADMIN_CONSOLE_HOST = 'admin_console_host'
185 ARG_ADMIN_CONSOLE_SERVER = 'admin_console_server'
186 ARG_ALLOW_SKIPPED_FILES = 'allow_skipped_files'
187 ARG_AUTH_DOMAIN = 'auth_domain'
188 ARG_AUTO_ID_POLICY = 'auto_id_policy'
189 ARG_BACKENDS = 'backends'
190 ARG_BLOBSTORE_PATH = 'blobstore_path'
191 ARG_CLEAR_DATASTORE = 'clear_datastore'
192 ARG_CLEAR_PROSPECTIVE_SEARCH = 'clear_prospective_search'
193 ARG_CLEAR_SEARCH_INDEX = 'clear_search_indexes'
194 ARG_DATASTORE_PATH = 'datastore_path'
195 ARG_DEBUG_IMPORTS = 'debug_imports'
196 ARG_DEFAULT_PARTITION = 'default_partition'
197 ARG_DISABLE_TASK_RUNNING = 'disable_task_running'
198 ARG_ENABLE_SENDMAIL = 'enable_sendmail'
199 ARG_HIGH_REPLICATION = 'high_replication'
200 ARG_HISTORY_PATH = 'history_path'
201 ARG_LOGIN_URL = 'login_url'
202 ARG_LOG_LEVEL = 'log_level'
203 ARG_LOGS_PATH = 'logs_path'
204 ARG_MULTIPROCESS = multiprocess.ARG_MULTIPROCESS
205 ARG_MULTIPROCESS_API_PORT = multiprocess.ARG_MULTIPROCESS_API_PORT
206 ARG_MULTIPROCESS_API_SERVER = multiprocess.ARG_MULTIPROCESS_API_SERVER
207 ARG_MULTIPROCESS_APP_INSTANCE_ID = multiprocess.ARG_MULTIPROCESS_APP_INSTANCE_ID
208 ARG_MULTIPROCESS_BACKEND_ID = multiprocess.ARG_MULTIPROCESS_BACKEND_ID
209 ARG_MULTIPROCESS_BACKEND_INSTANCE_ID = multiprocess.ARG_MULTIPROCESS_BACKEND_INSTANCE_ID
210 ARG_MULTIPROCESS_FRONTEND_PORT = multiprocess.ARG_MULTIPROCESS_FRONTEND_PORT
211 ARG_MULTIPROCESS_MIN_PORT = multiprocess.ARG_MULTIPROCESS_MIN_PORT
212 ARG_MYSQL_HOST = 'mysql_host'
213 ARG_MYSQL_PASSWORD = 'mysql_password'
214 ARG_MYSQL_PORT = 'mysql_port'
215 ARG_MYSQL_SOCKET = 'mysql_socket'
216 ARG_MYSQL_USER = 'mysql_user'
217 ARG_PORT = 'port'
218 ARG_PROSPECTIVE_SEARCH_PATH = 'prospective_search_path'
219 ARG_REQUIRE_INDEXES = 'require_indexes'
220 ARG_SEARCH_INDEX_PATH = 'search_indexes_path'
221 ARG_SHOW_MAIL_BODY = 'show_mail_body'
222 ARG_SKIP_SDK_UPDATE_CHECK = 'skip_sdk_update_check'
223 ARG_SMTP_HOST = 'smtp_host'
224 ARG_SMTP_PASSWORD = 'smtp_password'
225 ARG_SMTP_PORT = 'smtp_port'
226 ARG_SMTP_USER = 'smtp_user'
227 ARG_STATIC_CACHING = 'static_caching'
228 ARG_TASK_RETRY_SECONDS = 'task_retry_seconds'
231 ARG_TRUSTED = 'trusted'
232 ARG_USE_SQLITE = 'use_sqlite'
233 ARG_PORT_SQLITE_DATA = 'port_sqlite_data'
234 ARG_CONSOLE = 'console'
237 SDK_PATH = os.path.dirname(
238 os.path.dirname(
239 os.path.dirname(
240 os.path.dirname(os_compat.__file__)
246 PRODUCTION_VERSION = (2, 5)
247 WARN_ABOUT_PYTHON_VERSION = True
249 DEFAULT_ARGS = {
250 ARG_ADDRESS: 'localhost',
251 ARG_ADMIN_CONSOLE_HOST: None,
252 ARG_ADMIN_CONSOLE_SERVER: DEFAULT_ADMIN_CONSOLE_SERVER,
253 ARG_ALLOW_SKIPPED_FILES: False,
254 ARG_AUTH_DOMAIN: 'gmail.com',
255 ARG_AUTO_ID_POLICY: 'scattered',
256 ARG_BLOBSTORE_PATH: os.path.join(tempfile.gettempdir(),
257 'dev_appserver.blobstore'),
258 ARG_CLEAR_DATASTORE: False,
259 ARG_CLEAR_PROSPECTIVE_SEARCH: False,
260 ARG_CLEAR_SEARCH_INDEX: False,
261 ARG_DATASTORE_PATH: os.path.join(tempfile.gettempdir(),
262 'dev_appserver.datastore'),
263 ARG_DEFAULT_PARTITION: 'dev',
264 ARG_DISABLE_TASK_RUNNING: False,
265 ARG_ENABLE_SENDMAIL: False,
266 ARG_HIGH_REPLICATION: False,
267 ARG_HISTORY_PATH: os.path.join(tempfile.gettempdir(),
268 'dev_appserver.datastore.history'),
269 ARG_LOGIN_URL: '/_ah/login',
270 ARG_LOG_LEVEL: logging.INFO,
271 ARG_LOGS_PATH: None,
272 ARG_MYSQL_HOST: 'localhost',
273 ARG_MYSQL_PASSWORD: '',
274 ARG_MYSQL_PORT: 3306,
275 ARG_MYSQL_SOCKET: '',
276 ARG_MYSQL_USER: '',
277 ARG_PORT: 8080,
278 ARG_PROSPECTIVE_SEARCH_PATH: os.path.join(tempfile.gettempdir(),
279 'dev_appserver.prospective_search'),
280 ARG_REQUIRE_INDEXES: False,
281 ARG_SEARCH_INDEX_PATH: os.path.join(tempfile.gettempdir(),
282 'dev_appserver.searchindexes'),
283 ARG_SHOW_MAIL_BODY: False,
284 ARG_SKIP_SDK_UPDATE_CHECK: False,
285 ARG_SMTP_HOST: '',
286 ARG_SMTP_PASSWORD: '',
287 ARG_SMTP_PORT: 25,
288 ARG_SMTP_USER: '',
289 ARG_STATIC_CACHING: True,
290 ARG_TASK_RETRY_SECONDS: 30,
291 ARG_TRUSTED: False,
292 ARG_USE_SQLITE: False,
293 ARG_PORT_SQLITE_DATA: False
297 OPTIONS = 'a:cdhp:'
300 LONG_OPTIONS = [
301 'address=',
302 'admin_console_host=',
303 'admin_console_server=',
304 'allow_skipped_files',
305 'auth_domain=',
306 'auto_id_policy=',
307 'backends',
308 'blobstore_path=',
309 'clear_datastore',
310 'clear_prospective_search',
311 'clear_search_indexes',
312 'datastore_path=',
313 'debug',
314 'debug_imports',
315 'default_partition=',
316 'disable_static_caching',
317 'disable_task_running',
318 'enable_sendmail',
319 'help',
320 'high_replication',
321 'history_path=',
322 'logs_path=',
323 'multiprocess',
324 'multiprocess_api_port=',
325 'multiprocess_api_server',
326 'multiprocess_app_instance_id=',
327 'multiprocess_backend_id=',
328 'multiprocess_backend_instance_id=',
329 'multiprocess_frontend_port=',
330 'multiprocess_min_port=',
331 'mysql_host=',
332 'mysql_password=',
333 'mysql_port=',
334 'mysql_socket=',
335 'mysql_user=',
336 'persist_logs',
337 'port=',
338 'require_indexes',
339 'search_indexes_path=',
340 'show_mail_body',
341 'skip_sdk_update_check',
342 'smtp_host=',
343 'smtp_password=',
344 'smtp_port=',
345 'smtp_user=',
346 'task_retry_seconds=',
347 'trusted',
348 'use_sqlite',
349 'port_sqlite_data',
350 'enable_console',
351 'disable_console',
355 def PrintUsageExit(code):
356 """Prints usage information and exits with a status code.
358 Args:
359 code: Status code to pass to sys.exit() after displaying usage information.
361 render_dict = DEFAULT_ARGS.copy()
362 render_dict['script'] = os.path.basename(sys.argv[0])
363 render_dict['logs_path'] = os.path.join(tempfile.gettempdir(),
364 'dev_appserver.logs')
365 print sys.modules['__main__'].__doc__ % render_dict
366 sys.stdout.flush()
367 sys.exit(code)
370 def ParseArguments(argv):
371 """Parses command-line arguments.
373 Args:
374 argv: Command-line arguments, including the executable name, used to
375 execute this application.
377 Returns:
378 Tuple (args, option_dict) where:
379 args: List of command-line arguments following the executable name.
380 option_dict: Dictionary of parsed flags that maps keys from DEFAULT_ARGS
381 to their values, which are either pulled from the defaults, or from
382 command-line flags.
384 option_dict = DEFAULT_ARGS.copy()
386 try:
387 opts, args = getopt.gnu_getopt(argv[1:], OPTIONS, LONG_OPTIONS)
388 except getopt.GetoptError, e:
389 print >>sys.stderr, 'Error: %s' % e
390 PrintUsageExit(1)
392 for option, value in opts:
393 if option in ('-h', '--help'):
394 PrintUsageExit(0)
396 if option in ('-d', '--debug'):
397 option_dict[ARG_LOG_LEVEL] = logging.DEBUG
399 if option in ('-p', '--port'):
400 try:
401 option_dict[ARG_PORT] = int(value)
402 if not (65535 > option_dict[ARG_PORT] > 0):
403 raise ValueError
404 except ValueError:
405 print >>sys.stderr, 'Invalid value supplied for port'
406 PrintUsageExit(1)
408 def expand_path(s):
409 return os.path.abspath(os.path.expanduser(s))
411 if option in ('-a', '--address'):
412 option_dict[ARG_ADDRESS] = value
414 if option == '--blobstore_path':
415 option_dict[ARG_BLOBSTORE_PATH] = expand_path(value)
417 if option == '--datastore_path':
418 option_dict[ARG_DATASTORE_PATH] = expand_path(value)
420 if option == '--search_indexes_path':
421 option_dict[ARG_SEARCH_INDEX_PATH] = expand_path(value)
423 if option == '--prospective_search_path':
424 option_dict[ARG_PROSPECTIVE_SEARCH_PATH] = expand_path(value)
426 if option == '--skip_sdk_update_check':
427 option_dict[ARG_SKIP_SDK_UPDATE_CHECK] = True
429 if option == '--auto_id_policy':
430 option_dict[ARG_AUTO_ID_POLICY] = value.lower()
432 if option == '--use_sqlite':
433 option_dict[ARG_USE_SQLITE] = True
435 if option == '--port_sqlite_data':
436 option_dict[ARG_PORT_SQLITE_DATA] = True
438 if option == '--high_replication':
439 option_dict[ARG_HIGH_REPLICATION] = True
441 if option == '--history_path':
442 option_dict[ARG_HISTORY_PATH] = expand_path(value)
444 if option in ('-c', '--clear_datastore'):
445 option_dict[ARG_CLEAR_DATASTORE] = True
447 if option == '--clear_prospective_search':
448 option_dict[ARG_CLEAR_PROSPECTIVE_SEARCH] = True
450 if option == '--clear_search_indexes':
451 option_dict[ARG_CLEAR_SEARCH_INDEX] = True
453 if option == '--require_indexes':
454 option_dict[ARG_REQUIRE_INDEXES] = True
456 if option == '--mysql_host':
457 option_dict[ARG_MYSQL_HOST] = value
459 if option == '--mysql_port':
460 option_dict[ARG_MYSQL_PORT] = _ParsePort(value, '--mysql_port')
462 if option == '--mysql_user':
463 option_dict[ARG_MYSQL_USER] = value
465 if option == '--mysql_password':
466 option_dict[ARG_MYSQL_PASSWORD] = value
468 if option == '--mysql_socket':
469 option_dict[ARG_MYSQL_SOCKET] = value
471 if option == '--smtp_host':
472 option_dict[ARG_SMTP_HOST] = value
474 if option == '--smtp_port':
475 option_dict[ARG_SMTP_PORT] = _ParsePort(value, '--smtp_port')
477 if option == '--smtp_user':
478 option_dict[ARG_SMTP_USER] = value
480 if option == '--smtp_password':
481 option_dict[ARG_SMTP_PASSWORD] = value
483 if option == '--enable_sendmail':
484 option_dict[ARG_ENABLE_SENDMAIL] = True
486 if option == '--show_mail_body':
487 option_dict[ARG_SHOW_MAIL_BODY] = True
489 if option == '--auth_domain':
490 option_dict['_DEFAULT_ENV_AUTH_DOMAIN'] = value
492 if option == '--debug_imports':
493 option_dict['_ENABLE_LOGGING'] = True
495 if option == '--admin_console_server':
496 option_dict[ARG_ADMIN_CONSOLE_SERVER] = value.strip()
498 if option == '--admin_console_host':
499 option_dict[ARG_ADMIN_CONSOLE_HOST] = value
501 if option == '--allow_skipped_files':
502 option_dict[ARG_ALLOW_SKIPPED_FILES] = True
504 if option == '--disable_static_caching':
505 option_dict[ARG_STATIC_CACHING] = False
507 if option == '--disable_task_running':
508 option_dict[ARG_DISABLE_TASK_RUNNING] = True
510 if option == '--task_retry_seconds':
511 try:
512 option_dict[ARG_TASK_RETRY_SECONDS] = int(value)
513 if option_dict[ARG_TASK_RETRY_SECONDS] < 0:
514 raise ValueError
515 except ValueError:
516 print >>sys.stderr, 'Invalid value supplied for task_retry_seconds'
517 PrintUsageExit(1)
519 if option == '--trusted':
520 option_dict[ARG_TRUSTED] = True
522 if option == '--logs_path':
523 option_dict[ARG_LOGS_PATH] = value
524 if option == '--persist_logs' and not option_dict[ARG_LOGS_PATH]:
525 option_dict[ARG_LOGS_PATH] = os.path.join(tempfile.gettempdir(),
526 'dev_appserver.logs')
528 if option == '--backends':
529 option_dict[ARG_BACKENDS] = value
530 if option == '--multiprocess':
531 option_dict[ARG_MULTIPROCESS] = value
532 if option == '--multiprocess_min_port':
533 option_dict[ARG_MULTIPROCESS_MIN_PORT] = value
534 if option == '--multiprocess_api_server':
535 option_dict[ARG_MULTIPROCESS_API_SERVER] = value
536 if option == '--multiprocess_api_port':
537 option_dict[ARG_MULTIPROCESS_API_PORT] = value
538 if option == '--multiprocess_app_instance_id':
539 option_dict[ARG_MULTIPROCESS_APP_INSTANCE_ID] = value
540 if option == '--multiprocess_backend_id':
541 option_dict[ARG_MULTIPROCESS_BACKEND_ID] = value
542 if option == '--multiprocess_backend_instance_id':
543 option_dict[ARG_MULTIPROCESS_BACKEND_INSTANCE_ID] = value
544 if option == '--multiprocess_frontend_port':
545 option_dict[ARG_MULTIPROCESS_FRONTEND_PORT] = value
547 if option == '--default_partition':
548 option_dict[ARG_DEFAULT_PARTITION] = value
550 if option == '--enable_console':
551 option_dict[ARG_CONSOLE] = True
552 if option == '--disable_console':
553 option_dict[ARG_CONSOLE] = False
555 option_dict.setdefault(ARG_CONSOLE,
556 option_dict[ARG_ADDRESS] == DEFAULT_ARGS[ARG_ADDRESS])
557 return args, option_dict
560 def _ParsePort(port, description):
561 """Parses a port number from a string.
563 Args:
564 port: string
565 description: string to use in error messages.
567 Returns: integer between 0 and 65535
569 Raises:
570 ValueError if port is not a valid port number.
572 try:
573 port = int(port)
574 if not (65535 > port > 0):
575 raise ValueError
576 return port
577 except ValueError:
578 print >>sys.stderr, 'Invalid value %s supplied for %s' % (port, description)
579 PrintUsageExit(1)
582 def MakeRpcServer(option_dict):
583 """Create a new HttpRpcServer.
585 Creates a new HttpRpcServer to check for updates to the SDK.
587 Args:
588 option_dict: The dict of command line options.
590 Returns:
591 A HttpRpcServer.
593 server = appengine_rpc.HttpRpcServer(
594 option_dict[ARG_ADMIN_CONSOLE_SERVER],
595 lambda: ('unused_email', 'unused_password'),
596 appcfg.GetUserAgent(sdk_product='dev_appserver_py'),
597 appcfg.GetSourceName(),
598 host_override=option_dict[ARG_ADMIN_CONSOLE_HOST])
600 server.authenticated = True
601 return server
604 def SigTermHandler(signum, frame):
605 """Handler for TERM signal.
607 Raises a KeyboardInterrupt to perform a graceful shutdown on SIGTERM signal.
609 raise KeyboardInterrupt()
612 def main(argv):
613 """Runs the development application server."""
614 args, option_dict = ParseArguments(argv)
616 if len(args) != 1:
617 print >>sys.stderr, 'Invalid arguments'
618 PrintUsageExit(1)
620 root_path = args[0]
622 if '_DEFAULT_ENV_AUTH_DOMAIN' in option_dict:
623 auth_domain = option_dict['_DEFAULT_ENV_AUTH_DOMAIN']
624 old_dev_appserver.DEFAULT_ENV['AUTH_DOMAIN'] = auth_domain
625 if '_ENABLE_LOGGING' in option_dict:
626 enable_logging = option_dict['_ENABLE_LOGGING']
627 old_dev_appserver.HardenedModulesHook.ENABLE_LOGGING = enable_logging
629 log_level = option_dict[ARG_LOG_LEVEL]
633 option_dict['root_path'] = os.path.realpath(root_path)
636 logging.getLogger().setLevel(log_level)
638 default_partition = option_dict[ARG_DEFAULT_PARTITION]
639 appinfo = None
640 try:
641 appinfo, _, _ = old_dev_appserver.LoadAppConfig(
642 root_path, {}, default_partition=default_partition)
643 except yaml_errors.EventListenerError, e:
644 logging.error('Fatal error when loading application configuration:\n%s', e)
645 return 1
646 except old_dev_appserver.InvalidAppConfigError, e:
647 logging.error('Application configuration file invalid:\n%s', e)
648 return 1
650 version_tuple = tuple(sys.version_info[:2])
651 expected_version = PRODUCTION_VERSION
652 if appinfo.runtime == 'python27':
653 expected_version = (2, 7)
655 if ARG_MULTIPROCESS not in option_dict and WARN_ABOUT_PYTHON_VERSION:
656 if version_tuple < expected_version:
657 sys.stderr.write('Warning: You are using a Python runtime (%d.%d) that '
658 'is older than the production runtime environment '
659 '(%d.%d). Your application may be dependent on Python '
660 'behaviors that have changed and may not work correctly '
661 'when deployed to production.\n' % (
662 version_tuple[0], version_tuple[1],
663 expected_version[0], expected_version[1]))
665 if version_tuple > expected_version:
666 sys.stderr.write('Warning: You are using a Python runtime (%d.%d) that '
667 'is more recent than the production runtime environment '
668 '(%d.%d). Your application may use features that are '
669 'not available in the production environment and may '
670 'not work correctly when deployed to production.\n' % (
671 version_tuple[0], version_tuple[1],
672 expected_version[0], expected_version[1]))
674 if appinfo.runtime == 'python':
675 appcfg.MigratePython27Notice()
677 multiprocess.Init(argv, option_dict, root_path, appinfo)
678 dev_process = multiprocess.GlobalProcess()
679 port = option_dict[ARG_PORT]
680 login_url = option_dict[ARG_LOGIN_URL]
681 address = option_dict[ARG_ADDRESS]
682 allow_skipped_files = option_dict[ARG_ALLOW_SKIPPED_FILES]
683 static_caching = option_dict[ARG_STATIC_CACHING]
684 skip_sdk_update_check = option_dict[ARG_SKIP_SDK_UPDATE_CHECK]
685 interactive_console = option_dict[ARG_CONSOLE]
687 if (option_dict[ARG_ADMIN_CONSOLE_SERVER] != '' and
688 not dev_process.IsSubprocess()):
690 server = MakeRpcServer(option_dict)
691 if skip_sdk_update_check:
692 logging.info('Skipping update check.')
693 else:
694 update_check = sdk_update_checker.SDKUpdateChecker(server, appinfo)
695 update_check.CheckSupportedVersion()
696 if update_check.AllowedToCheckForUpdates():
697 update_check.CheckForUpdates()
699 if dev_process.IsSubprocess():
700 logging.getLogger().setLevel(logging.WARNING)
702 try:
703 old_dev_appserver.SetupStubs(appinfo.application,
704 _use_atexit_for_datastore_stub=True,
705 **option_dict)
706 except:
707 exc_type, exc_value, exc_traceback = sys.exc_info()
708 logging.error(str(exc_type) + ': ' + str(exc_value))
709 logging.debug(''.join(traceback.format_exception(
710 exc_type, exc_value, exc_traceback)))
711 return 1
713 frontend_port=option_dict.get(ARG_MULTIPROCESS_FRONTEND_PORT, None)
714 if frontend_port is not None:
715 frontend_port = int(frontend_port)
716 http_server = old_dev_appserver.CreateServer(
717 root_path,
718 login_url,
719 port,
720 sdk_dir=SDK_PATH,
721 serve_address=address,
722 allow_skipped_files=allow_skipped_files,
723 static_caching=static_caching,
724 default_partition=default_partition,
725 frontend_port=frontend_port,
726 interactive_console=interactive_console)
728 signal.signal(signal.SIGTERM, SigTermHandler)
730 dev_process.PrintStartMessage(appinfo.application, address, port)
732 if dev_process.IsInstance():
733 logging.getLogger().setLevel(logging.INFO)
735 try:
736 try:
737 http_server.serve_forever()
738 except KeyboardInterrupt:
739 if not dev_process.IsSubprocess():
740 logging.info('Server interrupted by user, terminating')
741 except:
742 exc_info = sys.exc_info()
743 info_string = '\n'.join(traceback.format_exception(*exc_info))
744 logging.error('Error encountered:\n%s\nNow terminating.', info_string)
745 return 1
746 finally:
747 http_server.server_close()
748 finally:
749 done = False
750 while not done:
751 try:
752 multiprocess.Shutdown()
753 done = True
754 except KeyboardInterrupt:
755 pass
756 old_dev_appserver.TearDownStubs()
758 return 0
762 if __name__ == '__main__':
763 sys.exit(main(sys.argv))