Beispiel #1
0
def init() -> None:
    '''
    Enable our default logging configuration for the active Python process if
    this configuration has yet to be enabled *or* reduce to a noop otherwise
    (e.g., if this method has already been called).

    Specifically, this function instantiates the private :data:`_log_conf`
    singleton to an instance of the application-specific :class:`LogConf`
    class, publicly accessible via the :func:`get_log_conf` module getter. This
    singleton defines sane default filters, formatters, and handlers for the
    root logger, which callers may customize by setting singleton properties.
    '''

    # Avoid circular import dependencies.
    from betse.util.io.log import logs
    from betse.util.io.log.conf.logconfcls import LogConf

    # Module-scoped variables to be set below.
    global _log_conf

    # If a logging configuration already exists...
    if _log_conf is not None:
        # Log a non-fatal warning.
        logs.log_warning('Logging already configured (e.g., due to '
                         'logconf.init() having been called).')

        # Reduce to a noop.
        return

    # Instantiate this singleton global with the requisite defaults.
    # print('Reinitializing logging.')
    _log_conf = LogConf()

    # Log this initialization *AFTER* guaranteeing logging sanity.
    logs.log_debug('Initialized singleton logging configuration.')
Beispiel #2
0
    def deinit(self) -> None:
        '''
        Deinitialize this application metadata singleton and hence the current
        application, which requires this singleton for basic functioning.

        Caveats
        ----------
        **No application logic may be performed after calling this method.**
        This method nullifies this singleton *and* closes open logfile handles,
        both of which are required for basic application logic.

        See Also
        ----------
        :func:`betse.util.app.meta.appmetoone.deinit`
            Higher-level function encapsulating this lower-level method.
        '''

        # Avoid circular import dependencies.
        from betse.util.app.meta import appmetaone
        from betse.util.io.log import logs
        from betse.util.io.log.conf import logconf

        # Log this attempt.
        logs.log_debug('Deinitializing application metadata singleton...')

        # Disable our default logging configuration, ensuring that open logfile
        # handles are closed on application closure.
        logconf.deinit()

        # Deglobalize this singleton *AFTER* prior logic (e.g., the call to
        # logconf.deinit()), any of which could potentially require this
        # singleton.
        appmetaone.unset_app_meta()
Beispiel #3
0
def import_requirement(requirement: Requirement) -> ModuleType:
    '''
    Import and return the top-level module object satisfying the passed
    :mod:`setuptools`-specific requirement.

    Parameters
    ----------
    requirement : Requirement
        Object describing this module or package's required name and version.

    Returns
    ----------
    ModuleType
        Top-level package object implementing this requirement.

    Raises
    ----------
    ImportError
        If this package is unimportable.
    '''

    # Avoid circular import dependencies.
    from betse.util.py.module import pymodname
    from betse.util.py.module.pymodname import (
        DISTUTILS_PROJECT_NAME_TO_MODULE_NAME)

    # Fully-qualified name of this requirement's package.
    package_name = DISTUTILS_PROJECT_NAME_TO_MODULE_NAME[
        requirement.project_name]

    # Log this importation, which can often have unexpected side effects.
    logs.log_debug('Importing third-party package "%s"...', package_name)

    # Import and return this package.
    return pymodname.import_module(package_name)
Beispiel #4
0
def remove_file(filename: str) -> None:
    '''
    Remove the non-directory file with the passed filename.

    Parameters
    ----------
    filename : str
        Absolute or relative filename of the file to be removed.

    Raises
    ----------
    BetseFileException
        If this file does *not* exist.
    FileNotFoundError
        If this file did exist at the time this function was called but was
        removed immediately before this function called the low-level
        :func:`os.remove` function -- or, in simpler words, if a filesystem
        race condition occurs.
    '''

    # Log this removal.
    logs.log_debug('Removing file: %s', filename)

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

    # Remove this file. Note that the os.remove() and os.unlink() functions are
    # identical. (That was a tad silly, Guido.)
    os.remove(filename)
Beispiel #5
0
def reading_chars(filename: str, encoding: str = 'utf-8') -> TextIOWrapper:
    '''
    Open and return a filehandle suitable for reading the plaintext file with
    the passed filename encoded with the passed encoding.

    This function returns a :class:`file`-like object suitable for use wherever
    the :func:`open` builtin is callable (e.g., in ``with`` statements).

    Parameters
    ----------
    filename : str
        Relative or absolute path of the plaintext text to be read.
    encoding : optional[str]
        Name of the encoding to be used. Defaults to UTF-8.

    Returns
    ----------
    TextIOWrapper
        :class:`file`-like object encapsulating the opened file.
    '''

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

    # Log this I/O operation.
    logs.log_debug('Reading chars: %s', filename)

    # Raise an exception unless this file exists.
    files.die_unless_file(filename)

    # Open this file.
    return open(filename, mode='rt', encoding=encoding)
