profiledir.py 7.7 KB

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