Ejemplo n.º 1
0
def _generate_index(source, logger):
    # Take contents of source, look for lists of tools and commands,
    # and insert tools and commands from bundles that come with
    # documentation
    from chimerax import app_dirs
    user_dir = os.path.join(app_dirs.user_cache_dir, 'docs', 'user')
    path = os.path.join(user_dir, 'index.html')
    if os.path.exists(path):
        return path
    os.makedirs(user_dir, exist_ok=True)

    from chimerax.core import toolshed
    ts = toolshed.get_toolshed()
    if ts is None:
        return None
    # Look for <div id="foobar">
    import lxml.html
    html = lxml.html.parse(source)
    for node in html.iterfind(".//div[@id]"):
        ident = node.attrib["id"]
        if ident == "clist":
            _update_list(ts, node, 'commands', _update_commands, logger)
        elif ident == "tlist":
            _update_list(ts, node, 'tools', _update_tools, logger)
    data = lxml.html.tostring(html)
    os.makedirs(user_dir, exist_ok=True)
    with open(path, 'wb') as f:
        f.write(data)
    return path
Ejemplo n.º 2
0
 def take_snapshot(self, session, flags):
     """For session saving"""
     from chimerax.core.toolshed import get_toolshed
     ts = get_toolshed()
     return {
         'version':
         2,
         'seqs':
         self._seqs,
         'ident':
         self.ident,
         'file attrs':
         self.file_attrs,
         'file markups':
         self.file_markups,
         'associations':
         self.associations,
         'match maps': [s.match_maps for s in self._seqs],
         'auto_destroy':
         self.auto_destroy,
         'auto_associate':
         self.auto_associate,
         'description':
         self.description,
         'intrinsic':
         self.intrinsic,
         'sseq to chain':
         self._sseq_to_chain,
         'headers': [(ts.find_bundle_for_class(hdr.__class__).name,
                      hdr.__class__.__name__, hdr.get_state())
                     for hdr in self.headers]
     }
Ejemplo n.º 3
0
 def initialize(session, bundle_info):
     global _new_bundle_handler
     ts = toolshed.get_toolshed()
     _new_bundle_handler = ts.triggers.add_handler(
         toolshed.TOOLSHED_BUNDLE_INSTALLED, _update_cache)
     # ? = ts.triggers.add_handler(
     #    toolshed.TOOLSHED_BUNDLE_UNINSTALLED, _update_cache)
     _update_cache()
Ejemplo n.º 4
0
 def delete(self):
     if self._handlers:
         from chimerax.core.toolshed import get_toolshed
         ts = get_toolshed()
         if ts:
             for h in self._handlers:
                 ts.triggers.remove_handler(h)
         self._handlers = None
     super().delete()
Ejemplo n.º 5
0
 def restore_snapshot(session, data):
     """For restoring scenes/sessions"""
     ident = data['ident'] if 'ident' in data else data['name']
     create_headers = data['version'] < 2
     aln = Alignment(session,
                     data['seqs'],
                     ident,
                     data['file attrs'],
                     data['file markups'],
                     data['auto_destroy'],
                     "session" if data['auto_associate'] else False,
                     data.get('description', ident),
                     data.get('intrinsic', False),
                     create_headers=create_headers,
                     session_restore=True)
     aln.associations = data['associations']
     for s, mm in zip(aln.seqs, data['match maps']):
         s.match_maps = mm
         for chain, match_map in mm.items():
             match_map.mod_handler = match_map.triggers.add_handler(
                 'modified', aln._mmap_mod_cb)
     if 'sseq to chain' in data:
         aln._sseq_to_chain = data['sseq to chain']
     from chimerax.core.toolshed import get_toolshed
     ts = get_toolshed()
     if not create_headers:
         for bundle_name, class_name, header_state in data['headers']:
             bundle = ts.find_bundle(bundle_name,
                                     session.logger,
                                     installed=True)
             if not bundle:
                 bundle = ts.find_bundle(bundle_name,
                                         session.logger,
                                         installed=False)
                 if bundle:
                     session.logger.error(
                         "You need to install bundle %s in order to restore"
                         " alignment header of type %s" %
                         (bundle_name, class_name))
                 else:
                     session.logger.error(
                         "Cannot restore alignment header of type %s due to"
                         " being unable to find any bundle named %s" %
                         (class_name, bundle_name))
                 continue
             header_class = bundle.get_class(class_name, session.logger)
             if header_class:
                 aln._headers.append(
                     header_class.session_restore(session, aln,
                                                  header_state))
             else:
                 session.logger.warning(
                     "Could not find alignment header class %s" %
                     class_name)
     aln._session_restore = False
     return aln
