command.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. """The root `jupyter` command.
  2. This does nothing other than dispatch to subcommands or output path info.
  3. """
  4. # Copyright (c) Jupyter Development Team.
  5. # Distributed under the terms of the Modified BSD License.
  6. from __future__ import print_function
  7. import argparse
  8. import errno
  9. import json
  10. import os
  11. import sys
  12. import sysconfig
  13. from subprocess import Popen
  14. try:
  15. # py3
  16. from shutil import which
  17. except ImportError:
  18. from .utils.shutil_which import which
  19. from . import paths
  20. from .version import __version__
  21. class JupyterParser(argparse.ArgumentParser):
  22. @property
  23. def epilog(self):
  24. """Add subcommands to epilog on request
  25. Avoids searching PATH for subcommands unless help output is requested.
  26. """
  27. return 'Available subcommands: %s' % ' '.join(list_subcommands())
  28. @epilog.setter
  29. def epilog(self, x):
  30. """Ignore epilog set in Parser.__init__"""
  31. pass
  32. def jupyter_parser():
  33. parser = JupyterParser(
  34. description="Jupyter: Interactive Computing",
  35. )
  36. group = parser.add_mutually_exclusive_group(required=True)
  37. # don't use argparse's version action because it prints to stderr on py2
  38. group.add_argument('--version', action='store_true',
  39. help="show the jupyter command's version and exit")
  40. group.add_argument('subcommand', type=str, nargs='?', help='the subcommand to launch')
  41. group.add_argument('--config-dir', action='store_true',
  42. help="show Jupyter config dir")
  43. group.add_argument('--data-dir', action='store_true',
  44. help="show Jupyter data dir")
  45. group.add_argument('--runtime-dir', action='store_true',
  46. help="show Jupyter runtime dir")
  47. group.add_argument('--paths', action='store_true',
  48. help="show all Jupyter paths. Add --json for machine-readable format.")
  49. parser.add_argument('--json', action='store_true',
  50. help="output paths as machine-readable json")
  51. return parser
  52. def list_subcommands():
  53. """List all jupyter subcommands
  54. searches PATH for `jupyter-name`
  55. Returns a list of jupyter's subcommand names, without the `jupyter-` prefix.
  56. Nested children (e.g. jupyter-sub-subsub) are not included.
  57. """
  58. subcommand_tuples = set()
  59. # construct a set of `('foo', 'bar') from `jupyter-foo-bar`
  60. for d in _path_with_self():
  61. try:
  62. names = os.listdir(d)
  63. except OSError:
  64. continue
  65. for name in names:
  66. if name.startswith('jupyter-'):
  67. if sys.platform.startswith('win'):
  68. # remove file-extension on Windows
  69. name = os.path.splitext(name)[0]
  70. subcommand_tuples.add(tuple(name.split('-')[1:]))
  71. # build a set of subcommand strings, excluding subcommands whose parents are defined
  72. subcommands = set()
  73. # Only include `jupyter-foo-bar` if `jupyter-foo` is not already present
  74. for sub_tup in subcommand_tuples:
  75. if not any(sub_tup[:i] in subcommand_tuples for i in range(1, len(sub_tup))):
  76. subcommands.add('-'.join(sub_tup))
  77. return sorted(subcommands)
  78. def _execvp(cmd, argv):
  79. """execvp, except on Windows where it uses Popen
  80. Python provides execvp on Windows, but its behavior is problematic (Python bug#9148).
  81. """
  82. if sys.platform.startswith('win'):
  83. # PATH is ignored when shell=False,
  84. # so rely on shutil.which
  85. try:
  86. from shutil import which
  87. except ImportError:
  88. from .utils.shutil_which import which
  89. cmd_path = which(cmd)
  90. if cmd_path is None:
  91. raise OSError('%r not found' % cmd, errno.ENOENT)
  92. p = Popen([cmd_path] + argv[1:])
  93. # Don't raise KeyboardInterrupt in the parent process.
  94. # Set this after spawning, to avoid subprocess inheriting handler.
  95. import signal
  96. signal.signal(signal.SIGINT, signal.SIG_IGN)
  97. p.wait()
  98. sys.exit(p.returncode)
  99. else:
  100. os.execvp(cmd, argv)
  101. def _jupyter_abspath(subcommand):
  102. """This method get the abspath of a specified jupyter-subcommand with no
  103. changes on ENV.
  104. """
  105. # get env PATH with self
  106. search_path = os.pathsep.join(_path_with_self())
  107. # get the abs path for the jupyter-<subcommand>
  108. jupyter_subcommand = 'jupyter-{}'.format(subcommand)
  109. abs_path = which(jupyter_subcommand, path=search_path)
  110. if abs_path is None:
  111. raise Exception(
  112. 'Jupyter command `{}` not found.'.format(jupyter_subcommand)
  113. )
  114. if not os.access(abs_path, os.X_OK):
  115. raise Exception(
  116. 'Jupyter command `{}` is not executable.'.format(jupyter_subcommand)
  117. )
  118. return abs_path
  119. def _path_with_self():
  120. """Put `jupyter`'s dir at the front of PATH
  121. Ensures that /path/to/jupyter subcommand
  122. will do /path/to/jupyter-subcommand
  123. even if /other/jupyter-subcommand is ahead of it on PATH
  124. """
  125. path_list = (os.environ.get('PATH') or os.defpath).split(os.pathsep)
  126. # Insert the "scripts" directory for this Python installation
  127. # This allows the "jupyter" command to be relocated, while still
  128. # finding subcommands that have been installed in the default
  129. # location.
  130. # We put the scripts directory at the *end* of PATH, so that
  131. # if the user explicitly overrides a subcommand, that override
  132. # still takes effect.
  133. try:
  134. bindir = sysconfig.get_path('scripts')
  135. except KeyError:
  136. # The Python environment does not specify a "scripts" location
  137. pass
  138. else:
  139. path_list.append(bindir)
  140. scripts = [sys.argv[0]]
  141. if os.path.islink(scripts[0]):
  142. # include realpath, if `jupyter` is a symlink
  143. scripts.append(os.path.realpath(scripts[0]))
  144. for script in scripts:
  145. bindir = os.path.dirname(script)
  146. if (
  147. os.path.isdir(bindir)
  148. and os.access(script, os.X_OK) # only if it's a script
  149. ):
  150. # ensure executable's dir is on PATH
  151. # avoids missing subcommands when jupyter is run via absolute path
  152. path_list.insert(0, bindir)
  153. return path_list
  154. def main():
  155. if len(sys.argv) > 1 and not sys.argv[1].startswith('-'):
  156. # Don't parse if a subcommand is given
  157. # Avoids argparse gobbling up args passed to subcommand, such as `-h`.
  158. subcommand = sys.argv[1]
  159. else:
  160. parser = jupyter_parser()
  161. args, opts = parser.parse_known_args()
  162. subcommand = args.subcommand
  163. if args.version:
  164. print('{:<17}:'.format('jupyter core'), __version__)
  165. for package, name in [
  166. ('notebook', 'jupyter-notebook'),
  167. ('qtconsole', 'qtconsole'),
  168. ('IPython', 'ipython'),
  169. ('ipykernel', 'ipykernel'),
  170. ('jupyter_client', 'jupyter client'),
  171. ('jupyterlab', 'jupyter lab'),
  172. ('nbconvert', 'nbconvert'),
  173. ('ipywidgets', 'ipywidgets'),
  174. ('nbformat', 'nbformat'),
  175. ('traitlets', 'traitlets'),
  176. ]:
  177. version = None
  178. try:
  179. mod = __import__(package)
  180. version = mod.__version__
  181. except ImportError:
  182. version = 'not installed'
  183. print('{:<17}:'.format(name), version)
  184. return
  185. if args.json and not args.paths:
  186. sys.exit("--json is only used with --paths")
  187. if args.config_dir:
  188. print(paths.jupyter_config_dir())
  189. return
  190. if args.data_dir:
  191. print(paths.jupyter_data_dir())
  192. return
  193. if args.runtime_dir:
  194. print(paths.jupyter_runtime_dir())
  195. return
  196. if args.paths:
  197. data = {}
  198. data['runtime'] = [paths.jupyter_runtime_dir()]
  199. data['config'] = paths.jupyter_config_path()
  200. data['data'] = paths.jupyter_path()
  201. if args.json:
  202. print(json.dumps(data))
  203. else:
  204. for name in sorted(data):
  205. path = data[name]
  206. print('%s:' % name)
  207. for p in path:
  208. print(' ' + p)
  209. return
  210. if not subcommand:
  211. parser.print_usage(file=sys.stderr)
  212. sys.exit("subcommand is required")
  213. command = _jupyter_abspath(subcommand)
  214. try:
  215. _execvp(command, sys.argv[1:])
  216. except OSError as e:
  217. sys.exit("Error executing Jupyter command %r: %s" % (subcommand, e))
  218. if __name__ == '__main__':
  219. main()