Exemplo n.º 1
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())
Exemplo n.º 2
0
def die_if_empty(
    sequence: SequenceTypes, exception_message: StrOrNoneTypes = None) -> None:
    '''
    Raise an exception with the passed message (defaulting to a human-readable
    message) if the passed sequence is **empty** (i.e., contains *no* items).

    Parameters
    ----------
    sequence: SequenceTypes
        Sequence to be validated.
    exception_message : StrOrNoneTypes
        Exception message to be raised. Defaults to ``None``, in which case an
        exception message synthesized from the passed arguments is raised.

    Raises
    ----------
    BetseSequenceException
        If the sequence is empty.
    '''

    # Avoid circular import dependencies.
    from betse.util.type.obj import objects

    # If this sequence is empty, raise an exception.
    if is_empty(sequence):
        # If no exception message was passed, synthesize one.
        if not exception_message:
            exception_message = 'Sequence "{}" empty.'.format(
                objects.get_class_name_unqualified(sequence))

        # Raise this exception.
        raise BetseSequenceException(exception_message)
Exemplo n.º 3
0
def die_unless_has_method(obj: object, *method_names: str) -> None:
    '''
    Raise an exception unless the passed object defines all methods with the
    passed names.

    Parameters
    ----------
    obj : object
        Object to be validated.
    method_names : tuple[str]
        Tuple of the names of all methods to validate this object to define.

    Raises
    ----------
    BetseMethodException
        If one or more such methods are *not* bound to this object.
    '''

    # Avoid circular import dependencies.
    from betse.util.type.obj import objects

    # If this object fails to define one or more of these methods...
    if not has_method(obj, *method_names):
        # For the name of each such method...
        for method_name in method_names:
            # If this object fails to define a method with this name, raise an
            # exception describing this failure.
            if not has_method(obj, method_name):
                raise BetseMethodException('Method "{}.{}" undefined.'.format(
                    objects.get_class_name_unqualified(obj), method_name))
Exemplo n.º 4
0
def get_enum_name_from_member(enum_member: EnumMemberType) -> str:
    '''
    Unqualified name of the enumeration class to which the passed enumeration
    member belongs (e.g., ``SolverType`` given a member ``SolverType.FULL``).

    Parameters
    ----------
    enum_member : EnumMemberType
        Enumeration member to be inspected.

    Returns
    ----------
    str
        Unqualified name of this member's enumeration class.
    '''

    # Avoid circular import dependencies.
    from betse.util.type.obj import objects

    # Strange, but true. We don't ask questions. Neither should you.
    return objects.get_class_name_unqualified(enum_member)
Exemplo n.º 5
0
    def iter_runners_enabled(self, phase: SimPhase) -> GeneratorType:
        '''
        Generator yielding the method and simulation configuration of each
        **enabled simulation pipeline runner** (i.e., method bound to this
        pipeline decorated by the :func:`piperunner` decorator *and* enabled by
        this simulation configuration) for the passed simulation phase.

        If this simulation phase disables this pipeline, this generator reduces
        to the empty generator (i.e., yields no values); else, this generator
        excludes:

        * All methods defined by this pipeline subclass *not* decorated by the
          :func:`piperunner` decorator.
        * All methods defined by this pipeline subclass decorated by that
          decorator currently disabled by their simulation configuration (e.g.,
          such that the YAML-backed value of the ``enabled`` key in this
          configuration evaluates to ``False``).

        Parameters
        ----------
        phase : SimPhase
            Current simulation phase.

        Yields
        ----------
        (runner_method : MethodType, runner_conf : SimConfExportABC)
            2-tuple where:

            * ``runner_method`` is the method implementing this runner, whose
              method name is guaranteed to be prefixed by the substring
              :attr:`_RUNNER_METHOD_NAME_PREFIX`. Note that this method object
              defines the following custom instance variables:

              * ``metadata``, whose value is an instance of the
                :class:`SimPipeRunnerMetadata` class.

            * ``runner_conf`` is the YAML-backed list item configuring this
              runner, derived from the YAML-formatted simulation configuration
              file associated with the passed simulation phase.

        Raises
        ----------
        BetseSimPipeException
            If any simulation runner configured for this pipeline by the passed
            simulation phase is **unrecognized** (i.e., if this pipeline
            defines no corresponding method).
        '''

        # If this pipeline is disabled, log this fact and return immediately.
        if not self._is_enabled(phase):
            logs.log_debug('Ignoring disabled %s...',
                           self._noun_plural_lowercase)
            return
        # Else, this pipeline is enabled.

        # For each runner configuration specified for this pipeline...
        for runner_conf in self.iter_runners_conf(phase):
            # If this configuration is *NOT* YAML-backed, raise an exception.
            objtest.die_unless_instance(obj=runner_conf, cls=SimConfExportABC)

            # If this runner is disabled, log this fact and ignore this runner.
            if not runner_conf.is_enabled:
                logs.log_debug('Ignoring disabled %s "%s"...',
                               self._noun_singular_lowercase, runner_conf.kind)
                continue
            # Else, this runner is enabled.

            # Name of the pipeline method implementing this runner.
            runner_method_name = (self._RUNNER_METHOD_NAME_PREFIX +
                                  runner_conf.kind)

            # Method running this runner if recognized *OR* "None" otherwise.
            runner_method = objects.get_callable_or_none(
                obj=self, callable_name=runner_method_name)

            # If this runner is unrecognized, raise an exception.
            if runner_method is None:
                raise BetseSimPipeException(
                    '{} "{}" unrecognized '
                    '(i.e., method {}.{}() not found).'.format(
                        self._noun_singular_uppercase, runner_conf.kind,
                        objects.get_class_name_unqualified(self),
                        runner_method_name))
            # Else, this runner is recognized.

            # Yield a 2-tuple of this runner's method and configuration.
            yield runner_method, runner_conf
