コード例 #1
0
    def run(self):

        # Defer heavyweight imports.
        from betse.util.os.brand import posix
        from betse.util.os.shell import shelldir
        from betse.util.path import files, pathnames

        # If the current operating system is POSIX-incompatible, such system
        # does *NOT* support conventional symbolic links. Return immediately.
        if not posix.is_posix():
            return

        # Absolute path of betse's top-level Python package in the current
        # directory.
        package_dirname = pathnames.join(shelldir.get_cwd_dirname(),
                                         self._setup_options['name'])

        # Absolute path of such symbolic link.
        symlink_filename = pathnames.join(self.install_dir,
                                          self._setup_options['name'])

        #FIXME: Define a new files.make_symlink() function resembling the
        #existing buputils.make_symlink() function.
        # (Re)create such link.
        files.make_symlink(package_dirname, symlink_filename)
コード例 #2
0
def join_or_die(*pathnames: str) -> str:
    '''
    Pathname of the file produced by joining (i.e., concatenating) the passed
    pathnames with the directory separator specific to the current platform if
    this file exists *or* raise an exception otherwise (i.e., if this file does
    *not* exist).

    See Also
    -----------
    :func:`betse.util.path.pathnames.join`
    :func:`die_unless_file`
        Further details.
    '''

    # Avoid circular import dependencies.
    from betse.util.path.pathnames import join

    # Filename producing by joining these pathnames.
    filename = join(*pathnames)

    # If this file does *NOT* exist, raise an exception.
    die_unless_file(filename)

    # Return this dirname.
    return filename
コード例 #3
0
    def __init__(self, filename: str, dirname: str) -> None:
        '''
        Initialize this tissue picker.

        Parameters
        ----------
        filename : str
            Absolute or relative filename of this image mask. If relative (i.e.,
            *not* prefixed by a directory separator), this filename will be
            canonicalized into an absolute filename relative to the passed
            absolute dirname.
        dirname : str
            Absolute dirname of the directory containing this image mask. If the
            ``filename`` parameter is relative, that filename will be prefixed
            by this path to convert that filename into an absolute path; else,
            this dirname is ignored.
        '''

        # If this is a relative path, convert this into an absolute path
        # relative to the directory containing the source configuration file.
        if pathnames.is_relative(filename):
            filename = pathnames.join(dirname, filename)

        # If this absolute path is *NOT* an existing file, raise an exception.
        files.die_unless_file(filename)

        # Classify this parameter.
        self.filename = filename
コード例 #4
0
    def get_repl_history_filename(self, repl_module_name: str) -> dict:
        '''
        Absolute pathname of this application's default user-specific history
        file for the read–eval–print loop (REPL) implemented by the third-party
        module with the passed name.

        This history file is a plaintext file to which this REPL appends each
        read user command for preserving command history between REPL sessions.

        Parameters
        ----------
        repl_module_name : str
            Fully-qualified name of the third-party module implementing this
            REPL.

        Returns
        ----------
        str
            Absolute path of the user-specific history file for this REPL.

        Raises
        ----------
        BetseModuleException
            If this REPL module name is unrecognized.
        '''

        # Avoid circular import dependencies.
        from betse.exceptions import BetseModuleException
        from betse.util.path import pathnames

        # Dictionary mapping each REPL module name to its history filename.
        REPL_MODULE_NAME_TO_HISTORY_FILENAME = {
            'ptpython': pathnames.join(self.dot_dirname, 'ptpython.hist'),
            'readline': pathnames.join(self.dot_dirname, 'readline.hist'),
        }

        # If this REPL module name is unrecognized, fail.
        if repl_module_name not in REPL_MODULE_NAME_TO_HISTORY_FILENAME:
            raise BetseModuleException(
                'REPL module "{}" unrecognized.'.format(repl_module_name))

        # Else, return the history filename for this REPL.
        return REPL_MODULE_NAME_TO_HISTORY_FILENAME[repl_module_name]
コード例 #5
0
    def __init__(self, p):

        #FIXME: Extract the "MetabolicNetwork.betse" basename into a new
        #configuration option, perhaps in the "init file saving" section.
        #FIXME: Replace "MetabolicNetwork.betse" with
        #"MetabolicNetwork.betse.gz" to compress this pickled file.

        # Define data paths for saving an initialization and simulation run:
        self.savedMoM = pathnames.join(p.init_pickle_dirname,
                                       'MetabolicNetwork.betse')
コード例 #6
0
    def profile_default_filename(self) -> str:
        '''
        Absolute filename of this application's **default profile dumpfile**
        (i.e., user-specific binary file to which profiled statistics are saved
        by default).
        '''

        # Avoid circular import dependencies.
        from betse.util.path import pathnames

        # Return the absolute path of this file.
        return pathnames.join(self.dot_dirname, self.package_name + '.prof')
コード例 #7
0
    def log_default_filename(self) -> str:
        '''
        Absolute filename of this application's **default logfile** (i.e.,
        plaintext user-specific file to which all messages, warnings, errors,
        and exceptions are logged by default).
        '''

        # Avoid circular import dependencies.
        from betse.util.path import pathnames

        # Return the absolute path of this file.
        return pathnames.join(self.dot_dirname, self.package_name + '.log')
コード例 #8
0
    def _set_environment_variables(
        self,
        script_basename: str,
        script_type: str,
        entry_point: str,
    ) -> None:
        '''
        Set all environment variables used to communicate with the
        application-specific PyInstaller specification file run in a separate
        process, given the passed arguments yielded by the
        :meth:`command_entry_points` generator.

        While hardly ideal, PyInstaller appears to provide no other means of
        communicating with that file.
        '''

        # Defer heavyweight imports.
        from betse.util.app.meta import appmetaone
        from betse.util.os.shell import shellenv
        from betse.util.path import files, pathnames

        # Absolute path of the entry module.
        #
        # This module's relative path to the top-level project directory is
        # obtained by converting the entry point specifier defined by
        # "setup.py" for the current entry point (e.g.,
        # "betse.gui.guicli:main") into a platform-specific path. Sadly,
        # setuptools provides no cross-platform API for reliably obtaining the
        # absolute path of the corresponding script wrapper. Even if it did,
        # such path would be of little use under POSIX-incompatible platforms
        # (e.g., Windows), where these wrappers are binary blobs rather than
        # valid Python scripts.
        #
        # Instead, we reverse-engineer the desired path via brute-force path
        # manipulation. Thus burns out another tawdry piece of my soul.
        module_filename = pathnames.join(
            appmetaone.get_app_meta().project_dirname,
            entry_point.module_name.replace('.', os.path.sep) + '.py')

        # Ensure such module exists.
        files.die_unless_file(module_filename)

        # Such path.
        shellenv.set_var('__FREEZE_MODULE_FILENAME', module_filename)

        # Whether to freeze in "one-file" or "one-directory" mode.
        shellenv.set_var('__FREEZE_MODE', self._get_freeze_mode)

        # Whether to freeze a CLI- or GUI-based application.
        shellenv.set_var('__FREEZE_INTERFACE_TYPE', script_type)
コード例 #9
0
    def _get_csv_filename(
        self,

        # Mandatory parameters.
        phase: SimPhase,
        basename_sans_filetype: str,

        # Optional parameters.
        dirname: StrOrNoneTypes = None,
    ) -> str:
        '''
        Absolute filename of a CSV file to be exported with the passed basename
        excluding suffixing ``.``-prefixed filetype (which this method appends
        to this basename as configured by the current simulation configuration)
        residing in the directory with the passed dirname.

        Parameters
        ----------
        phase : SimPhase
            Current simulation phase.
        basename_sans_filetype : str
            Basename (excluding suffixing ``.``-prefixed filetype) of this
            file.
        dirname : StrOrNoneTypes
            Absolute pathname of the directory containing this file. If this
            directory does *not* already exist, this method creates this
            directory. Defaults to ``None``, in which case this pathname
            defaults to the top-level directory containing all CSV files
            exported for the current simulation phase.

        Returns
        ----------
        str
            Absolute filename of a CSV file to be exported with this basename.
        '''

        # If unpassed, default this directory to the top-level directory
        # containing all CSV files exported for this simulation phase.
        if dirname is None:
            dirname = phase.export_dirname

        # Create this directory if needed.
        dirs.make_unless_dir(dirname)

        # Basename of this file.
        basename = '{}.{}'.format(basename_sans_filetype, phase.p.csv.filetype)

        # Create and return the absolute filename of this file.
        return pathnames.join(dirname, basename)