Beispiel #6
0
def init() -> None:
    '''
    Initialize this submodule.

    Specifically (in order):

    #. Initialize all uninitialized global variables of this submodule.
    #. If the currently installed version of Numpy was linked against an
       unoptimized BLAS implementation and is thus itself unoptimized, log a
       non-fatal warning.
    '''

    # Log this initialization.
    logs.log_debug('Initializing NumPy...')

    # Initialize all uninitialized global variables of this submodule.
    _init_globals()

    # If Numpy linked against an unoptimized BLAS, log a non-fatal warning.
    if not is_blas_optimized():
        logs.log_warning(
            'Numpy unoptimized; scaling down to single-core operation. '
            'Consider installing an optimized multithreaded '
            'CBLAS implementation (e.g., OpenBLAS, ATLAS, ACML, MKL) and '
            'reinstalling Numpy to use this implementation.')
Beispiel #7
0
def deinit() -> None:
    '''
    Disable our default logging configuration for the active Python process if
    this configuration has yet to be disabled *or* reduce to a noop otherwise
    (e.g., if this method has already been called).

    Specifically, this function calls the :meth:`LogConf.deinit` method of the
    private :data:`_log_conf` singleton and then nullifies that singleton.
    '''

    # Avoid circular import dependencies.
    from betse.util.io.log import logs

    # Module-scoped variables to be set below.
    global _log_conf

    # If no logging configuration exists, silently noop.
    #
    # Note that this common edge case occurs when an exception is raised early
    # at application startup *BEFORE* the associated init() function is called.
    # Ergo, this constitutes neither a fatal error nor non-fatal warning.
    if _log_conf is None:
        return
    # Else, a logging configuration currently exists.

    # Log this deinitialization *AFTER* guaranteeing logging sanity.
    logs.log_debug('Deinitializing singleton logging configuration...')

    # Deinitialize this logging configuration.
    _log_conf.deinit()

    # Nullify this singleton global for safety *AFTER* all other actions above.
    _log_conf = None
Beispiel #8
0
def _upgrade_sim_conf_to_1_0_0(p: Parameters) -> None:
    '''
    Upgrade the in-memory contents of the passed simulation configuration to
    reflect the newest structure of these contents expected by version 1.0.0
    of this application.
    '''

    # Log this upgrade attempt.
    logs.log_debug('Upgrading simulation configuration to 1.0.0 format...')

    # Localize configuration subdictionaries for convenience.
    results_dict = p._conf['results options']
    tissue_dict = p._conf['tissue profile definition']

    # For each tissue profile, define the "color" cell targets type if needed.
    for profile in tissue_dict['tissue']['profiles']:
        if 'color' not in profile['cell targets']:
            profile['cell targets']['color'] = 'ff0000'  # Red. Just 'cause.

    # Define a default uniquified name for each pipelined export.
    _upgrade_sim_conf_to_1_0_0_exports_name(p)

    # If the "visuals" subsection is undefined, define this subsection.
    if 'visuals' not in results_dict:
        results_dict['visuals'] = {
            'cell indices': {
                'show': results_dict['enumerate cells'],
                'single cell': results_dict['plot cell index'],
            }
        }
Beispiel #9
0
def init() -> None:
    '''
    Validate the active Python interpreter.

    This function does *not* validate this interpreter's version, as the
    top-level :mod:`betse.metadata` submodule already does so at the start of
    application startup. Instead, this function (in order):

    #. Logs a non-fatal warning if this interpreter is *not* 64-bit.
    '''

    # Log this validation.
    logs.log_debug('Validating Python interpreter...')

    # If this Python interpreter is 32- rather than 64-bit, log a non-fatal
    # warning. While technically feasible, running BETSE under 32-bit Python
    # interpreters imposes non-trivial constraints detrimental to sanity.
    if is_wordsize_32():
        logs.log_warning(
            '32-bit Python interpreter detected. '
            '{name} will be confined to low-precision datatypes and '
            'at most 4GB of available RAM, '
            'impeding the reliability and scalability of modelling. '
            'Consider running {name} only under a '
            '64-bit Python interpreter.'.format(name=metadata.NAME))
