shim.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. from functools import wraps
  2. from copy import deepcopy
  3. from traitlets import TraitError
  4. from traitlets.config.loader import (
  5. Config,
  6. )
  7. from jupyter_core.application import JupyterApp
  8. from jupyter_server.serverapp import ServerApp
  9. from jupyter_server.extension.application import ExtensionApp
  10. from .traits import NotebookAppTraits
  11. def NBAPP_AND_SVAPP_SHIM_MSG(trait_name): return (
  12. "'{trait_name}' was found in both NotebookApp "
  13. "and ServerApp. This is likely a recent change. "
  14. "This config will only be set in NotebookApp. "
  15. "Please check if you should also config these traits in "
  16. "ServerApp for your purpose.".format(
  17. trait_name=trait_name,
  18. )
  19. )
  20. def NBAPP_TO_SVAPP_SHIM_MSG(trait_name): return (
  21. "'{trait_name}' has moved from NotebookApp to "
  22. "ServerApp. This config will be passed to ServerApp. "
  23. "Be sure to update your config before "
  24. "our next release.".format(
  25. trait_name=trait_name,
  26. )
  27. )
  28. def EXTAPP_AND_NBAPP_AND_SVAPP_SHIM_MSG(trait_name, extapp_name): return (
  29. "'{trait_name}' is found in {extapp_name}, NotebookApp, "
  30. "and ServerApp. This is a recent change. "
  31. "This config will only be set in {extapp_name}. "
  32. "Please check if you should also config these traits in "
  33. "NotebookApp and ServerApp for your purpose.".format(
  34. trait_name=trait_name,
  35. extapp_name=extapp_name
  36. )
  37. )
  38. def EXTAPP_AND_SVAPP_SHIM_MSG(trait_name, extapp_name): return (
  39. "'{trait_name}' is found in both {extapp_name} "
  40. "and ServerApp. This is a recent change. "
  41. "This config will only be set in {extapp_name}. "
  42. "Please check if you should also config these traits in "
  43. "ServerApp for your purpose.".format(
  44. trait_name=trait_name,
  45. extapp_name=extapp_name
  46. )
  47. )
  48. def EXTAPP_AND_NBAPP_SHIM_MSG(trait_name, extapp_name): return (
  49. "'{trait_name}' is found in both {extapp_name} "
  50. "and NotebookApp. This is a recent change. "
  51. "This config will only be set in {extapp_name}. "
  52. "Please check if you should also config these traits in "
  53. "NotebookApp for your purpose.".format(
  54. trait_name=trait_name,
  55. extapp_name=extapp_name
  56. )
  57. )
  58. def NOT_EXTAPP_NBAPP_AND_SVAPP_SHIM_MSG(trait_name, extapp_name): return (
  59. "'{trait_name}' is not found in {extapp_name}, but "
  60. "it was found in both NotebookApp "
  61. "and ServerApp. This is likely a recent change. "
  62. "This config will only be set in ServerApp. "
  63. "Please check if you should also config these traits in "
  64. "NotebookApp for your purpose.".format(
  65. trait_name=trait_name,
  66. extapp_name=extapp_name
  67. )
  68. )
  69. def EXTAPP_TO_SVAPP_SHIM_MSG(trait_name, extapp_name): return (
  70. "'{trait_name}' has moved from {extapp_name} to "
  71. "ServerApp. Be sure to update your config before "
  72. "our next release.".format(
  73. trait_name=trait_name,
  74. extapp_name=extapp_name
  75. )
  76. )
  77. def EXTAPP_TO_NBAPP_SHIM_MSG(trait_name, extapp_name): return (
  78. "'{trait_name}' has moved from {extapp_name} to "
  79. "NotebookApp. Be sure to update your config before "
  80. "our next release.".format(
  81. trait_name=trait_name,
  82. extapp_name=extapp_name
  83. )
  84. )
  85. # A tuple of traits that shouldn't be shimmed or throw any
  86. # warnings of any kind.
  87. IGNORED_TRAITS = ("open_browser", "log_level", "log_format", "default_url", "show_banner")
  88. class NotebookConfigShimMixin:
  89. """A Mixin class for shimming configuration from
  90. NotebookApp to ServerApp. This class handles warnings, errors,
  91. etc.
  92. This class should be used during a transition period for apps
  93. that are switching from depending on NotebookApp to ServerApp.
  94. After one release cycle, this class can be safely removed
  95. from the inheriting class.
  96. TL;DR
  97. The entry point to shimming is at the `update_config` method.
  98. Once traits are loaded, before updating config across all
  99. configurable objects, this class injects a method to reroute
  100. traits to their *most logical* classes.
  101. This class raises warnings when:
  102. 1. a trait has moved.
  103. 2. a trait is redundant across classes.
  104. Redundant traits across multiple classes now must be
  105. configured separately, *or* removed from their old
  106. location to avoid this warning.
  107. For a longer description on how individual traits are handled,
  108. read the docstring under `shim_config_from_notebook_to_jupyter_server`.
  109. """
  110. @wraps(JupyterApp.update_config)
  111. def update_config(self, config):
  112. # Shim traits to handle transition from NotebookApp to ServerApp
  113. shimmed_config = self.shim_config_from_notebook_to_jupyter_server(
  114. config)
  115. super().update_config(shimmed_config)
  116. def shim_config_from_notebook_to_jupyter_server(self, config):
  117. """Reorganizes a config object to reroute traits to their expected destinations
  118. after the transition from NotebookApp to ServerApp.
  119. A detailed explanation of how traits are handled:
  120. 1. If the argument is prefixed with `ServerApp`,
  121. pass this trait to `ServerApp`.
  122. 2. If the argument is prefixed with `NotebookApp`,
  123. * If the argument is a trait of `NotebookApp` *and* `ServerApp`:
  124. 1. Raise a warning—**for the extension developers**—that
  125. there's redundant traits.
  126. 2. Pass trait to `NotebookApp`.
  127. * If the argument is a trait of just `ServerApp` only
  128. (i.e. the trait moved from `NotebookApp` to `ServerApp`):
  129. 1. Raise a "this trait has moved" **for the user**.
  130. 3. Pass trait to `ServerApp`.
  131. * If the argument is a trait of `NotebookApp` only, pass trait
  132. to `NotebookApp`.
  133. * If the argument is not found in any object, raise a
  134. `"Trait not found."` error.
  135. 3. If the argument is prefixed with `ExtensionApp`:
  136. * If the argument is a trait of `ExtensionApp`,
  137. `NotebookApp`, and `ServerApp`,
  138. 1. Raise a warning about redundancy.
  139. 2. Pass to the ExtensionApp
  140. * If the argument is a trait of `ExtensionApp` and `NotebookApp`,
  141. 1. Raise a warning about redundancy.
  142. 2. Pass to ExtensionApp.
  143. * If the argument is a trait of `ExtensionApp` and `ServerApp`,
  144. 1. Raise a warning about redundancy.
  145. 2. Pass to ExtensionApp.
  146. * If the argument is a trait of `ExtensionApp`.
  147. 1. Pass to ExtensionApp.
  148. * If the argument is a trait of `NotebookApp` but not `ExtensionApp`,
  149. 1. Raise a warning that trait has likely moved to NotebookApp.
  150. 2. Pass to NotebookApp
  151. * If the arguent is a trait of `ServerApp` but not `ExtensionApp`,
  152. 1. Raise a warning that the trait has likely moved to ServerApp.
  153. 2. Pass to ServerApp.
  154. * else
  155. * Raise a TraitError: "trait not found."
  156. """
  157. extapp_name = self.__class__.__name__
  158. # Pop out the various configurable objects that we need to evaluate.
  159. nbapp_config = config.pop('NotebookApp', {})
  160. svapp_config = config.pop('ServerApp', {})
  161. extapp_config = config.pop(extapp_name, {})
  162. # Created shimmed configs.
  163. # Leave the rest of the config alone.
  164. config_shim = deepcopy(config)
  165. svapp_config_shim = {}
  166. nbapp_config_shim = {}
  167. extapp_config_shim = {}
  168. extapp_traits = (
  169. self.__class__.class_trait_names() +
  170. ExtensionApp.class_trait_names()
  171. )
  172. svapp_traits = ServerApp.class_trait_names()
  173. nbapp_traits = (
  174. NotebookAppTraits.class_trait_names() +
  175. ExtensionApp.class_trait_names()
  176. )
  177. # 1. Handle ServerApp traits.
  178. svapp_config_shim.update(svapp_config)
  179. # 2. Handle NotebookApp traits.
  180. warning_msg = None
  181. for trait_name, trait_value in nbapp_config.items():
  182. in_svapp = trait_name in svapp_traits
  183. in_nbapp = trait_name in nbapp_traits
  184. if trait_name in IGNORED_TRAITS:
  185. # Pass trait through without any warning message.
  186. nbapp_config_shim.update({trait_name: trait_value})
  187. elif in_svapp and in_nbapp:
  188. warning_msg = NBAPP_AND_SVAPP_SHIM_MSG(trait_name)
  189. nbapp_config_shim.update({trait_name: trait_value})
  190. elif in_svapp:
  191. warning_msg = NBAPP_TO_SVAPP_SHIM_MSG(trait_name)
  192. svapp_config_shim.update({trait_name: trait_value})
  193. elif in_nbapp:
  194. nbapp_config_shim.update({trait_name: trait_value})
  195. else:
  196. raise TraitError("Trait, {}, not found.".format(trait_name))
  197. # Raise a warning if it's given.
  198. if warning_msg:
  199. self.log.warning(warning_msg)
  200. # 3. Handle ExtensionApp traits.
  201. warning_msg = None
  202. for trait_name, trait_value in extapp_config.items():
  203. in_extapp = trait_name in extapp_traits
  204. in_svapp = trait_name in svapp_traits
  205. in_nbapp = trait_name in nbapp_traits
  206. if trait_name in IGNORED_TRAITS:
  207. # Pass trait through without any warning message.
  208. extapp_config_shim.update({trait_name: trait_value})
  209. elif all([in_extapp, in_svapp, in_nbapp]):
  210. warning_msg = EXTAPP_AND_NBAPP_AND_SVAPP_SHIM_MSG(
  211. trait_name,
  212. extapp_name
  213. )
  214. extapp_config_shim.update({trait_name: trait_value})
  215. elif in_extapp and in_svapp:
  216. warning_msg = EXTAPP_AND_SVAPP_SHIM_MSG(
  217. trait_name,
  218. extapp_name
  219. )
  220. extapp_config_shim.update({trait_name: trait_value})
  221. elif in_extapp and in_nbapp:
  222. warning_msg = EXTAPP_AND_NBAPP_SHIM_MSG(
  223. trait_name,
  224. extapp_name
  225. )
  226. extapp_config_shim.update({trait_name: trait_value})
  227. elif in_extapp:
  228. extapp_config_shim.update({trait_name: trait_value})
  229. elif in_svapp and in_nbapp:
  230. warning_msg = NOT_EXTAPP_NBAPP_AND_SVAPP_SHIM_MSG(
  231. trait_name,
  232. extapp_name
  233. )
  234. svapp_config_shim.update({trait_name: trait_value})
  235. elif in_svapp:
  236. warning_msg = EXTAPP_TO_SVAPP_SHIM_MSG(
  237. trait_name,
  238. extapp_name
  239. )
  240. svapp_config_shim.update({trait_name: trait_value})
  241. elif in_nbapp:
  242. warning_msg = EXTAPP_TO_NBAPP_SHIM_MSG(
  243. trait_name,
  244. extapp_name
  245. )
  246. nbapp_config_shim.update({trait_name: trait_value})
  247. else:
  248. raise TraitError("Trait, {}, not found.".format(trait_name))
  249. # Raise warning if one is given
  250. if warning_msg:
  251. self.log.warning(warning_msg)
  252. # Build config for shimmed traits.
  253. new_config = Config({
  254. 'NotebookApp': nbapp_config_shim,
  255. 'ServerApp': svapp_config_shim,
  256. })
  257. if extapp_config_shim:
  258. new_config.update(Config({
  259. self.__class__.__name__: extapp_config_shim
  260. }))
  261. # Update the full config with new values
  262. config_shim.update(new_config)
  263. return config_shim