コード例 #10
0
    def run(self):
        '''Run the current command and all subcommands thereof.'''

        # Defer heavyweight imports.
        from betse.util.os.brand import posix
        from betse.util.path import files, pathnames

        # If the current operating system is POSIX-compatible, such system
        # supports symbolic links. In such case, remove the previously
        # installed symbolic link.
        if posix.is_posix():
            #FIXME: Define a new files.remove_symlink() function resembling the
            #existing buputils.remove_symlink() function.
            files.remove_symlink(
                pathnames.join(
                    self.install_package_dirname,
                    self._setup_options['name'],
                ))

        # Remove all installed scripts.
        for script_basename, _, _ in (
                supcommand.iter_command_entry_points(self)):
            files.remove_file(
                pathnames.join(self.install_wrapper_dirname, script_basename))
コード例 #11
0
    def finalize_options(self):
        '''
        Default undefined command-specific options to the options passed to the
        current parent command if any (e.g., ``install``).
        '''

        # Defer heavyweight imports.
        from betse.util.os.brand import macos
        from betse.util.path import dirs, pathnames
        from betse.util.os.command import cmdrun, cmds

        # Finalize superclass options.
        super().finalize_options()

        #FIXME: Replicate this functionality for the "install" command as well.

        # If the current system is OS X *AND* the OS X-specific Homebrew package
        # manager is installed...
        if macos.is_macos() and cmds.is_cmd('brew'):
            # Absolute dirname of Homebrew's top-level system-wide cellar
            # directory (e.g., "/usr/local/Cellar").
            brew_cellar_dir = cmdrun.get_output_interleaved_or_die(
                command_words=('brew', '--cellar'))
            #print('Here!')

            # Absolute dirname of Homebrew's top-level system-wide directory
            # (e.g., "/usr/local").
            brew_dir = cmdrun.get_output_interleaved_or_die(
                command_words=('brew', '--prefix'))

            # Absolute dirname of Homebrew's top-level system-wide binary
            # directory (e.g., "/usr/local/bin").
            brew_binary_dir = pathnames.join(brew_dir, 'bin')

            # If this directory does not exist, raise an exception.
            dirs.die_unless_dir(brew_binary_dir)

            # If the directory to which wrappers will be installed is a
            # Python-specific subdirectory of this cellar directory (e.g.,
            # "/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/bin"),
            # that subdirectory is unlikely to reside in the current ${PATH},
            # in which case wrappers installed to that subdirectory will remain
            # inaccessible. Correct this by forcing wrappers to be installed
            # to the Homebrew's conventional binary directory instead.
            if self.install_scripts.startswith(brew_cellar_dir):
                self.install_scripts = brew_binary_dir
                print('Detected Homebrew installation directory "{}".'.format(
                    brew_binary_dir))
コード例 #12
0
def copy_dir_into_dir(src_dirname: str, trg_dirname: str, *args,
                      **kwargs) -> None:
    '''
    Recursively copy the source directory with the passed dirname to a
    subdirectory of the target directory with the passed dirname whose basename
    is that of this source directory.

    Parameters
    -----------
    src_dirname : str
        Absolute or relative dirname of the source directory to be copied from.
    trg_dirname : str
        Absolute or relative dirname of the target directory to be copied into.

    All remaining parameters are passed as is to the :func:`copy` function.

    See Also
    ----------
    :func:`copy_dir`
        Further details.

    Examples
    ----------
        >>> from betse.util.path import dirs
        >>> dirs.copy_into_dir('/usr/src/linux/', '/tmp/')
        >>> dirs.is_dir('/tmp/linux/')
        True
    '''

    # Avoid circular import dependencies.
    from betse.util.path import pathnames

    # Basename of this source directory.
    src_basename = pathnames.get_basename(src_dirname)

    # Absolute or relative dirname of the target directory to copy to.
    trg_subdirname = pathnames.join(trg_dirname, src_basename)

    # Recursively copy this source to target directory.
    copy_dir(*args,
             src_dirname=src_dirname,
             trg_dirname=trg_subdirname,
             **kwargs)
コード例 #13
0
def is_conda() -> bool:
    '''
    ``True`` only if the active Python interpreter is managed by ``conda``, the
    open-source, cross-platform, language-agnostic package manager provided by
    the Anaconda and Miniconda distributions.

    Specifically, this function returns ``True`` only if the
    ``{sys.prefix}/conda-meta/history`` file exists such that:

    * ``{sys.prefix}`` is the site-specific Python directory prefix. Under
      Linux, this is typically:

      * ``/usr`` for system-wide Python interpreters.
      * A user-specific directory for ``conda``-based Python interpreters
        (e.g., ``${HOME}/Miniconda3/envs/${CONDA_ENV_NAME}``).

    * ``conda-meta/history`` is a file relative to this prefix guaranteed to
      exist for *all* ``conda`` environments (including the base environment).
      Since this file is unlikely to be created in non-``conda`` environments,
      the existence of this file effectively guarantees this interpreter to be
      managed by ``conda``.

    See Also
    ----------
    https://stackoverflow.com/a/47730405/2809027
        StackOverflow answer strongly inspiring this implementation.
    https://github.com/conda/constructor/blob/2.0.1/constructor/install.py#L218-L234
        Function in the ``conda`` codebase responsible for creating the
        ``{sys.prefix}/conda-meta/history`` file.
    '''

    # Avoid circular import dependencies.
    from betse.util.path import files, pathnames

    # Absolute filename of a file relative to the site-specific Python
    # directory prefix guaranteed to exist for all "conda" environments.
    conda_history_filename = pathnames.join(sys.prefix, 'conda-meta',
                                            'history')

    # Return true only if this file exists.
    return files.is_file(conda_history_filename)
コード例 #14
0
def join_and_make_unless_dir(*partnames: str) -> str:
    '''
    Join (i.e., concatenate) the passed pathnames with the directory separator
    specific to the current platform, create the directory with the resulting
    absolute or relative path, and return this path.

    This convenience function chains the lower-level
    :func:`betse.util.path.pathnames.join` and :func:`make_unless_dir`
    functions.
    '''

    # Avoid circular import dependencies.
    from betse.util.path import pathnames

    # Dirname joined from these pathnames.
    dirname = pathnames.join(*partnames)

    # Create this directory if needed.
    make_unless_dir(dirname)

    # Return this dirname.
    return dirname
コード例 #15
0
    def _do_try(self) -> object:
        '''
        Run the ``try`` subcommand and return the result of doing so.
        '''

        # Basename of the sample configuration file to be created.
        config_basename = 'sample_sim.yaml'

        # Relative filename of this file, relative to the current directory.
        self._args.conf_filename = pathnames.join(
            'sample_sim', config_basename)

        #FIXME: Insufficient. We only want to reuse this file if this file's
        #version is identical to that of the default YAML configuration file's
        #version. Hence, this logic should (arguably) be shifted elsewhere --
        #probably into "betse.science.sim_config".

        # If this file already exists, reuse this file.
        if files.is_file(self._args.conf_filename):
            logs.log_info(
                'Reusing simulation configuration "%s".', config_basename)
        # Else, create this file.
        else:
            self._do_config()

        # Run all general-purposes phases, thus excluding network-isolated
        # phases (e.g., "_do_sim_grn"), in the expected order.
        self._do_seed()
        self._do_init()
        self._do_sim()
        self._do_plot_seed()
        self._do_plot_init()

        # Return the value returned by the last such phase, permitting this
        # subcommand to be memory profiled. While any value would technically
        # suffice, the value returned by the last such phase corresponds to a
        # complete simulation run and hence is likely to consume maximal memory.
        return self._do_plot_sim()
コード例 #16
0
def join_or_die(*pathnames: str) -> str:
    '''
    Pathname of the directory produced by joining (i.e., concatenating) the
    passed pathnames with the directory separator specific to the current
    platform if this directory exists *or* raise an exception otherwise (i.e.,
    if this directory does *not* exist).

    This higher-level function is a convenience wrapper encapsulating both the
    lower-level :func:`betse.util.path.pathnames.join` and
    :func:`die_unless_dir` functions.
    '''

    # Avoid circular import dependencies.
    from betse.util.path.pathnames import join

    # Dirname producing by joining these pathnames.
    dirname = join(*pathnames)

    # If this directory does *NOT* exist, raise an exception.
    die_unless_dir(dirname)

    # Return this dirname.
    return dirname
