|
|
|
"""An object for managing IPython profile directories.""" |
|
|
|
|
|
|
|
|
|
import os |
|
import shutil |
|
import errno |
|
from pathlib import Path |
|
|
|
from traitlets.config.configurable import LoggingConfigurable |
|
from ..paths import get_ipython_package_dir |
|
from ..utils.path import expand_path, ensure_dir_exists |
|
from traitlets import Unicode, Bool, observe |
|
|
|
|
|
|
|
|
|
|
|
class ProfileDirError(Exception): |
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProfileDir(LoggingConfigurable): |
|
"""An object to manage the profile directory and its resources. |
|
|
|
The profile directory is used by all IPython applications, to manage |
|
configuration, logging and security. |
|
|
|
This object knows how to find, create and manage these directories. This |
|
should be used by any code that wants to handle profiles. |
|
""" |
|
|
|
security_dir_name = Unicode('security') |
|
log_dir_name = Unicode('log') |
|
startup_dir_name = Unicode('startup') |
|
pid_dir_name = Unicode('pid') |
|
static_dir_name = Unicode('static') |
|
security_dir = Unicode(u'') |
|
log_dir = Unicode(u'') |
|
startup_dir = Unicode(u'') |
|
pid_dir = Unicode(u'') |
|
static_dir = Unicode(u'') |
|
|
|
location = Unicode(u'', |
|
help="""Set the profile location directly. This overrides the logic used by the |
|
`profile` option.""", |
|
).tag(config=True) |
|
|
|
_location_isset = Bool(False) |
|
@observe('location') |
|
def _location_changed(self, change): |
|
if self._location_isset: |
|
raise RuntimeError("Cannot set profile location more than once.") |
|
self._location_isset = True |
|
new = change['new'] |
|
ensure_dir_exists(new) |
|
|
|
|
|
self.security_dir = os.path.join(new, self.security_dir_name) |
|
self.log_dir = os.path.join(new, self.log_dir_name) |
|
self.startup_dir = os.path.join(new, self.startup_dir_name) |
|
self.pid_dir = os.path.join(new, self.pid_dir_name) |
|
self.static_dir = os.path.join(new, self.static_dir_name) |
|
self.check_dirs() |
|
|
|
def _mkdir(self, path, mode=None): |
|
"""ensure a directory exists at a given path |
|
|
|
This is a version of os.mkdir, with the following differences: |
|
|
|
- returns True if it created the directory, False otherwise |
|
- ignores EEXIST, protecting against race conditions where |
|
the dir may have been created in between the check and |
|
the creation |
|
- sets permissions if requested and the dir already exists |
|
""" |
|
if os.path.exists(path): |
|
if mode and os.stat(path).st_mode != mode: |
|
try: |
|
os.chmod(path, mode) |
|
except OSError: |
|
self.log.warning( |
|
"Could not set permissions on %s", |
|
path |
|
) |
|
return False |
|
try: |
|
if mode: |
|
os.mkdir(path, mode) |
|
else: |
|
os.mkdir(path) |
|
except OSError as e: |
|
if e.errno == errno.EEXIST: |
|
return False |
|
else: |
|
raise |
|
|
|
return True |
|
|
|
@observe('log_dir') |
|
def check_log_dir(self, change=None): |
|
self._mkdir(self.log_dir) |
|
|
|
@observe('startup_dir') |
|
def check_startup_dir(self, change=None): |
|
self._mkdir(self.startup_dir) |
|
|
|
readme = os.path.join(self.startup_dir, 'README') |
|
src = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'README_STARTUP') |
|
|
|
if not os.path.exists(src): |
|
self.log.warning("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src) |
|
|
|
if os.path.exists(src) and not os.path.exists(readme): |
|
shutil.copy(src, readme) |
|
|
|
@observe('security_dir') |
|
def check_security_dir(self, change=None): |
|
self._mkdir(self.security_dir, 0o40700) |
|
|
|
@observe('pid_dir') |
|
def check_pid_dir(self, change=None): |
|
self._mkdir(self.pid_dir, 0o40700) |
|
|
|
def check_dirs(self): |
|
self.check_security_dir() |
|
self.check_log_dir() |
|
self.check_pid_dir() |
|
self.check_startup_dir() |
|
|
|
def copy_config_file(self, config_file: str, path: Path, overwrite=False) -> bool: |
|
"""Copy a default config file into the active profile directory. |
|
|
|
Default configuration files are kept in :mod:`IPython.core.profile`. |
|
This function moves these from that location to the working profile |
|
directory. |
|
""" |
|
dst = Path(os.path.join(self.location, config_file)) |
|
if dst.exists() and not overwrite: |
|
return False |
|
if path is None: |
|
path = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'default') |
|
assert isinstance(path, Path) |
|
src = path / config_file |
|
shutil.copy(src, dst) |
|
return True |
|
|
|
@classmethod |
|
def create_profile_dir(cls, profile_dir, config=None): |
|
"""Create a new profile directory given a full path. |
|
|
|
Parameters |
|
---------- |
|
profile_dir : str |
|
The full path to the profile directory. If it does exist, it will |
|
be used. If not, it will be created. |
|
""" |
|
return cls(location=profile_dir, config=config) |
|
|
|
@classmethod |
|
def create_profile_dir_by_name(cls, path, name=u'default', config=None): |
|
"""Create a profile dir by profile name and path. |
|
|
|
Parameters |
|
---------- |
|
path : unicode |
|
The path (directory) to put the profile directory in. |
|
name : unicode |
|
The name of the profile. The name of the profile directory will |
|
be "profile_<profile>". |
|
""" |
|
if not os.path.isdir(path): |
|
raise ProfileDirError('Directory not found: %s' % path) |
|
profile_dir = os.path.join(path, u'profile_' + name) |
|
return cls(location=profile_dir, config=config) |
|
|
|
@classmethod |
|
def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): |
|
"""Find an existing profile dir by profile name, return its ProfileDir. |
|
|
|
This searches through a sequence of paths for a profile dir. If it |
|
is not found, a :class:`ProfileDirError` exception will be raised. |
|
|
|
The search path algorithm is: |
|
1. ``os.getcwd()`` # removed for security reason. |
|
2. ``ipython_dir`` |
|
|
|
Parameters |
|
---------- |
|
ipython_dir : unicode or str |
|
The IPython directory to use. |
|
name : unicode or str |
|
The name of the profile. The name of the profile directory |
|
will be "profile_<profile>". |
|
""" |
|
dirname = u'profile_' + name |
|
paths = [ipython_dir] |
|
for p in paths: |
|
profile_dir = os.path.join(p, dirname) |
|
if os.path.isdir(profile_dir): |
|
return cls(location=profile_dir, config=config) |
|
else: |
|
raise ProfileDirError('Profile directory not found in paths: %s' % dirname) |
|
|
|
@classmethod |
|
def find_profile_dir(cls, profile_dir, config=None): |
|
"""Find/create a profile dir and return its ProfileDir. |
|
|
|
This will create the profile directory if it doesn't exist. |
|
|
|
Parameters |
|
---------- |
|
profile_dir : unicode or str |
|
The path of the profile directory. |
|
""" |
|
profile_dir = expand_path(profile_dir) |
|
if not os.path.isdir(profile_dir): |
|
raise ProfileDirError('Profile directory not found: %s' % profile_dir) |
|
return cls(location=profile_dir, config=config) |
|
|