profiledir.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # encoding: utf-8
  2. """An object for managing IPython profile directories."""
  3. # Copyright (c) IPython Development Team.
  4. # Distributed under the terms of the Modified BSD License.
  5. import os
  6. import shutil
  7. import errno
  8. from pathlib import Path
  9. from traitlets.config.configurable import LoggingConfigurable
  10. from ..paths import get_ipython_package_dir
  11. from ..utils.path import expand_path, ensure_dir_exists
  12. from traitlets import Unicode, Bool, observe
  13. from typing import Optional
  14. #-----------------------------------------------------------------------------
  15. # Module errors
  16. #-----------------------------------------------------------------------------
  17. class ProfileDirError(Exception):
  18. pass
  19. #-----------------------------------------------------------------------------
  20. # Class for managing profile directories
  21. #-----------------------------------------------------------------------------
  22. class ProfileDir(LoggingConfigurable):
  23. """An object to manage the profile directory and its resources.
  24. The profile directory is used by all IPython applications, to manage
  25. configuration, logging and security.
  26. This object knows how to find, create and manage these directories. This
  27. should be used by any code that wants to handle profiles.
  28. """
  29. security_dir_name = Unicode('security')
  30. log_dir_name = Unicode('log')
  31. startup_dir_name = Unicode('startup')
  32. pid_dir_name = Unicode('pid')
  33. static_dir_name = Unicode('static')
  34. security_dir = Unicode(u'')
  35. log_dir = Unicode(u'')
  36. startup_dir = Unicode(u'')
  37. pid_dir = Unicode(u'')
  38. static_dir = Unicode(u'')
  39. location = Unicode(u'',
  40. help="""Set the profile location directly. This overrides the logic used by the
  41. `profile` option.""",
  42. ).tag(config=True)
  43. _location_isset = Bool(False) # flag for detecting multiply set location
  44. @observe('location')
  45. def _location_changed(self, change):
  46. if self._location_isset:
  47. raise RuntimeError("Cannot set profile location more than once.")
  48. self._location_isset = True
  49. new = change['new']
  50. ensure_dir_exists(new)
  51. # ensure config files exist:
  52. self.security_dir = os.path.join(new, self.security_dir_name)
  53. self.log_dir = os.path.join(new, self.log_dir_name)
  54. self.startup_dir = os.path.join(new, self.startup_dir_name)
  55. self.pid_dir = os.path.join(new, self.pid_dir_name)
  56. self.static_dir = os.path.join(new, self.static_dir_name)
  57. self.check_dirs()
  58. def _mkdir(self, path: str, mode: Optional[int] = None) -> bool:
  59. """ensure a directory exists at a given path
  60. This is a version of os.mkdir, with the following differences:
  61. - returns whether the directory has been created or not.
  62. - ignores EEXIST, protecting against race conditions where
  63. the dir may have been created in between the check and
  64. the creation
  65. - sets permissions if requested and the dir already exists
  66. Parameters
  67. ----------
  68. path: str
  69. path of the dir to create
  70. mode: int
  71. see `mode` of `os.mkdir`
  72. Returns
  73. -------
  74. bool:
  75. returns True if it created the directory, False otherwise
  76. """
  77. if os.path.exists(path):
  78. if mode and os.stat(path).st_mode != mode:
  79. try:
  80. os.chmod(path, mode)
  81. except OSError:
  82. self.log.warning(
  83. "Could not set permissions on %s",
  84. path
  85. )
  86. return False
  87. try:
  88. if mode:
  89. os.mkdir(path, mode)
  90. else:
  91. os.mkdir(path)
  92. except OSError as e:
  93. if e.errno == errno.EEXIST:
  94. return False
  95. else:
  96. raise
  97. return True
  98. @observe('log_dir')
  99. def check_log_dir(self, change=None):
  100. self._mkdir(self.log_dir)
  101. @observe('startup_dir')
  102. def check_startup_dir(self, change=None):
  103. if self._mkdir(self.startup_dir):
  104. readme = os.path.join(self.startup_dir, "README")
  105. src = os.path.join(
  106. get_ipython_package_dir(), "core", "profile", "README_STARTUP"
  107. )
  108. if os.path.exists(src):
  109. if not os.path.exists(readme):
  110. shutil.copy(src, readme)
  111. else:
  112. self.log.warning(
  113. "Could not copy README_STARTUP to startup dir. Source file %s does not exist.",
  114. src,
  115. )
  116. @observe('security_dir')
  117. def check_security_dir(self, change=None):
  118. self._mkdir(self.security_dir, 0o40700)
  119. @observe('pid_dir')
  120. def check_pid_dir(self, change=None):
  121. self._mkdir(self.pid_dir, 0o40700)
  122. def check_dirs(self):
  123. self.check_security_dir()
  124. self.check_log_dir()
  125. self.check_pid_dir()
  126. self.check_startup_dir()
  127. def copy_config_file(self, config_file: str, path: Path, overwrite=False) -> bool:
  128. """Copy a default config file into the active profile directory.
  129. Default configuration files are kept in :mod:`IPython.core.profile`.
  130. This function moves these from that location to the working profile
  131. directory.
  132. """
  133. dst = Path(os.path.join(self.location, config_file))
  134. if dst.exists() and not overwrite:
  135. return False
  136. if path is None:
  137. path = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'default')
  138. assert isinstance(path, Path)
  139. src = path / config_file
  140. shutil.copy(src, dst)
  141. return True
  142. @classmethod
  143. def create_profile_dir(cls, profile_dir, config=None):
  144. """Create a new profile directory given a full path.
  145. Parameters
  146. ----------
  147. profile_dir : str
  148. The full path to the profile directory. If it does exist, it will
  149. be used. If not, it will be created.
  150. """
  151. return cls(location=profile_dir, config=config)
  152. @classmethod
  153. def create_profile_dir_by_name(cls, path, name=u'default', config=None):
  154. """Create a profile dir by profile name and path.
  155. Parameters
  156. ----------
  157. path : unicode
  158. The path (directory) to put the profile directory in.
  159. name : unicode
  160. The name of the profile. The name of the profile directory will
  161. be "profile_<profile>".
  162. """
  163. if not os.path.isdir(path):
  164. raise ProfileDirError('Directory not found: %s' % path)
  165. profile_dir = os.path.join(path, u'profile_' + name)
  166. return cls(location=profile_dir, config=config)
  167. @classmethod
  168. def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
  169. """Find an existing profile dir by profile name, return its ProfileDir.
  170. This searches through a sequence of paths for a profile dir. If it
  171. is not found, a :class:`ProfileDirError` exception will be raised.
  172. The search path algorithm is:
  173. 1. ``os.getcwd()`` # removed for security reason.
  174. 2. ``ipython_dir``
  175. Parameters
  176. ----------
  177. ipython_dir : unicode or str
  178. The IPython directory to use.
  179. name : unicode or str
  180. The name of the profile. The name of the profile directory
  181. will be "profile_<profile>".
  182. """
  183. dirname = u'profile_' + name
  184. paths = [ipython_dir]
  185. for p in paths:
  186. profile_dir = os.path.join(p, dirname)
  187. if os.path.isdir(profile_dir):
  188. return cls(location=profile_dir, config=config)
  189. else:
  190. raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
  191. @classmethod
  192. def find_profile_dir(cls, profile_dir, config=None):
  193. """Find/create a profile dir and return its ProfileDir.
  194. This will create the profile directory if it doesn't exist.
  195. Parameters
  196. ----------
  197. profile_dir : unicode or str
  198. The path of the profile directory.
  199. """
  200. profile_dir = expand_path(profile_dir)
  201. if not os.path.isdir(profile_dir):
  202. raise ProfileDirError('Profile directory not found: %s' % profile_dir)
  203. return cls(location=profile_dir, config=config)