コード例 #17
0
def is_worktree(dirname: str) -> bool:
    '''
    ``True`` only if the directory with the passed pathname is a **Git working
    tree** (i.e., contains a ``.git`` subdirectory).

    Parameters
    ----------
    dirname : str
        Relative or absolute pathname of the directory to be tested.

    Returns
    ----------
    bool
        ``True`` only if this directory is a Git working tree.
    '''

    # Avoid circular import dependencies.
    from betse.util.path import dirs, pathnames

    # Absolute or relative pathname of this directory's ".git" subdirectory.
    git_subdirname = pathnames.join(dirname, '.git')

    # Return True only if this subdirectory exists.
    return dirs.is_dir(git_subdirname)
コード例 #18
0
    def _init_saving(self, save_dir_parent_basename: str) -> None:
        '''
        Initialize this animation for platform-compatible file saving if
        enabled by the current simulation configuration _or_ noop otherwise.

        Parameters
        ----------
        save_dir_parent_basename : str
            Basename of the parent directory of the subdirectory to which this
            animation's frames will be saved when requested by the current
            simulation configuration. Defaults to a suitably generic basename.
        '''

        # Animation configuration localized for convenience.
        anim_config = self._phase.p.anim

        # If it is *NOT* the case that...
        if not (
            # This animation is being saved...
            self._is_save and (
            # ...as either images or video.
            anim_config.is_images_save or anim_config.is_video_save)
        # Then this animation is unsaved. In this case, silently noop.
        ):
            return

        #FIXME: This is silly. Rather than prohibiting animation names
        #containing directory separators, simply sanitize this animation's name
        #by globally replacing all such separators with non-separator characters
        #guaranteed to be permitted in pathnames for all platforms (e.g.,
        #replacing "/" with "_" or "-" characters on POSIX systems).

        # If the human-readable name of this animation contains directory
        # separators and hence is *NOT* a valid basename, raise an exception.
        pathnames.die_unless_basename(self._kind)

        # Path of the subdirectory to which these files will be saved,
        # creating this subdirectory and all parents thereof if needed.
        save_dirname = dirs.canonicalize_and_make_unless_dir(pathnames.join(
            self._phase.export_dirname, save_dir_parent_basename, self._kind))

        # If saving animation frames as images, prepare to do so.
        if anim_config.is_images_save:
            #FIXME: This currently defaults to padding frames with six or seven
            #zeroes, on average. Let's make this a bit more aesthetic by
            #padding frames to only as many zeroes are absolutely required by
            #the current frame count. To do that, in turn, we'll probably need
            #to shift everything that follows in this method to the _animate()
            #method, where the actual frame count is finally passed.

            # Template expanding to the basename of each image to be saved.
            # The "ImageMovieWriter" class subsequently expands the "{{"- and
            # "}}"-delimited substring to the 0-based index of the current
            # frame number.
            save_frame_template_basename = '{}_{{:07d}}.{}'.format(
                self._kind, anim_config.image_filetype)

            # Template expanding to the absolute path of each image to be
            # saved.
            writer_images_template = pathnames.join(
                save_dirname, save_frame_template_basename)

            # Object writing animation frames as images.
            self._writer_images = ImageMovieWriter()

            # Log this preparation.
            logs.log_debug(
                'Preparing to save animation frames "%s"...',
                writer_images_template)

            # Prepare to save these animation frames.
            self._writer_images.setup(
                fig=self._figure,
                outfile=writer_images_template,
                dpi=anim_config.image_dpi,
            )

        # If saving animation frames as video, prepare to do so.
        if anim_config.is_video_save:
            # Name of the first video encoder installed on the current system.
            video_writer_name = mplvideo.get_first_writer_name(
                anim_config.video_writer_names)
            # print('found video writer: {}'.format(VideoWriterClass))

            # Matplotlib animation writer class encapsulating this encoder.
            VideoWriterClass = mplvideo.get_writer_class(video_writer_name)

            # Name of the first video codec supported by both this video
            # encoder and the video container format corresponding to this
            # video's filetype.
            video_codec_name = mplvideo.get_first_codec_name(
                writer_name=video_writer_name,
                container_filetype=anim_config.video_filetype,
                codec_names=anim_config.video_codec_names,
            )

            # Basename of the video to be written.
            save_video_basename = '{}.{}'.format(
                self._kind, anim_config.video_filetype)

            # Absolute path of the video to be written.
            writer_video_filename = pathnames.join(
                save_dirname, save_video_basename)

            # Object writing animation frames as video. Matplotlib animation
            # writer classes must *NOT* be manually instantiated, as doing so
            # fails to apply the monkey-patching applied by this function.
            self._writer_video = mplvideo.make_writer(
                cls=VideoWriterClass,
                bitrate=anim_config.video_bitrate,
                codec=video_codec_name,
                fps=anim_config.video_framerate,
                metadata=anim_config.video_metadata,
            )

            # Log this preparation.
            logs.log_debug(
                'Preparing to save animation video "%s"...',
                writer_video_filename)

            # Prepare to save this animation video. Matplotlib squelches
            # critical (technically non-fatal but effectively fatal)
            # warnings and errors emitted by the external command invoked
            # by this call to the MovieWriter.setup() method *UNLESS* the
            # current matplotlib-specific verbosity level is "debug".
            # Temporarily ensure this for the duration of this call.
            with mpl_config.reducing_log_level_to_debug_if_info():
                self._writer_video.setup(
                    fig=self._figure,
                    outfile=writer_video_filename,
                    dpi=anim_config.video_dpi,
                )
コード例 #19
0
    def _run_pyinstaller_command(
        self,
        script_basename: str,
        script_type: str,
        entry_point,
    ) -> None:
        '''
        Run the currently configured PyInstaller command for the passed entry
        point's script wrapper.

        Attributes
        ----------
        script_basename : str
            Basename of the executable wrapper script running this entry point.
        script_type : str
            Type of the executable wrapper script running this entry point,
            guaranteed to be either:

            * If this script is console-specific, ``console`` .
            * Else, ``gui``.
        entry_point : EntryPoint
            Entry point, whose attributes specify the module to be imported and
            function to be run by this script.
        '''

        # Defer heavyweight imports.
        from betse.util.io import stderrs
        from betse.util.os.shell import shellstr
        from betse.util.path import files, pathnames

        # If this spec exists, instruct PyInstaller to reuse rather than
        # recreate this file, thus preserving edits to this file.
        if files.is_file(self._pyinstaller_spec_filename):
            print('Reusing spec file "{}".'.format(
                self._pyinstaller_spec_filename))

            # Append the relative path of this spec file.
            self._pyinstaller_args.append(
                shellstr.shell_quote(self._pyinstaller_spec_filename))

            # Freeze this script with this spec file.
            self._run_pyinstaller_imported()
        # Else, instruct PyInstaller to (re)create this spec file.
        else:
            # Absolute path of the directory containing this files.
            pyinstaller_spec_dirname = pathnames.get_dirname(
                self._pyinstaller_spec_filename)

            # Absolute path of the current script wrapper.
            script_filename = pathnames.join(self.install_dir, script_basename)
            files.die_unless_file(
                script_filename,
                'File "{}" not found. {}'.format(script_filename,
                                                 freeze.EXCEPTION_ADVICE))

            # Inform the user of this action *AFTER* the above validation.
            # Since specification files should typically be reused rather
            # than regenerated, do so as a non-fatal warning.
            stderrs.output_warning('Generating spec file "{}".'.format(
                self._pyinstaller_spec_filename))

            # Append all options specific to spec file generation.
            self._pyinstaller_args.extend([
                # If this is a console script, configure standard input and
                # output for console handling; else, do *NOT* and, if the
                # current operating system is OS X, generate an ".app"-suffixed
                # application bundle rather than a customary executable.
                '--console' if script_type == 'console' else '--windowed',

                # Non-default PyInstaller directories.
                '--additional-hooks-dir=' +
                shellstr.shell_quote(self._pyinstaller_hooks_dirname),
                '--specpath=' + shellstr.shell_quote(pyinstaller_spec_dirname),
            ])

            # Append all subclass-specific options.
            self._pyinstaller_args.extend(self._get_pyinstaller_options())

            # Append the absolute path of this script.
            self._pyinstaller_args.append(
                shellstr.shell_quote(script_filename))

            # Freeze this script and generate a spec file.
            self._run_pyinstaller_imported()

            # Absolute path of this file.
            script_spec_filename = pathnames.join(pyinstaller_spec_dirname,
                                                  script_basename + '.spec')

            # Rename this file to have the basename expected by the prior
            # conditional on the next invocation of this setuptools command.
            #
            # Note that "pyinstaller" accepts an option "--name" permitting
            # the basename of this file to be specified prior to generating
            # this file. Unfortunately, this option *ALSO* specifies the
            # basename of the generated executable. While the former is
            # reliably renamable, the former is *NOT* (e.g., due to code
            # signing). Hence, this file is manually renamed without passing
            # this option.
            files.move_file(script_spec_filename,
                            self._pyinstaller_spec_filename)