Beispiel #10
0
    def _parse_options_top_log(self) -> None:
        '''
        Parse top-level logging options globally applicable to all subcommands.
        '''

        # Avoid circular import dependencies.
        from betse.util.io.log.logenum import LogLevel

        # Singleton logging configuration for the current Python process.
        log_config = logconf.get_log_conf()

        # Configure logging according to the passed options. Note that order of
        # assignment is insignificant here.
        # print('is verbose? {}'.format(self._args.is_verbose))
        log_config.is_verbose = self._args.is_verbose
        log_config.filename = self._args.log_filename
        log_config.file_level = LogLevel[self._args.log_level.upper()]

        # Log (and thus display by default) a human-readable synopsis of
        # metadata associated with this application.
        #
        # Note that this logging is intentionally deferred from the earliest
        # time at which logging could technically be performed (namely, the
        # AppMetaABC.init_sans_libs() method). Why? Because logging requires
        # finalizing the logging configuration, which requires parsing *ALL*
        # command-line options. The disadvantage of this otherwise sane
        # approach is, of course, that this logging is deferred.
        self._log_header()

        # Log all string arguments passed to this command.
        logs.log_debug('Passed argument list: {}'.format(self._arg_list))
Beispiel #11
0
def init() -> None:
    '''
    Initialize both this submodule *and* the :mod:`dill` package.

    Specifically, this function instructs :mod:`dill` to pickle with our
    application-specific :class:`BetsePickler` subclass.
    '''

    # Log this initialization.
    logs.log_debug('Initializing dill...')

    # Core "dill" submodule. Sadly, dill 0.2.8.0 and 0.2.8.1 but (confusingly)
    # *NOT* dill >= 0.2.8.2 fundamentally broke backward compatibility by
    # renaming this public API -- necessitating conditional logic here to
    # properly find this submodule. For further details, see:
    #     https://github.com/uqfoundation/dill/issues/268
    dill_core_submodule = None

    # For dill >= 0.2.8.2, attempt to find the new "dill._dill" submodule.
    try:
        dill_core_submodule = dill._dill
    # For dill < 0.2.8.2, fallback to find the older "dill.dill" submodule.
    except:
        dill_core_submodule = dill.dill

    # If this submodule does *NOT* contain the expected "Pickler" base class,
    # raise an exception.
    objtest.die_unless_has_class(dill_core_submodule, 'Pickler')

    # Instruct "dill" to pickle with our application-specific pickler subclass.
    dill_core_submodule.Pickler = BetsePickler
Beispiel #12
0
def remove_file_if_found(filename: str) -> None:
    '''
    Remove the non-directory file with the passed filename if this file exists
    *or* silently reduce to a noop otherwise (i.e., if this file does *not*
    exist).

    For safety, this function removes this file atomically; in particular, this
    file's existence is *not* explicitly tested for.

    Parameters
    ----------
    filename : str
        Absolute or relative filename of the file to be removed.
    '''

    # Log this removal if the subsequent removal attempt is likely to actually
    # remove a file. Due to race conditions with other threads and processes,
    # this file could be removed after this test succeeds but before the
    # removal is performed. Since this is largely ignorable, the worst case is
    # an extraneous log message. *collective_shrug*
    if is_file(filename):
        logs.log_debug('Removing file: %s', filename)

    # Remove this file atomically. To avoid race conditions with other threads
    # and processes, this operation is *NOT* embedded in an explicit test for
    # file existence. Instead, the Pythonic Way is embraced.
    try:
        os.remove(filename)
    # If this file does *NOT* exist, ignore this exception.
    except FileNotFoundError:
        pass
Beispiel #13
0
    def get_mtime_recursive_newest(dirname: str) -> NumericSimpleTypes:

        # Log this recursion.
        logs.log_debug(
            'Recursively computing newest fwalk()-based mtime of: %s', dirname)

        # Return the maximum of all mtimes in the...
        return max(
            # Generator expression recursively yielding the maximum mtime of
            # all files and subdirectories of each subdirectory of this
            # directory including this directory.
            (
                # Maximum of this subdirectory's mtime and the mtimes of all
                # files in this subdirectory, whose filenames are produced by a
                # tuple comprehension joining this subdirectory's dirname to
                # each file's basename. By recursion, the mtimes of all
                # subdirectories of this subdirectory are implicitly computed
                # and hence need *NOT* be explicitly included here.
                #
                # For parity with the unoptimized get_mtime_recursive_newest()
                # implementation defined below as well to avoid unwanted
                # complications, symbolic links are *NOT* followed. Hence,
                # os.lstat() rather than os.stat() is intentionally called.
                max((os.fstat(parent_dir_fd).st_mtime, ) + tuple(
                    os.lstat(child_file_basename,
                             dir_fd=parent_dir_fd).st_mtime
                    for child_file_basename in child_file_basenames), )
                # For the currently visited directory's dirname, a sequence of
                # the dirnames of all subdirectories of this directory, a
                # sequence of the filenames of all files in this directory, and
                # a directory handle to this directory such that errors emitted
                # by low-level functions called by os.walk() (e.g.,
                # os.listdir()) are *NOT* silently ignored...
                for _, _, child_file_basenames, parent_dir_fd in _fwalk(
                    dirname)), )