Ejemplo n.º 6
0
 def _setup(self):
     #
     # Register for updates of name register/deregister events
     #
     session = self.session
     from chimerax.core.toolshed import get_toolshed
     ts = get_toolshed()
     t1 = ts.triggers.add_handler("selector registered", self.update_names)
     t2 = ts.triggers.add_handler("selector deregistered",
                                  self.update_names)
     self._handlers = (t1, t2)
Ejemplo n.º 7
0
 def _get_bundle_dirs(self, logger, dep):
     from chimerax.core import toolshed
     from pkg_resources import Requirement, get_distribution
     req = Requirement.parse(dep)
     if not get_distribution(req):
         raise RuntimeError("unsatisfied dependency: %s" % dep)
     ts = toolshed.get_toolshed()
     bundle = ts.find_bundle(req.project_name, logger)
     if not bundle:
         # The requirement is satisfied but is not recognized
         # as a bundle.  Probably just a regular Python package.
         return None, None
     inc = bundle.include_dir()
     lib = bundle.library_dir()
     return inc, lib
Ejemplo n.º 8
0
 def make_install(self, session, debug=False, user=None, no_deps=None):
     self.make_wheel(debug=debug)
     from chimerax.core.commands import run, FileNameArg
     cmd = "toolshed install %s" % FileNameArg.unparse(self.wheel_path)
     if user is not None:
         if user:
             cmd += " user true"
         else:
             cmd += " user false"
     if no_deps is not None:
         if no_deps:
             cmd += " noDeps true"
         else:
             cmd += " noDeps false"
     from chimerax.core import toolshed
     ts = toolshed.get_toolshed()
     bundle = ts.find_bundle(self.name, session.logger)
     if bundle is not None:
         cmd += " reinstall true"
     run(session, cmd)
Ejemplo n.º 9
0
 def finish(session, bundle_info):
     global _new_bundle_handler
     ts = toolshed.get_toolshed()
     if _new_bundle_handler is not None:
         ts.triggers.remove_handler(_new_bundle_handler)
         _new_bundle_handler = None