コード例 #20
0
    def plot_seed(self) -> SimPhase:
        '''
        Visualize the cell cluster seed by a prior call to the :meth:`seed`
        method and export the resulting plots and animations to various output
        files, specified by the current configuration file.

        Returns
        ----------
        SimPhase
            High-level simulation phase instance encapsulating all objects
            internally created by this method to run this phase.
        '''

        # Log this plotting attempt.
        logs.log_info(
            'Plotting cell cluster with configuration file "%s".',
            self._p.conf_basename)

        # If an initialization does *NOT* already exist, raise an exception.
        _die_unless_file_pickled(
            filename=self._p.seed_pickle_filename,
            subcommand='seed',
            subcommand_label='Seed')

        # Load the seed from cache.
        cells, _ = fh.loadWorld(self._p.seed_pickle_filename)
        logs.log_info('Cell cluster loaded.')

        # Simulation phase, created *AFTER* unpickling these objects above
        phase = SimPhase(
            kind=SimPhaseKind.SEED,
            p=self._p,
            cells=cells,
            callbacks=self._callbacks,
        )

        # Initialize core simulation data structures.
        phase.sim.init_core(phase)
        phase.dyna.init_profiles(phase)

        #FIXME: Refactor into a seed-specific plot pipeline. Dreaming androids!
        if self._p.autosave:
            savedImg = pathnames.join(self._p.init_export_dirname, 'fig_')

        # if self._p.plot_cell_cluster:
        # fig_tiss, ax_tiss, cb_tiss = viz.clusterPlot(
        #     self._p, phase.dyna, cells, clrmap=self._p.background_cm)

        if self._p.autosave:
            savename10 = savedImg + 'cluster_mosaic' + '.png'
            plt.savefig(savename10, format='png', transparent=True)

        if self._p.plot.is_after_sim_show:
            plt.show(block=False)

        if self._p.is_ecm:  # and self._p.plot_cluster_mask:
            plt.figure()
            ax99 = plt.subplot(111)
            plt.imshow(
                np.log10(phase.sim.D_env_weight.reshape(phase.cells.X.shape)),
                origin='lower',
                extent=[
                    self._p.um * phase.cells.xmin,
                    self._p.um * phase.cells.xmax,
                    self._p.um * phase.cells.ymin,
                    self._p.um * phase.cells.ymax,
                ],
                cmap=self._p.background_cm,
            )
            plt.colorbar()

            cell_edges_flat = self._p.um * phase.cells.mem_edges_flat
            coll = LineCollection(cell_edges_flat, colors='k')
            coll.set_alpha(1.0)
            ax99.add_collection(coll)

            plt.title('Logarithm of Environmental Diffusion Weight Matrix')

            if self._p.autosave:
                savename10 = savedImg + 'env_diffusion_weights' + '.png'
                plt.savefig(savename10, format='png', transparent=True)

            if self._p.plot.is_after_sim_show:
                plt.show(block=False)

            plt.figure()
            plt.imshow(
                cells.maskM,
                origin='lower',
                extent=[
                    self._p.um * phase.cells.xmin,
                    self._p.um * phase.cells.xmax,
                    self._p.um * phase.cells.ymin,
                    self._p.um * phase.cells.ymax,
                ],
                cmap=self._p.background_cm,
            )
            plt.colorbar()
            plt.title('Cluster Masking Matrix')

            if self._p.autosave:
                savename = savedImg + 'cluster_mask' + '.png'
                plt.savefig(savename, format='png', transparent=True)

            if self._p.plot.is_after_sim_show:
                plt.show(block=False)

        # Plot gap junctions.
        # if self._p.plot_cell_connectivity:
        plt.figure()
        ax_x = plt.subplot(111)

        if self._p.showCells:
            base_points = np.multiply(phase.cells.cell_verts, self._p.um)
            col_cells = PolyCollection(
                base_points, facecolors='k', edgecolors='none')
            col_cells.set_alpha(0.3)
            ax_x.add_collection(col_cells)

        con_segs = phase.cells.nn_edges
        connects = self._p.um * np.asarray(con_segs)
        collection = LineCollection(connects, linewidths=1.0, color='b')
        ax_x.add_collection(collection)
        plt.axis('equal')
        plt.axis([
            self._p.um * phase.cells.xmin,
            self._p.um * phase.cells.xmax,
            self._p.um * phase.cells.ymin,
            self._p.um * phase.cells.ymax,
        ])

        ax_x.set_xlabel('Spatial x [um]')
        ax_x.set_ylabel('Spatial y [um')
        ax_x.set_title('Cell Connectivity Network')

        if self._p.autosave:
            savename10 = savedImg + 'gj_connectivity_network' + '.png'
            plt.savefig(savename10, format='png', transparent=True)

        if self._p.turn_all_plots_off is False:
            plt.show(block=False)
        else:
            logs.log_info(
                'Plots exported to init results folder '
                'defined in configuration file "%s".',
                self._p.conf_basename)

        # Return this phase.
        return phase
コード例 #21
0
    def _run_pyinstaller_commands(self):
        '''
        Run the PyInstaller command previously constructed by the
        :meth:`_init_pyinstaller_command` method for each entry point.
        '''

        # Defer heavyweight imports.
        from betse.util.path import dirs, paths, pathnames

        # True if the current distribution has at least one entry point.
        is_entry_point = False

        # Freeze each previously installed script wrapper.
        for script_basename, script_type, entry_point in\
            supcommand.iter_command_entry_points(self):
            # Note at least one entry point to be installed.
            is_entry_point = True

            #FIXME: We went to a great deal of trouble to implement this method.
            #Why don't we call it anymore, again?

            # Validate this wrapper's entry point.
            # freeze._check_entry_point(entry_point)

            # Relative path of the output frozen executable file or directory,
            # created by stripping the suffixing ".exe" filetype on Windows.
            frozen_pathname = pathnames.join(
                self.dist_dir,
                pathnames.get_pathname_sans_filetype(script_basename))

            # If cleaning *AND* this path exists, remove this path before
            # validating this path. Why? This path could be an existing file
            # and the current command freezing to a directory (or vice versa),
            # in which case subsequent validation would raise an exception.
            if self.clean and paths.is_path(frozen_pathname):

                #FIXME: Define a new paths.remove_path() function resembling
                #the existing buputils.remove_path() function.
                paths.remove_path(frozen_pathname)

            # Validate this path.
            self._check_frozen_path(frozen_pathname)

            # Set all environment variables used to communicate with the BETSE-
            # specific PyInstaller specification file run below.
            self._set_environment_variables(script_basename, script_type,
                                            entry_point)

            # Run this PyInstaller command for this entry point.
            self._run_pyinstaller_command(script_basename, script_type,
                                          entry_point)

            # Report these results to the user.
            frozen_pathtype = ('directory'
                               if dirs.is_dir(frozen_pathname) else 'file')
            print('Froze {} "{}".\n'.format(frozen_pathtype, frozen_pathname))

            #FIXME: Excise when beginning GUI work.
            break

        # If no entry points are registered for the current distribution, raise
        # an exception.
        if not is_entry_point:
            raise DistutilsExecError('No entry points found. {}'.format(
                freeze.EXCEPTION_ADVICE))