Beispiel #14
0
def make_default() -> SimCallbacksBC:
    '''
    Create and return a new simulation phase callbacks object suitable for use
    as an efficient fallback in the event that a caller fails to supply a
    caller-specific callbacks object.

    Specifically, this factory function:

    * If tests are currently being run, this function creates and returns an
      instance of the :class:`SimCallbacksBC` subclass. While less efficient
      than the comparable :class:`SimCallbacksNoop` subclass, doing so ensures
      that contractual guarantees maintained by the :class:`CallbacksBC`
      superclass are properly exercised.
    * Else, this function creates and returns an instance of the
      :class:`SimCallbacksNoop` subclass. While more efficient than the
      comparable :class:`SimCallbacksBC` subclass, doing so avoids contractual
      guarantees maintained by the :class:`CallbacksBC` superclass.
    '''

    # If tests are currently being run...
    if tsttest.is_testing():
        # Log this behaviour.
        logs.log_debug('Enabling test-specific callbacks...')

        # Return a new simulation phase callbacks object preserving (and hence
        # testing) superclass API guarantees.
        return SimCallbacksBC()
    # Else, tests are *NOT* currently being run. In this case...
    else:
        # Log this behaviour.
        logs.log_debug('Enabling optimized noop callbacks...')

        # Return a new simulation phase callbacks object maximizing efficiency.
        return SimCallbacksNoop()
Beispiel #15
0
def make_unless_dir(*dirnames: str) -> None:
    '''
    Create all passed directories that do *not* already exist, silently
    ignoring those that *do* already exist.

    All nonexistent parents of this directory are also recursively created,
    reproducing the action of the POSIX-compliant ``mkdir -p`` shell command.

    Parameters
    -----------
    pathnames : tuple[str]
        Tuple of the absolute or relative pathnames of all directories to
        create.

    See Also
    -----------
    :func:`make_parent_unless_dir`
        Related function creating the parent directory of this path.
    '''

    # If this directory does *NOT* already exist, create this directory. To
    # support logging, this condition is explicitly tested for. To avoid race
    # conditions (e.g., in the event this directory is created between testing
    # and creating this directory), we preserve the makedirs() keyword argument
    # "exist_ok=True" below.
    for dirname in dirnames:
        if not is_dir(dirname):
            # Log this creation.
            logs.log_debug('Creating directory: %s', dirname)

            # Create this directory if still needed.
            os.makedirs(dirname, exist_ok=True)
Beispiel #16
0
    def get_mtime_recursive_newest(dirname: str) -> NumericSimpleTypes:

        # Log this recursion.
        logs.log_debug(
            'Recursively computing newest walk()-based mtime of: %s', dirname)

        # Return the maximum of all mtimes in the...
        return max(
            # Generator expression recursively yielding the maximum mtime of
            # all files and subdirectories of each subdirectory of this
            # directory including this directory.
            (
                # Maximum of this subdirectory's mtime and the mtimes of all
                # files in this subdirectory, whose filenames are produced by a
                # tuple comprehension joining this subdirectory's dirname to
                # each file's basename. By recursion, the mtimes of all
                # subdirectories of this subdirectory are implicitly computed
                # and hence need *NOT* be explicitly included here.
                max((os_path.getmtime(parent_dirname), ) + tuple(
                    os_path.getmtime(
                        os_path.join(parent_dirname, child_file_basename))
                    for child_file_basename in child_file_basenames), )
                # For the currently visited directory's dirname, a sequence of
                # the dirnames of all subdirectories of this directory, and a
                # sequence of the filenames of all files in this directory such
                # that errors emitted by low-level functions called by
                # os.walk() (e.g., os.listdir()) are *NOT* silently ignored...
                for parent_dirname, _, child_file_basenames in _walk(dirname)
            ), )