Ejemplo n.º 10
0
def init(argv, event_loop=True):
    import sys
    if sys.platform.startswith('darwin'):
        paths = os.environ['PATH'].split(':')
        if '/usr/sbin' not in paths:
            # numpy, numexpr, and pytables need sysctl in path
            paths.append('/usr/sbin')
            os.environ['PATH'] = ':'.join(paths)
        del paths

    if sys.platform.startswith('linux'):
        # Workaround for #638:
        # "any number of threads more than one leads to 200% CPU usage"
        os.environ["OPENBLAS_NUM_THREADS"] = "1"

    # Setup SSL CA certificates file
    # This used to be only necessary for darwin, but Windows
    # appears to need it as well.  So use it for all platforms.
    import certifi
    os.environ["SSL_CERT_FILE"] = certifi.where()

    # distlib, since 0.2.8, does not recognize "Obsoletes" as a legal
    # metadata classifier, but jurko 0.6 (SOAP package) claims to be
    # Metadata-Version 2.1 but specifies Obsolete.  Hack below makes
    # Obsolete not cause a traceback.
    from distlib import metadata
    try:
        if "Obsoletes" not in metadata._566_FIELDS:
            metadata._566_FIELDS = metadata._566_FIELDS + ("Obsoletes", )
    except AttributeError:
        pass

    opts, args = parse_arguments(argv)

    # install line_profile decorator, and install it before
    # initialize_ssl_cert_dir() in case the line profiling is in the
    # core (which would cause initialize_ssl_cert_dir() to fail)
    import builtins
    if not opts.line_profile:
        builtins.__dict__['line_profile'] = lambda x: x
    else:
        # write profile results on exit
        import atexit
        import line_profiler
        prof = line_profiler.LineProfiler()
        builtins.__dict__['line_profile'] = prof
        atexit.register(prof.dump_stats, "%s.lprof" % app_name)

    from chimerax.core.utils import initialize_ssl_cert_dir
    initialize_ssl_cert_dir()

    # find chimerax.core's version
    # we cannot use pip for this because we want to update
    # site.USER_SITE before importing pip, and site.USER_SITE
    # depends on the version
    try:
        from chimerax.core import version
    except ImportError:
        print("error: unable to figure out %s's version" % app_name)
        return os.EX_SOFTWARE

    if opts.use_defaults:
        from chimerax.core import configinfo
        configinfo.only_use_defaults = True

    from chimerax import core
    if opts.offscreen:
        # Flag to configure off-screen rendering before PyOpenGL imported
        core.offscreen_rendering = True
        opts.gui = False

    if not opts.gui and opts.load_tools:
        # only load tools if we have a GUI
        opts.load_tools = False

    is_root = False  # On Linux, don't create user directories if root (the installer uid)
    # figure out the user/system directories for application
    # invoked with -m ChimeraX_main, so argv[0] is full path to ChimeraX_main
    # Windows:
    # 'C:\\...\\ChimeraX.app\\bin\\lib\\site-packages\\ChimeraX_main.py'
    # Linux:
    # '/.../ChimeraX.app/lib/python3.5/site-packages/ChimeraX_main.py'
    # Mac OS X:
    # '/.../ChimeraX.app/Contents/lib/python3.5/site-packages/ChimeraX_main.py'
    # '/.../ChimeraX.app/Contents/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/ChimeraX_main.py'
    # TODO: more robust way
    dn = os.path.dirname
    rootdir = dn(dn(dn(dn(sys.argv[0]))))
    if sys.platform.startswith('darwin'):
        rootdir = dn(dn(dn(dn(dn(rootdir)))))
    if sys.platform.startswith('linux'):
        os.environ['XDG_CONFIG_DIRS'] = rootdir
        is_root = os.getuid() == 0
        if is_root:
            # ensure toolshed cache is not written
            os.environ['HOME'] = "/non/existent/directory"

    if sys.platform.startswith('win'):
        if 'HOME' in os.environ:
            # Windows uses HOMEPATH and HOMEDRIVE, so HOME's presence indicates
            # a non-standard startup environment.  So remove HOME to make
            # sure the the correct application paths are figured out.
            del os.environ['HOME']
        import ctypes
        # getpn = ctypes.pythonapi.Py_GetProgramName
        # getpn.restype = ctypes.c_wchar_p
        # pn = getpn()
        # assert(os.path.dirname(pn) == rootdir)
        # Python uses LoadLibraryEx with LOAD_WITH_ALTERED_SEARCH_PATH to
        # search in directory of library first instead of the directory of
        # application binary.  So add back the "bin" directory, which is
        # the Windows equivalent of the Linux/Mac OS X rpath directory.
        setdlldir = ctypes.windll.kernel32.SetDllDirectoryW
        setdlldir.argtypes = [ctypes.c_wchar_p]
        setdlldir.restype = ctypes.c_bool
        setdlldir(os.path.join(rootdir, 'bin'))

    from distlib.version import NormalizedVersion as Version
    epoch, ver, *_ = Version(version).parse(version)
    if len(ver) == 1:
        ver += (0, )
    partial_version = '%s.%s' % (ver[0], ver[1])

    import chimerax
    import appdirs
    chimerax.app_dirs = ad = appdirs.AppDirs(app_name,
                                             appauthor=app_author,
                                             version=partial_version)
    if not is_root:
        # make sure app_dirs.user_* directories exist
        for var, name in (('user_data_dir', "user's data"),
                          ('user_config_dir', "user's configuration"),
                          ('user_cache_dir', "user's cache")):
            directory = getattr(ad, var)
            try:
                os.makedirs(directory, exist_ok=True)
            except OSError as e:
                print("Unable to make %s directory: %s: %s" %
                      (name, e.strerror, e.filename),
                      file=sys.stderr)
                return os.EX_CANTCREAT

    # app_dirs_unversioned is primarily for caching data files that will
    # open in any version
    # app_dirs_unversioned.user_* directories are parents of those in app_dirs
    chimerax.app_dirs_unversioned = adu = appdirs.AppDirs(app_name,
                                                          appauthor=app_author)

    # update "site" user variables to use ChimeraX instead of Python paths
    # NB: USER_SITE is used by both pip and the toolshed, so
    # this must happen before pip is imported so that "--user" installs
    # will go in the right place.
    import site
    if not is_root:
        site.USER_BASE = adu.user_data_dir
        site.USER_SITE = os.path.join(ad.user_data_dir, "site-packages")
    else:
        from distutils import sysconfig
        site.USER_SITE = sysconfig.get_python_lib()

    # Find the location of "share" directory so that we can inform
    # the C++ layer.  Assume it's a sibling of the directory that
    # the executable is in.
    chimerax.app_bin_dir = os.path.join(rootdir, "bin")
    if sys.platform.startswith('win'):
        chimerax.app_data_dir = os.path.join(chimerax.app_bin_dir, "share")
    else:
        chimerax.app_data_dir = os.path.join(rootdir, "share")
    chimerax.app_lib_dir = os.path.join(rootdir, "lib")

    # inform the C++ layer of the appdirs paths
    from chimerax.core import _appdirs
    _appdirs.init_paths(os.sep, ad.user_data_dir, ad.user_config_dir,
                        ad.user_cache_dir, ad.site_data_dir,
                        ad.site_config_dir, ad.user_log_dir,
                        chimerax.app_data_dir, adu.user_cache_dir)

    from chimerax.core import session
    try:
        sess = session.Session(app_name,
                               debug=opts.debug,
                               silent=opts.silent,
                               minimal=opts.safe_mode)
    except ImportError as err:
        if opts.offscreen and 'OpenGL' in err.args[0]:
            if sys.platform.startswith("linux"):
                why = "failed"
            else:
                why = "not supported on this platform"
            print("Offscreen rendering is", why, file=sys.stderr)
            return os.EX_UNAVAILABLE
        raise

    from chimerax.core import core_settings
    core_settings.init(sess)

    session.common_startup(sess)

    if opts.uninstall:
        return uninstall(sess)

    # initialize qt
    if opts.gui:
        from chimerax.ui import initialize_qt
        initialize_qt()

    # initialize the user interface
    if opts.gui:
        from chimerax.ui import gui
        ui_class = gui.UI
    else:
        from chimerax.core import nogui
        ui_class = nogui.UI
        if opts.color is not None:
            nogui._color_output = opts.color
            if opts.color:

                def t():
                    return True

                sys.stdout.isatty = t
                sys.stderr.isatty = t
    # sets up logging, splash screen if gui
    # calls "sess.save_in_session(self)"
    sess.ui = ui_class(sess)
    sess.ui.stereo = opts.stereo
    sess.ui.autostart_tools = opts.load_tools

    # Set current working directory to Desktop when launched from icon.
    if ((sys.platform.startswith('darwin') and os.getcwd() == '/')
            or (sys.platform.startswith('win')
                and os.getcwd().endswith('\\Users\\Public\\Desktop'))):
        try:
            os.chdir(os.path.expanduser('~/Desktop'))
        except Exception:
            pass

    # splash screen
    if opts.gui:
        sess.ui.show_splash()
    num_splash_steps = 2
    if opts.gui:
        num_splash_steps += 1
    if not opts.gui and opts.load_tools:
        num_splash_steps += 1
    import itertools
    splash_step = itertools.count()

    # common core initialization
    if not opts.silent:
        sess.ui.splash_info("Initializing core", next(splash_step),
                            num_splash_steps)
        if sess.ui.is_gui and opts.debug:
            print("Initializing core", flush=True)

    # Install any bundles before toolshed is initialized so
    # the new ones get picked up in this session
    rebuild_cache = False
    from chimerax.core import toolshed
    inst_dir, restart_file = toolshed.restart_action_info()
    restart_action_msgs = []
    if os.path.exists(restart_file):
        # Move file out of the way so next restart of ChimeraX
        # (when we try to install the bundle) will not go into
        # an infinite loop reopening the restart file
        tmp_file = restart_file + ".tmp"
        try:
            # Remove in case old file lying around.
            # Windows does not allow renaming to an existing file.
            os.remove(tmp_file)
        except Exception:
            pass
        os.rename(restart_file, tmp_file)
        with open(tmp_file) as f:
            for line in f:
                sess.ui.splash_info("Restart action:\n%s" % line)
                restart_action(line, inst_dir, restart_action_msgs)
        os.remove(tmp_file)
        rebuild_cache = True

    if opts.toolshed is None:
        # Default to whatever the restart actions needed
        toolshed_url = _restart_toolshed_url
    elif opts.toolshed == "preview":
        toolshed_url = toolshed.preview_toolshed_url()
    else:
        toolshed_url = opts.toolshed
    toolshed.init(sess.logger,
                  debug=sess.debug,
                  rebuild_cache=rebuild_cache,
                  check_available=opts.get_available_bundles,
                  remote_url=toolshed_url,
                  ui=sess.ui)
    sess.toolshed = toolshed.get_toolshed()
    if opts.module != 'pip':
        # keep bugs in ChimeraX from preventing pip from working
        if not opts.silent:
            sess.ui.splash_info("Initializing bundles", next(splash_step),
                                num_splash_steps)
            if sess.ui.is_gui and opts.debug:
                print("Initializing bundles", flush=True)
        sess.toolshed.bootstrap_bundles(sess, opts.safe_mode)
        from chimerax.core import tools
        sess.tools = tools.Tools(sess, first=True)
        from chimerax.core import tasks
        sess.tasks = tasks.Tasks(sess, first=True)
        from chimerax.core import undo
        sess.undo = undo.Undo(sess, first=True)

    if opts.version >= 0:
        sess.silent = False
        if opts.version > 3:
            opts.version = 3
        format = [None, 'verbose', 'bundles', 'packages'][opts.version]
        from chimerax.core.commands import command_function
        version_cmd = command_function("version")
        version_cmd(sess, format)
        return os.EX_OK

    if opts.list_io_formats:
        sess.silent = False
        collate = {}
        for fmt in sess.data_formats.formats:
            collate.setdefault(fmt.category, []).append(fmt)
        categories = list(collate.keys())
        categories.sort(key=str.casefold)
        print("Supported file suffixes:")
        print("  o = open, s = save")
        openers = set(sess.open_command.open_data_formats)
        savers = set(sess.save_command.save_data_formats)
        for cat in categories:
            print("\n%s:" % cat)
            fmts = collate[cat]
            fmts.sort(key=lambda fmt: fmt.name.casefold())
            for fmt in fmts:
                o = 'o' if fmt in openers else ' '
                s = 's' if fmt in savers else ' '
                if fmt.suffixes:
                    exts = ': ' + ', '.join(fmt.suffixes)
                else:
                    exts = ''
                print("%c%c  %s%s" % (o, s, fmt.name, exts))
        # TODO: show database formats
        # TODO: show mime types?
        # TODO: show compression suffixes?
        return os.EX_OK

    if opts.gui:
        # build out the UI, populate menus, create graphics, etc.
        if not opts.silent:
            sess.ui.splash_info("Starting main interface", next(splash_step),
                                num_splash_steps)
            if sess.ui.is_gui and opts.debug:
                print("Starting main interface", flush=True)
        sess.ui.build()

    if opts.start_tools:
        if not opts.silent:
            msg = 'Starting tools %s' % ', '.join(opts.start_tools)
            sess.ui.splash_info(msg, next(splash_step), num_splash_steps)
            if sess.ui.is_gui and opts.debug:
                print(msg, flush=True)
        # canonicalize tool names
        start_tools = []
        for t in opts.start_tools:
            tools = sess.toolshed.find_bundle_for_tool(t)
            if not tools:
                sess.logger.warning("Unable to find tool %s" % repr(t))
                continue
            start_tools.append(tools[0][1])
        sess.tools.start_tools(start_tools)

    if opts.commands:
        if not opts.silent:
            msg = 'Running startup commands'
            # sess.ui.splash_info(msg, next(splash_step), num_splash_steps)
            if sess.ui.is_gui and opts.debug:
                print(msg, flush=True)
        from chimerax.core.commands import run
        for cmd in opts.commands:
            run(sess, cmd)

    if opts.scripts:
        if not opts.silent:
            msg = 'Running startup scripts'
            # sess.ui.splash_info(msg, next(splash_step), num_splash_steps)
            if sess.ui.is_gui and opts.debug:
                print(msg, flush=True)
        from chimerax.core.commands import run
        for script in opts.scripts:
            run(sess, 'runscript %s' % script)

    if not opts.silent:
        sess.ui.splash_info("Finished initialization", next(splash_step),
                            num_splash_steps)
        if sess.ui.is_gui and opts.debug:
            print("Finished initialization", flush=True)

    if opts.gui:
        sess.ui.close_splash()

    if not opts.silent:
        from chimerax.core.logger import log_version
        log_version(sess.logger)  # report version in log

    if opts.gui or hasattr(core, 'offscreen_rendering'):
        sess.update_loop.start_redraw_timer()
        sess.logger.info(
            '<a href="cxcmd:help help:credits.html">How to cite UCSF ChimeraX</a>',
            is_html=True)

    # Show any messages from installing bundles on restart
    if restart_action_msgs:
        for where, msg in restart_action_msgs:
            if where == "stdout":
                sess.logger.info(msg)
            else:
                sess.logger.warning(msg)

    if opts.module:
        import runpy
        import warnings
        sys.argv[:] = args  # runpy will insert appropriate argv[0]
        exit = SystemExit(os.EX_OK)
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore", category=BytesWarning)
            global_dict = {'session': sess}
            try:
                runpy.run_module(opts.module,
                                 init_globals=global_dict,
                                 run_name='__main__',
                                 alter_sys=True)
            except SystemExit as e:
                exit = e
        if opts.module == 'pip' and exit.code == os.EX_OK:
            has_install = 'install' in sys.argv
            has_uninstall = 'uninstall' in sys.argv
            if has_install or has_uninstall:
                per_user = '******' in sys.argv
                sess.toolshed.reload(sess.logger, rebuild_cache=True)
                sess.toolshed.set_install_timestamp(per_user)
            # Do not remove scripts anymore since we may be installing
            # using ChimeraX which would put the right paths in
            # generated files.
            # if has_install:
            #     remove_python_scripts(chimerax.app_bin_dir)
        return exit.code

    from chimerax.core import startup
    startup.run_user_startup_scripts(sess)

    if opts.cmd:
        # Emulated Python's -c option.
        # This is needed for -m pip to work in some cases.
        sys.argv = args
        sys.argv[0] = '-c'
        global_dict = {
            'session': sess,
            '__name__': '__main__',
        }
        exec(opts.cmd, global_dict)
        return os.EX_OK

    # the rest of the arguments are data files
    from chimerax.core import errors, commands
    for arg in args:
        if opts.safe_mode:
            # 'open' command unavailable; only open Python files
            if not arg.endswith('.py'):
                sess.logger.error(
                    "Can only open Python scripts in safe mode, not '%s'" %
                    arg)
                return os.EX_SOFTWARE
            from chimerax.core.scripting import open_python_script
            try:
                open_python_script(sess, open(arg, 'rb'), arg)
            except (IOError, errors.NotABug) as e:
                sess.logger.error(str(e))
                return os.EX_SOFTWARE
            except Exception:
                import traceback
                traceback.print_exc()
                return os.EX_SOFTWARE
        else:
            from chimerax.core.commands import StringArg
            try:
                commands.run(sess, 'open %s' % StringArg.unparse(arg))
            except (IOError, errors.NotABug) as e:
                sess.logger.error(str(e))
                return os.EX_SOFTWARE
            except Exception:
                import traceback
                traceback.print_exc()
                return os.EX_SOFTWARE

    # Open files dropped on application
    if opts.gui:
        sess.ui.open_pending_files(ignore_files=args)

    # Allow the event_loop to be disabled, so we can be embedded in
    # another application
    if event_loop and opts.event_loop:
        try:
            sess.ui.event_loop()
        except SystemExit as e:
            return e.code
        except Exception:
            import traceback
            traceback.print_exc()
            return os.EX_SOFTWARE
    return os.EX_OK