コード例 #22
0
    def _init_pyinstaller_command(self) -> None:
        '''
        Initialize the list of all shell words of the PyInstaller command to be
        run.
        '''

        # Defer heavyweight imports.
        from betse.util.io import stderrs
        from betse.util.path import dirs, pathnames
        from betse.util.os.command import cmds
        from betse.util.os.shell import shellstr

        # Relative path of the top-level PyInstaller directory.
        pyinstaller_dirname = 'freeze'

        # Relative path of the PyInstaller spec file converting such
        # platform-independent script into a platform-specific executable.
        self._pyinstaller_spec_filename = pathnames.join(
            pyinstaller_dirname, '.spec')

        # If the frozen executable directory was *NOT* explicitly passed on the
        # command-line, default to a subdirectory of this top-level directory.
        if self.dist_dir is None:
            self.dist_dir = pathnames.join(pyinstaller_dirname, 'dist')
        # Else, canonicalize the passed directory.
        else:
            self.dist_dir = pathnames.canonicalize(self.dist_dir)
        assert isinstance(self.dist_dir,
                          str), ('"{}" not a string.'.format(self.dist_dir))

        # Relative path of the input hooks subdirectory.
        self._pyinstaller_hooks_dirname = pathnames.join(
            pyinstaller_dirname, 'hooks')

        # Relative path of the intermediate build subdirectory.
        pyinstaller_work_dirname = pathnames.join(pyinstaller_dirname, 'build')

        # Create such hooks subdirectory if not found, as failing to do so
        # will induce fatal PyInstaller errors.
        dirs.make_unless_dir(self._pyinstaller_hooks_dirname)

        # List of all shell words of the PyInstaller command to be run,
        # starting with the basename of this command.
        self._pyinstaller_args = []

        # Append all PyInstaller command options common to running such command
        # for both reuse and regeneration of spec files. (Most such options are
        # specific to the latter only and hence omitted.)
        self._pyinstaller_args = [
            # Overwrite existing output paths under the "dist/" subdirectory
            # without confirmation, the default behaviour.
            '--noconfirm',

            # Non-default PyInstaller directories.
            '--workpath=' + shellstr.shell_quote(pyinstaller_work_dirname),
            '--distpath=' + shellstr.shell_quote(self.dist_dir),

            # Non-default log level.
            # '--log-level=DEBUG',
            '--log-level=INFO',
        ]

        # Forward all custom boolean options passed by the user to the current
        # setuptools command (e.g., "--clean") to the "pyinstaller" command.
        if self.clean:
            self._pyinstaller_args.append('--clean')
        if self.debug:
            self._pyinstaller_args.extend((
                '--debug',

                # UPX-based compression uselessly consumes non-trivial time
                # (especially under Windows, where process creation is fairly
                # heavyweight) when freezing debug binaries. To optimize and
                # simplify debugging, such compression is disabled.
                '--noupx',
            ))
            stderrs.output_warning('Enabling bootloader debug messages.')
            stderrs.output_warning('Disabling UPX-based compression.')
        # If *NOT* debugging and UPX is *NOT* found, print a non-fatal warning.
        # While optional, freezing in the absence of UPX produces uncompressed
        # and hence considerably larger executables.
        elif not cmds.is_cmd('upx'):
            stderrs.output_warning(
                'UPX not installed or "upx" not in the current ${PATH}.')
            stderrs.output_warning('Frozen binaries will *NOT* be compressed.')
コード例 #23
0
    def read_metabo_config(self, sim, cells, p):

        # create the path to read the metabolism config file:
        self.configPath = pathnames.join(p.conf_dirname,
                                         p.metabo_config_filename)

        # read the config file into a dictionary:
        self.config_dic = confio.read_metabo(self.configPath)

        # determine if mitochondria are enabled:
        self.mit_enabled = self.config_dic['enable mitochondria']

        # obtain specific sub-dictionaries from the config file:
        substances_config = self.config_dic['biomolecules']
        reactions_config = self.config_dic.get('reactions', None)
        transporters_config = self.config_dic.get('transporters', None)
        channels_config = self.config_dic.get('channels', None)
        modulators_config = self.config_dic.get('modulators', None)

        # initialize the substances of metabolism in a core field encapsulating
        # Master of Molecules:
        self.core = MasterOfNetworks(sim,
                                     cells,
                                     substances_config,
                                     p,
                                     mit_enabled=self.mit_enabled)

        if reactions_config is not None:

            # initialize the reactions of metabolism:
            self.core.read_reactions(reactions_config, sim, cells, p)
            self.core.write_reactions()
            self.core.create_reaction_matrix()
            self.core.write_reactions_env()
            self.core.create_reaction_matrix_env()

            if self.mit_enabled is True:
                self.core.write_reactions_mit()
                self.core.create_reaction_matrix_mit()

            self.reactions = True

        else:
            self.core.create_reaction_matrix()
            self.core.create_reaction_matrix_env()
            self.reactions = False

        # initialize transporters, if defined:
        if transporters_config is not None and len(transporters_config) > 0:
            self.core.read_transporters(transporters_config, sim, cells, p)
            self.core.write_transporters(sim, cells, p)
            self.transporters = True

        else:
            self.transporters = False

        # initialize any custom channels:-------------

        if channels_config is not None and len(channels_config) > 0:
            self.core.read_channels(channels_config, sim, cells, p)
            self.channels = True

        else:
            self.channels = False

        # initialize any modulators------------------

        if modulators_config is not None and len(modulators_config) > 0:
            self.core.read_modulators(modulators_config, sim, cells, p)
            self.modulators = True

        else:
            self.modulators = False

        # test to make sure the metabolic simulation includes core components:
        if 'ATP' not in self.core.molecules or 'ADP' not in self.core.molecules or 'Pi' not in self.core.molecules:

            raise BetseSimConfException(
                "This metabolic simulation does not contain key substances."
                "Please define 'ATP', 'ADP' and 'Pi' biomolecules in your "
                "metabolism configuration file and try again.")

        # read in network plotting options:
        self.core.net_plot_opts = self.config_dic.get('network plotting', None)

        # set plotting options for the network:
        set_net_opts(self.core, self.core.net_plot_opts, p)

        # after primary initialization, check and see if optimization required:
        opti = self.config_dic['optimization']['optimize network']
        self.core.opti_N = self.config_dic['optimization'][
            'optimization steps']
        self.core.opti_method = self.config_dic['optimization'][
            'optimization method']
        self.core.target_vmem = float(
            self.config_dic['optimization']['target Vmem'])
        self.core.opti_T = float(
            self.config_dic['optimization']['optimization T'])
        self.core.opti_step = float(
            self.config_dic['optimization']['optimization step'])
        #     self.core.opti_run = self.config_dic['optimization']['run from optimization']
        #
        if opti is True:
            logs.log_info(
                "The Metabolic Network is being analyzed for optimal rates...")
            self.core.optimizer(sim, cells, p)
            self.reinitialize(sim, cells, p)
コード例 #24
0
    def dot_dirname(self) -> str:
        '''
        Absolute dirname of this application's **root dot directory** (i.e.,
        top-level directory containing this application's user-specific files
        and hence residing in the home directory of the current user), silently
        creating this directory if *not* already found.

        This directory contains user-specific files (e.g., logfiles, profile
        files) both read from and written to at application runtime. These are
        typically plaintext files consumable by external users and third-party
        utilities.

        Locations
        ----------
        Denote:

        * ``{package_name}`` the value of the :meth:`package_name` property for
          this application (e.g., ``betse`` for BETSE).
        * ``{username}`` the name of the current user (e.g., ``leycec``).

        Then the dirname returned by this property is:

        * Under Linux, ``~/.{package_name}/``. This property intentionally does
          *not* currently comply with the `XDG Base Directory Specification`_
          (e.g., ``~/.local/share/betse``), which the authors regard as
          `unhelpful if not blatantly harmful <xkcd Standards_>`__.
        * Under OS X, ``~/Library/Application Support/{package_name}``.
        * Under Windows,
          ``C:\\Documents and Settings\\{username}\\Application Data\\{package_name}``.

        .. _XDG Base Directory Specification:
            http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
        .. _xkcd Standards:
            https://xkcd.com/927
        '''

        # Avoid circular import dependencies.
        from betse.util.os import oses
        from betse.util.os.shell import shellenv
        from betse.util.path import dirs, pathnames

        # Absolute dirname of this directory.
        dot_dirname = None

        # If the current platform is macOS, set the appropriate directory.
        if oses.is_macos():
            dot_dirname = pathnames.join(
                pathnames.get_home_dirname(),
                'Library',
                'Application Support',
                self.package_name,
            )
        # If the current platform is Windows, set the appropriate directory.
        elif oses.is_windows():
            dot_dirname = pathnames.join(shellenv.get_var('APPDATA'),
                                         self.package_name)
        # Else, assume the current platform to be POSIX-compatible.
        else:
            #FIXME: Explicitly assert POSIX compatibility here. To do so, we'll
            #want to define and call a new betse.util.os.oses.die_unless_posix()
            #function here.
            dot_dirname = pathnames.join(pathnames.get_home_dirname(),
                                         '.' + self.package_name)

        # Create this directory if not found.
        dirs.make_unless_dir(dot_dirname)

        # Return this dirname.
        return dot_dirname