Beispiel #17
0
def close_figure(figure: MatplotlibFigureType) -> None:
    '''
    **Close** (i.e., clear, delete, remove, garbage collect) the passed figure,
    guaranteeing that all resources consumed by this figure will be
    subsequently reclaimed after an indeterminate period of time.

    Specifically, this function (in order):

    #. Nullifies the contents of all non-axes artists of this figure.
    #. Nullifies the contents of all axes of this figure.
    #. Closes all interactive windows (if any) associated with this figure.
    #. Deassociates this figure from the :mod:`matplotlib.pyplot` GCF API.

    Caveats
    -----------
    **Figure closure is surprisingly non-trivial.** Failure to call this
    function on open figures usually results in some or all of the contents of
    those figures continuing to reside in memory. Ergo, this high-level
    function should *always* be called in lieu of low-level
    :mod:`matplotlib.pyplot` functions (e.g., :mod:`matplotlib.pyplot.close`)
    or equally low-level :class:`matplotlib.figure.Figure` methods (e.g.,
    :meth:`matplotlib.figure.Figure.cla`), which are all known to behave
    non-deterministically and hence unsafely.

    Parameters
    -----------
    figure : MatplotlibFigureType
        Figure to be closed.

    See Also
    -----------
    https://stackoverflow.com/a/17106460/2809027
        StackOverflow answer strongly inspiring this implementation.
    '''

    # Defer heavyweight imports.
    from matplotlib import pyplot

    # Log this closure.
    logs.log_debug('Closing matplotlib figure "%r"...', figure.number)

    # Nullify the contents of all non-axes artists of this figure.
    figure.clf()

    # For each axis of this figure, nullify the contents of this axis *AFTER*
    # that of non-axes. Why? We have utterly no idea. The matplotlib API is
    # dark voodoo that, frankly, not even matplotlib developers grok. It is
    # unsafe to ask too many questions.
    for figure_axis in figure.axes:
        figure_axis.cla()

    # Deassociate this figure from the "matplotlib.pyplot" GCF API, implicitly
    # closing all interactive windows (if any) associated with this figure. In
    # theory, doing so should also implicitly nullify the above content; in
    # practice, however, the matplotlib API is sufficiently unreliable across
    # version bumps that explicitly nullifying that content is all but
    # non-optional. Thanks for all of the bugs, matplotlib!
    pyplot.close(figure)
Beispiel #18
0
def _upgrade_sim_imports_to_0_5_2() -> None:
    '''
    Upgrade the in-memory module and class structure of the active Python
    interpreter to reflect the newest structure of these modules and classes
    expected by version 0.5.2 (i.e., "Happiest Hodgkin") of this application.
    '''

    # Log this upgrade attempt.
    logs.log_debug('Upgrading simulation imports to 0.5.2 format...')

    # Import all modules whose fully-qualified names have been modified.
    from betse.lib.yaml.abc import yamlabc, yamllistabc
    from betse.science import channels
    from betse.science.math import finitediff
    from betse.science.phase import phasecls
    from betse.science.config.export.visual import (confexpvisanim,
                                                    confexpvisplot,
                                                    confexpvisabc)
    from betse.science.config.export.visual.confexpvisanim import (
        SimConfExportAnimCells, SimConfExportAnimCellsEmbedded)
    from betse.science.config.export.visual.confexpvisplot import (
        SimConfExportPlotCells)
    from betse.util.type.iterable.mapping import mapcls

    # Alias obsolete module names to current module objects.
    sys.modules['betse.science.config.confabc'] = yamlabc
    sys.modules['betse.science.config.export.visual.confvisualabc'] = (
        confexpvisabc)
    sys.modules['betse.science.finitediff'] = finitediff
    sys.modules['betse.science.tissue.channels'] = channels
    sys.modules['betse.science.plot.plotconfig'] = confexpvisplot
    sys.modules['betse.science.plot.anim.animconfig'] = confexpvisanim
    sys.modules['betse.science.visual.anim.animconfig'] = confexpvisanim
    sys.modules['betse.science.visual.plot.plotconfig'] = confexpvisplot
    sys.modules['betse.util.type.mappings'] = mapcls

    # Alias obsolete to current class names.
    yamlabc.SimConfList = yamllistabc.YamlList
    confexpvisanim.SimConfAnimOne = SimConfExportAnimCells
    confexpvisabc.SimConfVisualABC = confexpvisabc.SimConfVisualCellsABC
    confexpvisabc.SimConfVisualMixin = confexpvisabc.SimConfVisualCellsYAMLMixin
    confexpvisabc.SimConfVisualMolecule = confexpvisabc.SimConfVisualCellsNonYAML
    confexpvisabc.SimConfVisualGeneric = SimConfExportAnimCellsEmbedded
    confexpvisabc.SimConfVisualListable = SimConfExportPlotCells
    confexpvisabc.SimConfVisual = SimConfExportPlotCells
    confexpvisabc.SimConfListableVisual = SimConfExportPlotCells
    phasecls.SimPhaseType = phasecls.SimPhaseKind
    sys.modules[
        'betse.science.config.export.visual.confexpvisanim'].SimConfAnim = (
            confexpvisanim.SimConfExportAnims)
    sys.modules[
        'betse.science.config.export.visual.confexpvisplot'].SimConfPlot = (
            confexpvisplot.SimConfExportPlots)
    sys.modules['betse.science.visual.anim.animconfig'].AnimConfig = (
        confexpvisanim.SimConfExportAnims)
    sys.modules['betse.science.visual.plot.plotconfig'].PlotConfig = (
        confexpvisplot.SimConfExportPlots)
