profiledir.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. if not os.path.exists(readme):
  106. import pkgutil
  107. with open(readme, "wb") as f:
  108. f.write(pkgutil.get_data(__name__, "profile/README_STARTUP"))
  109. return
  110. @observe('security_dir')
  111. def check_security_dir(self, change=None):
  112. self._mkdir(self.security_dir, 0o40700)
  113. @observe('pid_dir')
  114. def check_pid_dir(self, change=None):
  115. self._mkdir(self.pid_dir, 0o40700)
  116. def check_dirs(self):
  117. self.check_security_dir()
  118. self.check_log_dir()
  119. self.check_pid_dir()
  120. self.check_startup_dir()
  121. def copy_config_file(self, config_file: str, path: Path, overwrite=False) -> bool:
  122. """Copy a default config file into the active profile directory.
  123. Default configuration files are kept in :mod:`IPython.core.profile`.
  124. This function moves these from that location to the working profile
  125. directory.
  126. """
  127. dst = Path(os.path.join(self.location, config_file))
  128. if dst.exists() and not overwrite:
  129. return False
  130. if path is None:
  131. path = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'default')
  132. assert isinstance(path, Path)
  133. src = path / config_file
  134. shutil.copy(src, dst)
  135. return True
  136. @classmethod
  137. def create_profile_dir(cls, profile_dir, config=None):
  138. """Create a new profile directory given a full path.
  139. Parameters
  140. ----------
  141. profile_dir : str
  142. The full path to the profile directory. If it does exist, it will
  143. be used. If not, it will be created.
  144. """
  145. return cls(location=profile_dir, config=config)
  146. @classmethod
  147. def create_profile_dir_by_name(cls, path, name=u'default', config=None):
  148. """Create a profile dir by profile name and path.
  149. Parameters
  150. ----------
  151. path : unicode
  152. The path (directory) to put the profile directory in.
  153. name : unicode
  154. The name of the profile. The name of the profile directory will
  155. be "profile_<profile>".
  156. """
  157. if not os.path.isdir(path):
  158. raise ProfileDirError('Directory not found: %s' % path)
  159. profile_dir = os.path.join(path, u'profile_' + name)
  160. return cls(location=profile_dir, config=config)
  161. @classmethod
  162. def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
  163. """Find an existing profile dir by profile name, return its ProfileDir.
  164. This searches through a sequence of paths for a profile dir. If it
  165. is not found, a :class:`ProfileDirError` exception will be raised.
  166. The search path algorithm is:
  167. 1. ``os.getcwd()`` # removed for security reason.
  168. 2. ``ipython_dir``
  169. Parameters
  170. ----------
  171. ipython_dir : unicode or str
  172. The IPython directory to use.
  173. name : unicode or str
  174. The name of the profile. The name of the profile directory
  175. will be "profile_<profile>".
  176. """
  177. dirname = u'profile_' + name
  178. paths = [ipython_dir]
  179. for p in paths:
  180. profile_dir = os.path.join(p, dirname)
  181. if os.path.isdir(profile_dir):
  182. return cls(location=profile_dir, config=config)
  183. else:
  184. raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
  185. @classmethod
  186. def find_profile_dir(cls, profile_dir, config=None):
  187. """Find/create a profile dir and return its ProfileDir.
  188. This will create the profile directory if it doesn't exist.
  189. Parameters
  190. ----------
  191. profile_dir : unicode or str
  192. The path of the profile directory.
  193. """
  194. profile_dir = expand_path(profile_dir)
  195. if not os.path.isdir(profile_dir):
  196. raise ProfileDirError('Profile directory not found: %s' % profile_dir)
  197. return cls(location=profile_dir, config=config)