コード例 #25
0
    def reinitialize(self, sim, cells, p):

        # create the path to read the metabolism config file:
        self.configPath = pathnames.join(p.conf_dirname,
                                         p.metabo_config_filename)

        # read the config file into a dictionary:
        self.config_dic = confio.read_metabo(self.configPath)

        # determine if mitochondria are enabled:
        self.mit_enabled = self.config_dic['enable mitochondria']

        # obtain specific sub-dictionaries from the config file:
        substances_config = self.config_dic['biomolecules']
        reactions_config = self.config_dic.get('reactions', None)
        transporters_config = self.config_dic.get('transporters', None)
        channels_config = self.config_dic.get('channels', None)
        modulators_config = self.config_dic.get('modulators', None)

        # initialize the substances of metabolism in a core field encapsulating
        # Master of Molecules:
        self.core.tissue_init(sim, cells, substances_config, p)

        if reactions_config is not None and len(reactions_config):

            # initialize the reactions of metabolism:
            self.core.read_reactions(reactions_config, sim, cells, p)
            self.core.write_reactions()
            self.core.create_reaction_matrix()
            self.core.write_reactions_env()
            self.core.create_reaction_matrix_env()

            if self.mit_enabled is True:
                self.core.write_reactions_mit()
                self.core.create_reaction_matrix_mit()

            self.reactions = True

        else:
            self.core.create_reaction_matrix()
            self.core.create_reaction_matrix_env()
            self.reactions = False

        # initialize transporters, if defined:
        if transporters_config is not None and len(transporters_config) > 0:
            self.core.read_transporters(transporters_config, sim, cells, p)
            self.core.write_transporters(sim, cells, p)
            self.transporters = True

        else:
            self.transporters = False

        # initialize any custom channels:-------------

        if channels_config is not None and len(channels_config) > 0:
            self.core.read_channels(channels_config, sim, cells, p)
            self.channels = True

        else:
            self.channels = False

        # initialize any modulators------------------

        if modulators_config is not None and len(modulators_config) > 0:
            self.core.read_modulators(modulators_config, sim, cells, p)
            self.modulators = True

        else:
            self.modulators = False

        # test to make sure the metabolic simulation includes core components:
        if 'ATP' not in self.core.molecules or 'ADP' not in self.core.molecules or 'Pi' not in self.core.molecules:
            raise BetseSimConfException(
                "This metabolic simulation does not contain key substances."
                "Please define 'ATP', 'ADP' and 'Pi' biomolecules in your "
                "metabolism configuration file and try again.")
コード例 #26
0
def gradient_bitmap(pc, cells, p, bitmap_filename = None):

    """
    This modulator reads in a bitmap supplied by the user from the
    directory in params.

    Red is treated as positive, blue is treated as negative, and
    black is treated as zero.

    Parameters
    ------------

    pc:   array of cell centre or membrane midpoints
    cells:  BETSE cells object
    p:      BETSE parameters object

    """

    if bitmap_filename is None:  # default this to loading the 'gradient bitmap function' from params
        bitmap_filename = p.grad_bm_fn
        grad_bm_offset = np.max((p.grad_bm_offset, 0.0))

    else:
        grad_bm_offset = 0.0


    if len(pc) == len(cells.mem_i):
        xmap = cells.map_mem2ecm

    elif len(pc) == len(cells.cell_i):
        xmap = cells.map_cell2ecm

    # xmi = cells.xmin - 4*p.cell_radius
    # ymi = cells.ymin - 4*p.cell_radius
    # xma = cells.xmax + 4*p.cell_radius
    # yma = cells.ymax + 4*p.cell_radius

    xmi = cells.xmin
    ymi = cells.ymin
    xma = cells.xmax
    yma = cells.ymax

    xx = np.linspace(cells.xmin, cells.xmax, cells.X.shape[1])
    yy = np.linspace(cells.ymin, cells.ymax, cells.X.shape[0])

    fn1 = pathnames.join(p.conf_dirname, bitmap_filename)

    # Three-dimensional Numpy array of the RGB-ordered integer components of all
    # pixels loaded from this image.
    a1o = pilnumpy.load_image(filename=fn1, mode=ImageModeType.COLOR_RGB)

    a1 = np.asarray(a1o, dtype=np.float64)

    a1_F = (a1[:, :, 0] -  a1[:, :, 2]) / 255

    a1_F = np.flipud(a1_F)
    # a1_F = fd.integrator(a1_F, sharp=0.5) # smooth a little to avoid bizarre visual effects

    xa = np.linspace(xmi, xma, a1_F.shape[1])
    ya = np.linspace(ymi, yma, a1_F.shape[0])

    spline_F = interpolate.interp2d(xa, ya, a1_F, kind='linear', fill_value=0.0)
    fe = spline_F(xx, yy)

    fe = fd.integrator(fe, sharp=0.5) # smooth a little to avoid bizarre visual effects

    f = fe.ravel()[xmap]

    # f = (f/f.max()) + grad_bm_offset
    f = f + grad_bm_offset

    # indz = (f < 0.0).nonzero()
    # f[indz] = 0.0
    dynamics = lambda t:1

    return f, dynamics
コード例 #27
0
def get_package_worktree_dirname_or_none(
        package: ModuleType) -> StrOrNoneTypes:
    '''
    **Absolute canonical dirname** (i.e., absolute dirname after resolving
    symbolic links) of the **Git-based working tree** (i.e., top-level
    directory containing the canonical ``.git`` subdirectory) governing the
    passed top-level Python package if found *or* ``None`` otherwise.

    Caveats
    ----------
    **This directory typically does not exist.** This directory is only
    required during development by developers and hence should *not* be assumed
    to exist.

    For safety and efficiency, this function does *not* recursively search the
    filesystem up from the directory yielding this package for a ``.git``
    subdirectory; rather, this function only directly searches that directory
    itself for such a subdirectory. For that reason, this function typically
    returns ``None`` for *all* modules except top-level packages installed in a
    developer manner (e.g., by running ``sudo python3 setup.py develop``).

    Parameters
    ----------
    package : ModuleType
        Top-level Python package to be queried.

    Returns
    ----------
    StrOrNoneTypes
        Either:

        * The absolute canonical dirname of that package's Git working tree if
          that package was installed in a developer manner: e.g., by

          * ``python3 setup.py develop``.
          * ``python3 setup.py symlink``.

        * ``None`` if that package was installed in a non-developer manner:
          e.g., by

          * ``pip3 install``.
          * ``python3 setup.py develop``.
    '''

    # Avoid circular import dependencies.
    from betse.util.path import dirs, pathnames
    from betse.util.py.module import pymodule

    # Absolute canonical dirname of the directory providing this package,
    # canonicalized into a directory rather than symbolic link to increase the
    # likelihood of obtaining the actual parent directory of this package
    package_dirname = pymodule.get_dirname_canonical(package)

    # Absolute dirname of the parent directory of this directory.
    worktree_dirname = pathnames.get_dirname(package_dirname)

    # Absolute dirname of the ".git" subdirectory of this parent directory.
    git_subdirname = pathnames.join(worktree_dirname, '.git')

    # Return this parent directory's absolute dirname if this subdirectory
    # exists *OR* "None" otherwise.
    return worktree_dirname if dirs.is_dir(git_subdirname) else None