Beispiel #19
0
def is_blas_optimized() -> bool:
    '''
    ``True`` only if the currently installed version of Numpy is linked against
    an optimized BLAS (Basic Linear Algebra Subprograms) implementation,
    ideally but *not* necessarily parallelized across multiple processors.

    Optimized BLAS implementations are *strongly* recommended over unoptimized
    BLAS implementations. The :func:`numpy.dot` operator, which is implicitly
    optimized when Numpy is linked against a optimized BLAS implementation, is
    frequently called by BETSE in its critical path.

    Note that testing for parallelized optimized BLAS implementations, while
    more specific and hence preferable, is infeasible for common edge-cases
    (e.g., Debian-based Linux distributions). For further details, see the
    :data:`_OPTIMIZED_BLAS_OPT_INFO_LIBRARY_REGEX` string global.
    '''

    # For each private tester implementing a heuristic for this public test (in
    # order of decreasing generality, portability, and reliability)...
    for tester_heuristic in (
            # Detect conda-managed Numpy first, as doing so reduces to a single
            # well-defined filesystem access and hence is guaranteed to be both the
            # most optimal and portable solution.
            _is_blas_optimized_conda,
            _is_blas_optimized_opt_info_libraries,
            _is_blas_optimized_opt_info_library_dirs,
            _is_blas_optimized_opt_info_macos,
            _is_blas_optimized_posix_symlink,
    ):
        # Attempt to...
        try:
            # Log the current heuristic being attempted.
            logs.log_debug('Detecting BLAS by heuristic %s()...',
                           tester_heuristic.__name__)

            # Call this tester, capturing the result for subsequent handling.
            tester_result = tester_heuristic()

            # If this tester definitively identified Numpy as either
            # optimized or non-optimized...
            if tester_result is not None:
                # Log this result.
                logs.log_debug('BLAS optimization detected: %r', tester_result)

                # Return this result.
                return tester_result
            # Else, continue to the next tester.
        # If an error occurs, log that error *WITHOUT* raising an exception.
        # Detecting Numpy optimization is non-essential and hence hardly worth
        # halting the application over.
        except Exception as exception:
            logs.log_exception(exception)

    # Else, all heuristics failed to definitively identify Numpy to be either
    # optimized or non-optimized. For safety, assume the latter.
    return False
Beispiel #20
0
def writing_bytes(
    filename: str, is_overwritable: bool = False) -> BufferedIOBase:
    '''
    Open and return a filehandle suitable for writing the binary file with the
    passed filename, transparently compressing this file if the filetype of
    this filename is that of a supported archive format.

    This function returns a file-like object suitable for use wherever the
    :func:`open` builtin is callable (e.g., in ``with`` statements).

    Parameters
    ----------
    filename : str
        Relative or absolute path of the binary file to be written. If this
        filename is suffixed by a supported archive filetype (i.e., if the
        :func:`betse.util.path.archives.is_filetype` function returns ``True``
        for this filename), the returned filehandle automatically writes the
        compressed rather than uncompressed byte contents of this file.
    is_overwritable : optional[bool]
        ``True`` if overwriting this file when this file already exists *or*
        ``False`` if raising an exception when this file already exists.
        Defaults to ``False`` for safety.

    Returns
    ----------
    BufferedIOBase
        File-like object encapsulating this opened file.
    '''

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

    # Log this I/O operation.
    logs.log_debug('Writing bytes: %s', filename)

    # If this file is *NOT* overwritable, raise an exception if this path
    # already exists.
    if not is_overwritable:
        paths.die_if_path(filename)

    # Create the parent directory of this file if needed.
    dirs.make_parent_unless_dir(filename)

    # If this file is compressed, open and return a file handle writing
    # compressed bytes to this file.
    if archives.is_filetype(filename):
        return archives.write_bytes(filename, is_overwritable=is_overwritable)
    # Else, this file is uncompressed.
    else:
        # Mode with which to open this file for byte-oriented writing.
        mode = get_mode_write_bytes(is_overwritable)

        # Open and return a file handle writing uncompressed bytes to this file.
        return open(filename, mode=mode)
Beispiel #21
0
    def unload(self) -> None:

        # If this file is actually loaded, log this operation.
        if self.is_loaded:
            logs.log_debug('Closing YAML file: %s', self._conf_filename)

        # Unload our superclass.
        super().unload()

        # Nullify all instance variables for safety.
        self._conf_basename = None
        self._conf_dirname = None
        self._conf_filename = None
Beispiel #22
0
def init() -> None:
    '''
    Initialize Pillow if uninitialized *or* reduce to a noop otherwise (i.e.,
    if Pillow is already initialized).
    '''

    # Log this initialization.
    logs.log_debug('Initializing Pillow...')

    # If Pillow is already initialized, this function internally reduces to an
    # efficient noop in the expected manner (e.g., by accessing a private
    # boolean global).
    Image.init()