Exemplo n.º 6
0
    def init_sans_libs(self) -> None:
        '''
        Initialize this application *except* mandatory third-party dependencies
        of this application, which requires external resources (e.g.,
        command-line options, configuration files) to have been parsed.

        Specifically, the default implementation of this method (in order):

        #. Enables Python's standard handler for segmentation faults.
        #. Globalizes the passed application metadata singleton.
        #. Enables this application's default logging configuration.
        #. Validates but does *not* initialize all mandatory third-party
           dependencies of this application, which the :meth:`init_libs` method
           does so subsequently.
        #. Validates core directories and files required at program startup,
           creating all such directories and files that do *not* already exist
           and are reasonably creatable.
        #. Validates the active Python interpreter (e.g., to support
           multithreading).
        #. Validates the underlying operating system (e.g., to *not* be a
           vanilla Windows shell environment ala either CMD.exe or PowerShell).

        To support caller-specific error handling, this function is intended to
        be called immediately *after* this application begins catching
        otherwise uncaught exceptions.

        Caveats
        ----------
        **This method is idempotent.** This method has been explicitly designed
        to be safely recallable. Each subsequent invocation of this method
        following the first simply reinitializes this application. While
        typically useless, idempotency is required by low-level automation to
        guarantee consistency across repeated runs (e.g., tests, scripts).

        Design
        ----------
        This method intentionally avoids initializing mandatory dependencies,
        as doing so would require late-time startup logic assumed by such
        initiazilation to have already been performed -- namely, finalization
        of the logging configuration and hence command-line argument parsing.
        By compare, this method is internally called by the :meth:`__init__`
        method called as the first statement of this application. Since no
        startup logic has been performed yet, initialization of dependencies
        is deferred until significantly later in the startup process.
        '''

        # Avoid circular import dependencies.
        from betse.lib import libs
        from betse.util.io.error import errfault
        from betse.util.io.log import logconfig, logs
        from betse.util.os import oses
        from betse.util.py import pys
        from betse.util.test import tests
        from betse.util.type.obj import objects

        # Enable Python's standard handler for segmentation faults *BEFORE*
        # performing any further logic, any of which could conceivably trigger
        # a segmentation fault and hence process termination.
        errfault.handle_faults()

        # Enable our default logging configuration for the current Python
        # process *BEFORE* performing any validation, thus logging any
        # exceptions raised by this validation.
        logconfig.init()

        # Log all prior behaviour. Attempting to do so *BEFORE* enabling our
        # default logging configuration above would silently fail, since the
        # standard "logging" API silently squelches debug messages by default.
        logs.log_debug('Application singleton "%s" established.',
                       objects.get_class_name_unqualified((self)))
        logs.log_debug('Default segementation fault handler enabled.')
        logs.log_debug('Testing environment detected: %r', tests.is_testing())

        # Validate mandatory dependencies. Avoid initializing these
        # dependencies now (e.g., by calling init_libs()). Doing so requires
        # finalization of the logging configuration (e.g., by parsing CLI
        # options), which has yet to happen this early in the lifecycle.
        libs.die_unless_runtime_mandatory_all()

        # Validate the active Python interpreter and operating system *AFTER*
        # mandatory dependencies. While the former (mostly) comprises
        # unenforced recommendations, the latter comprises enforced
        # requirements and should thus be validated first.
        oses.init()
        pys.init()