コード例 #28
0
    def _export_cells_times_data(
        self,
        phase: SimPhase,
        cells_times_data: SequenceTypes,
        csv_column_name: str,
        csv_dir_basename: str,
        csv_basename_prefix: str,
    ) -> None:
        '''
        Save one plaintext file in comma-separated value (CSV) format
        containing arbitrary simulation data spatially situated at cell centres
        for each sampled time step of the current simulation phase.

        Parameters
        ----------
        phase : SimPhase
            Current simulation phase.
        cells_times_data : ndarray
            Two-dimensional Numpy array of arbitrary simulation data spatially
            situated at the centres of all cells for all sampled time steps.
        csv_column_name : str
            Name of the column containing this data in all CSV-formatted files
            exported by this method.
        csv_dir_basename : str
            Basename of the directory containing all CSV-formatted files
            exported by this method.
        csv_basename_prefix : str
            Substring prefixing the basenames of all CSV-formatted files
            exported by this method.
        '''

        # Absolute pathname of the directory containing all CSV files
        # specifically exported by this method.
        csv_dirname = pathnames.join(phase.export_dirname, csv_dir_basename)

        # One-dimensional Numpy arrays of the X and Y coordinates
        # (respectively) of the centres of all cells.
        cell_centres_x = mathunit.upscale_coordinates(
            phase.cells.cell_centres[:, 0])
        cell_centres_y = mathunit.upscale_coordinates(
            phase.cells.cell_centres[:, 1])

        # For the 0-based index of each sampled time step...
        for time_step in range(len(phase.sim.time)):
            # Basename of the CSV-formatted file exported for this time step,
            # excluding suffixing "."-prefixed filetype.
            csv_basename_sans_filetype = '{}{}'.format(csv_basename_prefix,
                                                       time_step)

            # Absolute filename of this CSV file.
            csv_filename = self._get_csv_filename(
                phase=phase,
                basename_sans_filetype=csv_basename_sans_filetype,
                dirname=csv_dirname,
            )

            # Ordered dictionary mapping from CSV column names to data arrays.
            csv_column_name_to_values = OrderedArgsDict(
                'x [um]',
                cell_centres_x,
                'y [um]',
                cell_centres_y,
                csv_column_name,
                cells_times_data[time_step],
            )

            # Export this data to this CSV file.
            npcsv.write_csv(filename=csv_filename,
                            column_name_to_values=csv_column_name_to_values)
コード例 #29
0
def get_pathname(package: ModuleOrStrTypes, pathname: str) -> str:
    '''
    Absolute pathname canonicalized from the passed relative pathname in a
    portable manner guaranteed to be relative to the absolute dirname of the
    passed application package if a path with this canonical pathname exists
    *or* raise an exception otherwise (i.e., if no such path exists).

    Specifically, this method returns:

    * If this application is a PyInstaller-frozen executable binary, the
      concatenation of (in order):

      #. The absolute path of the temporary directory containing all
         application data resources extracted from this binary by this
         executable's bootloader as specified by the PyInstaller-specific
         private attribute ``_MEIPASS`` injected into the canonical :mod:`sys`
         module by the PyInstaller bootloader embedded in this binary. "And
         it's turtles all the way down."
      #. The passed relative pathname.

    * If this application is a :mod:`setuptools`-installed script wrapper, the
      result of querying :mod:`setuptools` for the absolute path of the passed
      relative pathname. In this case, this path will have been preserved as is
      in the :mod:`setuptools`-installed copy of this application in the
      package tree for the active Python interpreter.
    * Else, the concatenation of (in order):

      #. The absolute path of the directory providing this root package.
      #. The passed relative pathname.

      In this case, this application is typically either a
      :mod:`setuptools`-symlinked script wrapper *or* was invoked via the
      secretive ``python3 -m {package.__name__}`` command.

    Parameters
    ----------
    package : ModuleOrStrTypes
        Topmost package of the application to canonicalize this pathname for,
        defined as either:

        * The fully-qualified name of this package, in which case this function
          dynamically imports this package.
        * A previously imported package object.
    pathname : str
        Relative pathname of the path to be canonicalized.

    Returns
    ----------
    Absolute pathname of this path relative to the absolute pathname of this
    application package.

    Raises
    ----------
    BetseModuleException
        If this package is a subpackage rather than topmost.
    BetsePathException
        If no path with the absolute pathname to be returned exists.
    BetsePathnameException
        If this pathname is absolute rather than relative.
    '''

    # Avoid circular import dependencies.
    from betse.lib.setuptools import supresource
    from betse.util.path import pathnames, paths
    from betse.util.py import pyfreeze
    from betse.util.py.module import pymodule

    # If this package is *NOT* topmost, raise an exception.
    pymodule.die_unless_topmost(package)

    # If this pathname is absolute rather than relative, raise an exception.
    pathnames.die_if_absolute(pathname)

    # Name of this package.
    package_name = pymodule.get_name_qualified(package)

    # If this application is frozen by PyInstaller, canonicalize this path
    # relative to the directory to which this application is unfrozen.
    if pyfreeze.is_frozen_pyinstaller():
        # Absolute dirname of the directory containing this frozen application.
        app_frozen_dirname = pyfreeze.get_app_dirname_pyinstaller()

        #FIXME: This requires generalization to the passed package, if in fact
        #this can be generalized. If this cannot be generalized, then some
        #other means will be needed to achieve the same or a similar effect.
        app_pathname = pathnames.join(app_frozen_dirname, pathname)
    # Else if this application is a setuptools-installed script wrapper,
    # canonicalize this path by deferring to the setuptools resource API.
    elif supresource.is_dir(module_name=package_name, dirname=pathname):
        app_pathname = supresource.get_pathname(module_name=package_name,
                                                pathname=pathname)
    # Else, the current application is either a setuptools-symlinked script
    # wrapper *OR* was invoked via the secretive "python3 -m betse"
    # command. In either case, this directory's path is directly obtainable
    # relative to the absolute path of the passed package.
    else:
        # Absolute canonical dirname of the directory defining this package.
        package_dirname = pymodule.get_dirname_canonical(package)

        # Canonicalize this path relative to this directory.
        app_pathname = pathnames.join(package_dirname, pathname)

    # If this path is not found, fail; else, return this path.
    return paths.path_or_die(app_pathname)