Beispiel #23
0
    def _log_metadata(self) -> None:
        '''
        Log low-level metadata pertaining to the current application.

        Specifically, this method logs (in no particular order):

        * The subclass of the currently registered application singleton.
        * The enabling of the default segmentation fault handler.
        * Whether a testing environment is detected.
        * Whether a headless environment is detected.
        '''

        # Avoid circular import dependencies.
        from betse.util.app.meta import appmetaone
        from betse.util.os import displays
        from betse.util.test import tsttest
        from betse.util.type.obj import objects

        # Log this metadata.
        logs.log_debug(
            'Application singleton "%s" established.',
            objects.get_class_name_unqualified(appmetaone.get_app_meta()))
        logs.log_debug(
            'Default segmentation fault handler enabled.')
        logs.log_debug(
            'Testing environment detected: %r', tsttest.is_testing())
        logs.log_debug(
            'Headless environment detected: %r', displays.is_headless())
Beispiel #24
0
def writing_chars(
    filename: str,
    is_overwritable: bool = False,
    encoding: str = 'utf-8',
) -> TextIOWrapper:
    '''
    Open and return a filehandle suitable for writing the plaintext file with
    the passed filename encoded with the passed encoding.

    This function returns a file-like object suitable for use wherever the
    :func:`open` builtin is callable (e.g., in ``with`` statements).

    Parameters
    ----------
    filename : str
        Relative or absolute path of the plaintext file to be written.
    is_overwritable : optional[bool]
        ``True`` if overwriting this file when this file already exists *or*
        ``False`` if raising an exception when this file already exists.
        Defaults to ``False`` for safety.
    encoding : optional[str]
        Name of the encoding to be used. Defaults to UTF-8.

    Returns
    ----------
    TextIOWrapper
        File-like object encapsulating this opened file.
    '''

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

    # Log this I/O operation.
    logs.log_debug('Writing chars: %s', filename)

    # If this file is *NOT* overwritable, raise an exception if this path
    # already exists.
    if not is_overwritable:
        paths.die_if_path(filename)

    # Create the parent directory of this file if needed.
    dirs.make_parent_unless_dir(filename)

    # Mode with which to open this file for character-oriented writing.
    mode = get_mode_write_chars(is_overwritable)

    # Open and return a file handle writing uncompressed bytes to this file.
    return open(filename, mode=mode, encoding=encoding)
Beispiel #25
0
def init() -> None:
    '''
    Validate the current platform.

    This function (in order):

    #. Logs a non-fatal warning if this platform is a non-WSL variant
       of Microsoft Windows (e.g., vanilla Windows, Cygwin Windows).
    #. Logs a non-fatal warning if this platform is *not* recognized as
       officially supported by this application (e.g., BSD*, Solaris).
    '''

    # Avoid circular import dependencies.
    from betse.util.os.brand import windows

    # Log this validation.
    logs.log_debug('Validating platform...')

    # Human-readable string describing the set of all officially supported
    # platforms known to interoperate sanely with this application.
    oses_supported_str = (
        'Consider running {name} only under Linux or macOS. '
        'Note that Linux is now freely emulatable under Windows 10 '
        'via the Windows Subsystem for Linux (WSL). '
        'See official installation instructions at:\n'
        '\thttps://docs.microsoft.com/en-us/windows/wsl/install-win10'.format(
            name=metadata.NAME))

    # If this is a non-WSL Windows variant, log a non-fatal warning.
    if windows.is_windows():
        #FIXME: Restore this logging statement to a non-fatal warning *AFTER*
        #resolving the "FIXME:" comment chain in the
        #"betse.util.os.brand.windows" submodule concerning PowerShell. Yikes!
        # logs.log_warning(
        logs.log_info(
            'Windows platform detected. '
            'Python itself and third-party scientific frameworks for Python '
            '(e.g., Numpy, SciPy, Matplotlib) are known to '
            'behave suboptimally on this platform. '
            '%s', oses_supported_str)

    # If this platform is officially unsupported by this application, log a
    # non-fatal warning.
    if not is_supported():
        logs.log_warning('Unsupported platform "%s" detected. %s', get_name(),
                         oses_supported_str)
Beispiel #26
0
def unset_app_meta() -> None:
    '''
    Unset the **application metadata singleton** (i.e., application-wide object
    synopsizing application metadata via read-only properties).

    Equivalently, this function resets this singleton to its initial state
    (i.e., ``None``).
    '''

    # Enable this singleton global to be overwritten be the passed parameter.
    global _app_meta

    # Log this attempt.
    logs.log_debug('Unsetting application metadata singleton...')

    # Revert this singleton global to its initial state.
    _app_meta = None
