diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 29e3be5d92..3426c54d30 100755 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -145,6 +145,7 @@ view=['jupyter_server.view.handlers'] ) + #----------------------------------------------------------------------------- # Helper functions #----------------------------------------------------------------------------- @@ -160,11 +161,13 @@ def random_ports(port, n): for i in range(n-5): yield max(1, port + random.randint(-2*n, 2*n)) + def load_handlers(name): """Load the (URL pattern, handler) tuples for each component.""" mod = __import__(name, fromlist=['default_handlers']) return mod.default_handlers + #----------------------------------------------------------------------------- # The Tornado web application #----------------------------------------------------------------------------- @@ -236,9 +239,9 @@ def init_settings(self, jupyter_app, kernel_manager, contents_manager, template_path=template_path, static_path=jupyter_app.static_file_path, static_custom_path=jupyter_app.static_custom_path, - static_handler_class = FileFindHandler, - static_url_prefix = url_path_join(base_url, '/static/'), - static_handler_args = { + static_handler_class=FileFindHandler, + static_url_prefix=url_path_join(base_url, '/static/'), + static_handler_args={ # don't cache custom.js 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')], }, @@ -340,8 +343,8 @@ def init_handlers(self, default_services, settings): # set the URL that will be redirected from `/` handlers.append( (r'/?', RedirectWithParams, { - 'url' : settings['default_url'], - 'permanent': False, # want 302, not 301 + 'url': settings['default_url'], + 'permanent': False, # want 302, not 301 }) ) else: @@ -395,7 +398,7 @@ def _config_file_default(self): def start(self): from .auth.security import set_password set_password(config_file=self.config_file) - self.log.info("Wrote hashed password to %s" % self.config_file) + self.log.info(_i18n("Wrote hashed password to %s" % self.config_file)) def shutdown_server(server_info, timeout=5, log=None): @@ -447,15 +450,15 @@ def shutdown_server(server_info, timeout=5, log=None): class JupyterServerStopApp(JupyterApp): version = __version__ - description = "Stop currently running Jupyter server for a given port" + description = _i18n("Stop currently running Jupyter server for a given port") port = Integer(8888, config=True, - help="Port of the server to be killed. Default 8888") + help=_i18n("Port of the server to be killed. Default 8888")) def parse_command_line(self, argv=None): super(JupyterServerStopApp, self).parse_command_line(argv) if self.extra_args: - self.port=int(self.extra_args[0]) + self.port = int(self.extra_args[0]) def shutdown_server(self, server): return shutdown_server(server, log=self.log) @@ -480,7 +483,7 @@ def start(self): class JupyterServerListApp(JupyterApp): version = __version__ - description=_i18n("List currently running notebook servers.") + description = _i18n("List currently running notebook servers.") flags = dict( jsonlist=({'JupyterServerListApp': {'jsonlist': True}}, @@ -490,7 +493,7 @@ class JupyterServerListApp(JupyterApp): ) jsonlist = Bool(False, config=True, - help=_i18n("If True, the output will be a JSON list of objects, one per " + help=_i18n("If True, the output will be a JSON list of objects, one per " "active notebook server, each with the details from the " "relevant server info file.")) json = Bool(False, config=True, @@ -513,6 +516,7 @@ def start(self): url = url + '?token=%s' % serverinfo['token'] print(url, "::", serverinfo['root_dir']) + #----------------------------------------------------------------------------- # Aliases and Flags #----------------------------------------------------------------------------- @@ -539,11 +543,11 @@ def start(self): ) flags['autoreload'] = ( {'ServerApp': {'autoreload': True}}, - """Autoreload the webapp + _i18n("""Autoreload the webapp Enable reloading of the tornado webapp and all imported Python packages when any changes are made to any Python src files in server or extensions. - """ + """) ) @@ -568,6 +572,7 @@ def start(self): 'gateway-url': 'GatewayClient.url', }) + #----------------------------------------------------------------------------- # ServerApp #----------------------------------------------------------------------------- @@ -628,25 +633,25 @@ def _default_log_format(self): # file to be opened in the Jupyter server file_to_run = Unicode('', - help="Open the named file when the application is launched." + help=_i18n("Open the named file when the application is launched.") ).tag(config=True) file_url_prefix = Unicode('notebooks', - help="The URL prefix where files are opened directly." + help=_i18n("The URL prefix where files are opened directly.") ).tag(config=True) # Network related information allow_origin = Unicode('', config=True, - help="""Set the Access-Control-Allow-Origin header + help=_i18n("""Set the Access-Control-Allow-Origin header Use '*' to allow any origin to access your server. Takes precedence over allow_origin_pat. - """ + """) ) allow_origin_pat = Unicode('', config=True, - help="""Use a regular expression for the Access-Control-Allow-Origin header + help=_i18n("""Use a regular expression for the Access-Control-Allow-Origin header Requests from an origin matching the expression will get replies with: @@ -655,7 +660,7 @@ def _default_log_format(self): where `origin` is the origin of the request. Ignored if allow_origin is set. - """ + """) ) allow_credentials = Bool(False, config=True, @@ -743,13 +748,13 @@ def _default_cookie_secret_file(self): return os.path.join(self.runtime_dir, 'jupyter_cookie_secret') cookie_secret = Bytes(b'', config=True, - help="""The random bytes used to secure cookies. + help=_i18n("""The random bytes used to secure cookies. By default this is a new random number every time you start the server. Set it to a value in a config file to enable logins to persist across server sessions. Note: Cookie secrets should be kept private, do not share config files with cookie_secret stored in plaintext (you can read the value from a file). - """ + """) ) @default('cookie_secret') @@ -800,12 +805,13 @@ def _token_default(self): return binascii.hexlify(os.urandom(24)).decode('ascii') min_open_files_limit = Integer(config=True, - help=""" + help=_i18n(""" Gets or sets a lower bound on the open file handles process resource limit. This may need to be increased if you run into an OSError: [Errno 24] Too many open files. This is not applicable when running on Windows. """) + ) @default('min_open_files_limit') def _default_min_open_files_limit(self): @@ -824,21 +830,21 @@ def _default_min_open_files_limit(self): return soft max_body_size = Integer(512 * 1024 * 1024, config=True, - help=""" + help=_i18n(""" Sets the maximum allowed size of the client request body, specified in the Content-Length request header field. If the size in a request exceeds the configured value, a malformed HTTP message is returned to the client. Note: max_body_size is applied even in streaming mode. - """ + """) ) max_buffer_size = Integer(512 * 1024 * 1024, config=True, - help=""" + help=_i18n(""" Gets or sets the maximum amount of memory, in bytes, that is allocated for use by the buffer manager. - """ + """) ) @observe('token') @@ -846,41 +852,41 @@ def _token_changed(self, change): self._token_generated = False password = Unicode(u'', config=True, - help="""Hashed password to use for web authentication. + help=_i18n("""Hashed password to use for web authentication. To generate, type in a python/IPython shell: from jupyter_server.auth import passwd; passwd() The string should be of the form type:salt:hashed-password. - """ + """) ) password_required = Bool(False, config=True, - help="""Forces users to use a password for the Jupyter server. + help=_i18n("""Forces users to use a password for the Jupyter server. This is useful in a multi user environment, for instance when everybody in the LAN can access each other's machine through ssh. In such a case, serving on localhost is not secure since any user can connect to the Jupyter server via ssh. - """ + """) ) allow_password_change = Bool(True, config=True, - help="""Allow password to be changed at login for the Jupyter server. + help=_i18n("""Allow password to be changed at login for the Jupyter server. While loggin in with a token, the Jupyter server UI will give the opportunity to the user to enter a new password at the same time that will replace the token login mechanism. This can be set to false to prevent changing password from the UI/API. - """ + """) ) disable_check_xsrf = Bool(False, config=True, - help="""Disable cross-site-request-forgery protection + help=_i18n("""Disable cross-site-request-forgery protection Jupyter notebook 4.3.1 introduces protection from cross-site request forgeries, requiring API requests to either: @@ -892,11 +898,11 @@ def _token_changed(self, change): completely without authentication. These services can disable all authentication and security checks, with the full knowledge of what that implies. - """ + """) ) allow_remote_access = Bool(config=True, - help="""Allow requests where the Host header doesn't point to a local server + help=_i18n("""Allow requests where the Host header doesn't point to a local server By default, requests get a 403 forbidden response if the 'Host' header shows that the browser thinks it's on a non-local domain. @@ -909,6 +915,7 @@ def _token_changed(self, change): Local IP addresses (such as 127.0.0.1 and ::1) are allowed as local, along with hostnames configured in local_hostnames. """) + ) @default('allow_remote_access') def _default_allow_remote(self): @@ -945,7 +952,7 @@ def _default_allow_remote(self): return not addr.is_loopback use_redirect_file = Bool(True, config=True, - help="""Disable launching browser by redirect file + help=_i18n("""Disable launching browser by redirect file For versions of notebook > 5.7.2, a security feature measure was added that prevented the authentication token used to launch the browser from being visible. This feature makes it difficult for other users on a multi-user system from @@ -957,32 +964,34 @@ def _default_allow_remote(self): Disabling this setting to False will disable this behavior, allowing the browser to launch by using a URL and visible token (as before). - """ + """) ) local_hostnames = List(Unicode(), ['localhost'], config=True, - help="""Hostnames to allow as local when allow_remote_access is False. + help=_i18n("""Hostnames to allow as local when allow_remote_access is False. Local IP addresses (such as 127.0.0.1 and ::1) are automatically accepted as local as well. - """ + """) ) open_browser = Bool(False, config=True, - help="""Whether to open in a browser after starting. + help=_i18n("""Whether to open in a browser after starting. The specific browser used is platform dependent and determined by the python standard library `webbrowser` module, unless it is overridden using the --browser (ServerApp.browser) configuration option. """) + ) browser = Unicode(u'', config=True, - help="""Specify what command to use to invoke a web + help=_i18n("""Specify what command to use to invoke a web browser when starting the server. If not specified, the default browser will be determined by the `webbrowser` standard library module, which allows setting of the BROWSER environment variable to override it. """) + ) webbrowser_open_new = Integer(2, config=True, help=_i18n("""Specify where to open the server on startup. This is the @@ -1038,11 +1047,11 @@ def _default_allow_remote(self): ) base_url = Unicode('/', config=True, - help='''The base URL for the Jupyter server. + help=_i18n('''The base URL for the Jupyter server. Leading and trailing slashes can be omitted, and will automatically be added. - ''') + ''')) @validate('base_url') def _update_base_url(self, proposal): @@ -1054,10 +1063,10 @@ def _update_base_url(self, proposal): return value extra_static_paths = List(Unicode(), config=True, - help="""Extra paths to search for serving static files. + help=_i18n("""Extra paths to search for serving static files. This allows adding javascript/css to be available from the Jupyter server machine, - or overriding individual files in the IPython""" + or overriding individual files in the IPython""") ) @property @@ -1093,15 +1102,15 @@ def template_file_path(self): ) websocket_url = Unicode("", config=True, - help="""The base URL for websockets, + help=_i18n("""The base URL for websockets, if it differs from the HTTP server (hint: it almost certainly doesn't). Should be in the form of an HTTP origin: ws[s]://hostname[:port] - """ + """) ) quit_button = Bool(True, config=True, - help="""If True, display controls to shut down the Jupyter server, such as menu items or buttons.""" + help=_i18n("""If True, display controls to shut down the Jupyter server, such as menu items or buttons.""") ) # REMOVE in VERSION 2.0 @@ -1165,13 +1174,13 @@ def _update_notebook_dir(self, change): kernel_spec_manager_class = Type( default_value=KernelSpecManager, config=True, - help=""" + help=_i18n(""" The kernel spec manager class to use. Should be a subclass of `jupyter_client.kernelspec.KernelSpecManager`. The Api of KernelSpecManager is provisional and might change without warning between this version of Jupyter and the next stable one. - """ + """) ) login_handler_class = Type( @@ -1273,7 +1282,7 @@ def _root_dir_validate(self, proposal): # If we receive a non-absolute path, make it absolute. value = os.path.abspath(value) if not os.path.isdir(value): - raise TraitError(trans.gettext("No such directory: '%r'") % value) + raise TraitError(_i18n("No such directory: '%r'") % value) return value @observe('root_dir') @@ -1310,7 +1319,7 @@ def _update_server_extensions(self, change): check the message and data rate limits.""")) shutdown_no_activity_timeout = Integer(0, config=True, - help=("Shut down the server after N seconds with no kernels or " + help=_i18n("Shut down the server after N seconds with no kernels or " "terminals running and no activity. " "This can be used together with culling idle kernels " "(MappingKernelManager.cull_idle_timeout) to " @@ -1331,9 +1340,9 @@ def _update_server_extensions(self, change): authenticate_prometheus = Bool( True, - help="""" + help=_i18n(""" Require authentication to access prometheus metrics. - """, + """), config=True ) @@ -1476,7 +1485,9 @@ def init_webapp(self): def init_resources(self): """initialize system resources""" if resource is None: - self.log.debug('Ignoring min_open_files_limit because the limit cannot be adjusted (for example, on Windows)') + self.log.debug( + _i18n('Ignoring min_open_files_limit because the limit cannot be adjusted (for example, on Windows)') + ) return old_soft, old_hard = resource.getrlimit(resource.RLIMIT_NOFILE) @@ -1486,7 +1497,7 @@ def init_resources(self): if hard < soft: hard = soft self.log.debug( - 'Raising open file limit: soft {}->{}; hard {}->{}'.format(old_soft, soft, old_hard, hard) + _i18n('Raising open file limit: soft {}->{}; hard {}->{}'.format(old_soft, soft, old_hard, hard)) ) resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard)) @@ -1703,17 +1714,17 @@ def shutdown_no_activity(self): seconds_since_active = \ (utcnow() - self.web_app.last_activity()).total_seconds() - self.log.debug("No activity for %d seconds.", - seconds_since_active) + self.log.debug(_i18n("No activity for %d seconds.", + seconds_since_active)) if seconds_since_active > self.shutdown_no_activity_timeout: - self.log.info("No kernels or terminals for %d seconds; shutting down.", - seconds_since_active) + self.log.info(_i18n("No kernels or terminals for %d seconds; shutting down.", + seconds_since_active)) self.stop() def init_shutdown_no_activity(self): if self.shutdown_no_activity_timeout > 0: - self.log.info("Will shut down after %d seconds with no kernels or terminals.", - self.shutdown_no_activity_timeout) + self.log.info(_i18n("Will shut down after %d seconds with no kernels or terminals.", + self.shutdown_no_activity_timeout)) pc = ioloop.PeriodicCallback(self.shutdown_no_activity, 60000) pc.start() @@ -1771,11 +1782,11 @@ def init_httpserver(self): @staticmethod def _init_asyncio_patch(): """no longer needed with tornado 6.1""" - warnings.warn( + warnings.warn(_i18n( """ServerApp._init_asyncio_patch called, and is longer needed for """ """tornado 6.1+, and will be removed in a future release.""", DeprecationWarning - ) + )) @catch_config_error def initialize(self, argv=None, find_extensions=True, new_httpserver=True, starter_extension=None): @@ -2138,6 +2149,8 @@ def list_running_servers(runtime_dir=None): os.unlink(os.path.join(runtime_dir, file_name)) except OSError: pass # TODO: This should warn or log or something + + #----------------------------------------------------------------------------- # Main entry point #-----------------------------------------------------------------------------