コード例 #30
0
def copy_dir(
    # Mandatory parameters.
    src_dirname: str,
    trg_dirname: str,

    # Optional parameters.
    overwrite_policy: DirOverwritePolicy = (
        DirOverwritePolicy.HALT_WITH_EXCEPTION),
    ignore_basename_globs: IterableOrNoneTypes = None,
) -> None:
    '''
    Recursively copy the source directory with the passed dirname into the
    target directory with the passed dirname.

    For generality:

    * All nonexistent parents of the target directory will be recursively
      created, mimicking the action of the ``mkdir -p`` shell command on
      POSIX-compatible platforms in a platform-agnostic manner.
    * All symbolic links in the source directory will be preserved (i.e.,
      copied as is rather than their transitive targets copied instead).

    Parameters
    -----------
    src_dirname : str
        Absolute or relative dirname of the source directory to be copied from.
    trg_dirname : str
        Absolute or relative dirname of the target directory to be copied to.
    overwrite_policy : DirOverwritePolicy
        **Directory overwrite policy** (i.e., strategy for handling existing
        paths to be overwritten by this copy). Defaults to
        :attr:`DirOverwritePolicy.HALT_WITH_EXCEPTION`, raising an exception if
        any target path already exists.
    ignore_basename_globs : optional[IterableTypes]
        Iterable of shell-style globs (e.g., ``('*.tmp', '.keep')``) matching
        the basenames of all paths transitively owned by this source directory
        to be ignored during recursion and hence neither copied nor visited.
        If non-``None`` and the ``overwrite_policy`` parameter is
        :attr:`DirOverwritePolicy.OVERWRITE`, this iterable is ignored and a
        non-fatal warning is logged. Defaults to ``None``, in which case *all*
        paths transitively owned by this source directory are unconditionally
        copied and visited.

    Raises
    -----------
    BetseDirException
        If either:

        * The source directory does *not* exist.
        * The target directory is a subdirectory of the source directory.
          Permitting this edge case induces non-trivial issues, including
          infinite recursion from within the musty entrails of the
          :mod:`distutils` package (e.g., due to relative symbolic links).
        * The passed ``overwrite_policy`` parameter is
          :attr:`DirOverwritePolicy.HALT_WITH_EXCEPTION` *and* one or more
          subdirectories of the target directory already exist that are also
          subdirectories of the source directory. For safety, this function
          always preserves rather than overwrites existing target
          subdirectories.

    See Also
    -----------
    https://stackoverflow.com/a/22588775/2809027
        StackOverflow answer strongly inspiring this function's
        :attr:`DirOverwritePolicy.SKIP_WITH_WARNING` implementation.
    '''

    # Log this copy.
    logs.log_debug('Copying directory: %s -> %s', src_dirname, trg_dirname)

    # If the source directory does *NOT* exist, raise an exception.
    die_unless_dir(src_dirname)

    # If the target directory is a subdirectory of the source directory, raise
    # an exception. Permitting this edge case provokes issues, including
    # infinite recursion from within the musty entrails of the "distutils"
    # codebase (possibly due to relative symbolic links).
    die_if_subdir(parent_dirname=src_dirname, child_dirname=trg_dirname)

    # If passed an iterable of shell-style globs matching ignorable basenames,
    # convert this iterable into a predicate function of the form required by
    # the shutil.copytree() function. Specifically, this function accepts the
    # absolute or relative pathname of an arbitrary directory and an iterable
    # of the basenames of all subdirectories and files directly in this
    # directory; this function returns an iterable of the basenames of all
    # subdirectories and files in this directory to be ignored. This signature
    # resembles:
    #
    #     def ignore_basename_func(
    #         parent_dirname: str,
    #         child_basenames: IterableTypes) -> IterableTypes
    ignore_basename_func = None
    if ignore_basename_globs is not None:
        ignore_basename_func = shutil.ignore_patterns(*ignore_basename_globs)

    # If raising a fatal exception if any target path already exists...
    if overwrite_policy is DirOverwritePolicy.HALT_WITH_EXCEPTION:
        # Dictionary of all keyword arguments to pass to shutil.copytree(),
        # preserving symbolic links as is.
        copytree_kwargs = {
            'symlinks': True,
        }

        # If ignoring basenames, inform shutil.copytree() of these basenames.
        if ignore_basename_func is not None:
            copytree_kwargs['ignore'] = ignore_basename_func

        # Raise an exception if this target directory already exists. While we
        # could defer to the exception raised by the shutil.copytree() function
        # for this case, this exception's message erroneously refers to this
        # directory as a file and is hence best avoided as non-human-readable:
        #
        #     [Errno 17] File exists: 'sample_sim'
        die_if_dir(trg_dirname)

        # Recursively copy this source to target directory. To avoid silently
        # overwriting all conflicting target paths, the shutil.copytree()
        # rather than dir_util.copy_tree() function is called.
        shutil.copytree(src=src_dirname, dst=trg_dirname, **copytree_kwargs)
    # Else if overwriting this target directory with this source directory...
    elif overwrite_policy is DirOverwritePolicy.OVERWRITE:
        # If an iterable of shell-style globs matching ignorable basenames was
        # passed, log a non-fatal warning. Since the dir_util.copy_tree()
        # function fails to support this functionality and we are currently too
        # lazy to do so, a warning is as much as we're willing to give.
        if ignore_basename_globs is not None:
            logs.log_warning(
                'dirs.copy() parameter "ignore_basename_globs" '
                'ignored when parameter "is_overwritable" enabled.')

        # Recursively copy this source to target directory, preserving symbolic
        # links as is. To silently overwrite all conflicting target paths, the
        # dir_util.copy_tree() rather than shutil.copytree() function is
        # called.
        dir_util.copy_tree(src_dirname, trg_dirname, preserve_symlinks=1)

    #FIXME: Given how awesomely flexible the manual approach implemented below
    #is, we should probably consider simply rewriting the above two approaches
    #to reuse the exact same logic. It works. It's preferable. Let's reuse it.
    #FIXME: Actually, this is increasingly critical. Third-party functions
    #called above -- notably, the dir_util.copy_tree() function -- appear to
    #suffer critical edge cases. This can be demonstrated via the BETSEE GUI by
    #attempting to save an opened simulation configuration to a subdirectory of
    #itself, which appears to provoke infinite recursion from within the musty
    #depths of the "distutils" codebase. Of course, the implementation below
    #could conceivably suffer similar issues. If this is the case, this
    #function should explicitly detect attempts to recursively copy a source
    #directory into a subdirectory of itself and raise an exception.
    #FIXME: See the above FIXME comment addressing the infinite recursion issue
    #discussed here.

    # Else if logging a warning for each target path that already exists, do so
    # by manually implementing recursive directory copying. Sadly, Python
    # provides no means of doing so "out of the box."
    elif overwrite_policy is DirOverwritePolicy.SKIP_WITH_WARNING:
        # Avoid circular import dependencies.
        from betse.util.path import files, paths, pathnames
        from betse.util.type.iterable import sequences

        # Passed parameters renamed for disambiguity.
        src_root_dirname = src_dirname
        trg_root_dirname = trg_dirname

        # Basename of the top-level target directory to be copied to.
        trg_root_basename = pathnames.get_basename(src_root_dirname)

        # For the absolute pathname of each recursively visited source
        # directory, an iterable of the basenames of all subdirectories of this
        # directory, and an iterable of the basenames of all files of this
        # directory...
        for src_parent_dirname, subdir_basenames, file_basenames in _walk(
                src_root_dirname):
            # Relative pathname of the currently visited source directory
            # relative to the absolute pathname of this directory.
            parent_dirname_relative = pathnames.relativize(
                src_dirname=src_root_dirname, trg_pathname=src_parent_dirname)

            # If ignoring basenames...
            if ignore_basename_func is not None:
                # Sets of the basenames of all ignorable subdirectories and
                # files of this source directory.
                subdir_basenames_ignored = ignore_basename_func(
                    src_parent_dirname, subdir_basenames)
                file_basenames_ignored = ignore_basename_func(
                    src_parent_dirname, file_basenames)

                # If ignoring one or more subdirectories...
                if subdir_basenames_ignored:
                    # Log the basenames of these subdirectories.
                    logs.log_debug(
                        'Ignoring source "%s/%s" subdirectories: %r',
                        trg_root_basename, parent_dirname_relative,
                        subdir_basenames_ignored)

                    # Remove these subdirectories from the original iterable.
                    # Since the os.walk() function supports in-place changes to
                    # this iterable, this iterable is modified via this less
                    # efficient function rather than efficient alternatives
                    # (e.g., set subtraction).
                    sequences.remove_items(sequence=subdir_basenames,
                                           items=subdir_basenames_ignored)

                # If ignoring one or more files...
                if file_basenames_ignored:
                    # Log the basenames of these files.
                    logs.log_debug('Ignoring source "%s/%s" files: %r',
                                   trg_root_basename, parent_dirname_relative,
                                   file_basenames_ignored)

                    # Remove these files from the original iterable. Unlike
                    # above, we could technically modify this iterable via
                    # set subtraction: e.g.,
                    #
                    #     subdir_basenames -= subdir_basenames_ignored
                    #
                    # For orthogonality, preserve the above approach instead.
                    sequences.remove_items(sequence=file_basenames,
                                           items=file_basenames_ignored)

            # Absolute pathname of the corresponding target directory.
            trg_parent_dirname = pathnames.join(trg_root_dirname,
                                                parent_dirname_relative)

            # Create this target directory if needed.
            make_unless_dir(trg_parent_dirname)

            # For the basename of each non-ignorable file of this source
            # directory...
            for file_basename in file_basenames:
                # Absolute filenames of this source and target file.
                src_filename = pathnames.join(src_parent_dirname,
                                              file_basename)
                trg_filename = pathnames.join(trg_parent_dirname,
                                              file_basename)

                # If this target file already exists...
                if paths.is_path(trg_filename):
                    # Relative filename of this file. The absolute filename of
                    # this source or target file could be logged instead, but
                    # this relative filename is significantly more terse.
                    filename_relative = pathnames.join(
                        trg_root_basename, parent_dirname_relative,
                        file_basename)

                    # Warn of this file being ignored.
                    logs.log_warning('Ignoring existing target file: %s',
                                     filename_relative)

                    # Ignore this file by continuing to the next.
                    continue

                # Copy this source to target file.
                files.copy(src_filename=src_filename,
                           trg_filename=trg_filename)
    # Else, this overwrite policy is unrecognized. Raise an exception.
    else:
        raise BetseDirException(
            'Overwrite policy "{}" unrecognized.'.format(overwrite_policy))