Beispiel #27
0
def _upgrade_sim_imports_to_0_7_1() -> None:
    '''
    Upgrade the in-memory module and class structure of the active Python
    interpreter to reflect the newest structure of these modules and classes
    expected by version 0.7.1 of this application.
    '''

    # Log this upgrade attempt.
    logs.log_debug('Upgrading simulation imports to 0.7.1 format...')

    # Import all modules whose fully-qualified names have been modified.
    from betse.science import channels
    from betse.science.config.export import visual

    # Alias obsolete module names to current module objects.
    sys.modules['betse.science.channelo'] = channels
    sys.modules['betse.science.config.visual'] = visual
Beispiel #28
0
def reading_bytes(filename: str) -> BufferedIOBase:
    '''
    Open and return a filehandle suitable for reading the binary file with the
    passed filename, transparently decompressing this file if the filetype of
    this filename is that of a supported archive format.

    This function returns a :class:`file`-like object suitable for use wherever
    the :func:`open` builtin is callable (e.g., in ``with`` statements).

    Parameters
    ----------
    filename : str
        Relative or absolute path of the binary file to be read. If this
        filename is suffixed by a supported archive filetype (i.e., if the
        :func:`betse.util.path.archives.is_filetype` function returns ``True``
        for this filename), the returned filehandle automatically reads the
        decompressed rather than compressed byte contents of this file.

    Returns
    ----------
    BufferedIOBase
        :class:`file`-like object encapsulating this opened file.

    Raises
    ----------
    BetseFileException
        If this filename is *not* that of an existing file.
    '''

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

    # Log this I/O operation.
    logs.log_debug('Reading bytes: %s', filename)

    # Raise an exception unless this file exists.
    files.die_unless_file(filename)

    # If this file is compressed, open and return a file handle reading
    # decompressed bytes from this file.
    if archives.is_filetype(filename):
        return archives.read_bytes(filename)
    # Else, this file is uncompressed. Open and return a typical file handle
    # reading bytes from this file.
    else:
        return open(filename, mode='rb')
Beispiel #29
0
    def save_inplace(self) -> None:
        '''
        Serialize the low-level dictionary internally stored in this object to
        the current YAML-formatted simulation configuration file associated
        with this object, replacing the prior contents of this file.

        This method effectively implements the "Save" GUI metaphor.
        '''

        # Log this save.
        logs.log_debug('Resaving YAML file: %s', self._conf_filename)

        # Resave this dictionary to this file.
        yamls.save(
            container=self.conf,
            filename=self.conf_filename,
            is_overwritable=True,
        )
Beispiel #30
0
def _upgrade_sim_imports_to_0_6_0() -> None:
    '''
    Upgrade the in-memory module and class structure of the active Python
    interpreter to reflect the newest structure of these modules and classes
    expected by version 0.6.0 of this application.
    '''

    # Log this upgrade attempt.
    logs.log_debug('Upgrading simulation imports to 0.6.0 format...')

    # Import all modules whose fully-qualified names have been modified.
    from betse.lib.yaml.abc import yamlabc
    from betse.science.config.model import conftis
    from betse.science.config.export.visual import (confexpvisanim,
                                                    confexpvisplot,
                                                    confexpvisabc)
    from betse.science.tissue import tisprofile, tishandler
    from betse.science.tissue.event import tisevecut, tisevevolt
    from betse.science.tissue.picker import tispickcls, tispickimage
    from betse.science.tissue.picker.tispickcls import TissuePickerPercent
    from betse.science.tissue.picker.tispickimage import (TissuePickerImage,
                                                          TissuePickerImageMask
                                                          )
    from betse.science.tissue.tisprofile import CutProfile

    # Alias obsolete module names to current module objects.
    sys.modules['betse.lib.yaml.yamlabc'] = yamlabc
    sys.modules['betse.science.config.event.eventcut'] = tisevecut
    sys.modules['betse.science.config.event.eventvoltage'] = tisevevolt
    sys.modules['betse.science.config.export.confexpvisanim'] = confexpvisanim
    sys.modules['betse.science.config.export.confexpvisplot'] = confexpvisplot
    sys.modules['betse.science.config.export.confvis'] = confexpvisabc
    sys.modules['betse.science.config.tissue.conftis'] = conftis
    sys.modules['betse.science.tissue.bitmapper'] = tispickimage
    sys.modules['betse.science.tissue.handler'] = tishandler
    sys.modules['betse.science.tissue.tiscls'] = tisprofile
    sys.modules['betse.science.tissue.tissuecls'] = tisprofile
    sys.modules['betse.science.tissue.tissuepick'] = tispickcls

    # Alias obsolete to current class names.
    tispickcls.TissuePickerBitmap = TissuePickerImage
    tispickcls.TissuePickerRandom = TissuePickerPercent
    tispickimage.BitMapper = TissuePickerImageMask
    tisprofile.TissueCut = CutProfile