Пример #1
0
def is_supported() -> bool:
    '''
    ``True`` only if the current platform is officially supported by
    this application.

    This function currently only returns ``True`` for the following platforms
    (in no particular order):

    * Apple macOS.
    * Linux.
    * Microsoft Windows.

    Caveats
    ----------
    This function returning ``True`` does *not* necessarily imply this
    application to behave optimally under this platform. In particular,
    Microsoft Windows is officially supported by popular demand but well-known
    to behave suboptimally with respect to Python itself and third-party
    scientific frameworks for Python (e.g., Numpy, Matplotlib).
    '''

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

    # The drawing of the Three draws nigh.
    return linux.is_linux() or macos.is_macos() or windows.is_windows()
Пример #2
0
def shell_quote(text: str) -> str:
    '''
    Shell-quote the passed string in a platform-specific manner.

    If the current platform is:

    * *Not* Windows (e.g., Linux, OS X), the returned string is guaranteed to
      be suitable for passing as an arbitrary positional argument to external
      commands.
    * Windows, the returned string is suitable for passing *only* to external
      commands parsing arguments in the same manner as the Microsoft C runtime.
      While *all* applications on POSIX-compliant systems are required to parse
      arguments in the same manner (i.e., according to Bourne shell lexing), no
      such standard applies to Windows applications. Shell quoting is therefore
      fragile under Windows -- like pretty much everything.
    '''

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

    # If the current OS is Windows, do *NOT* perform POSIX-compatible quoting.
    # Windows is POSIX-incompatible and hence does *NOT* parse command-line
    # arguments according to POSIX standards. In particular, Windows does *NOT*
    # treat single-quoted arguments as single arguments but rather as multiple
    # shell words delimited by the raw literal `'`. This is circumventable by
    # calling an officially undocumented Windows-specific function. (Awesome.)
    if windows.is_windows():
        import subprocess
        return subprocess.list2cmdline([text])
    # Else, perform POSIX-compatible quoting.
    else:
        import shlex
        return shlex.quote(text)
Пример #3
0
def get_version() -> str:
    '''
    Human-readable ``.``-delimited version specifier of the current kernel.

    This function returns:

    * Under Linux, the current Linux kernel version (e.g.,
      ``4.1.15-gentoo-r1``),
    * Under macOS, the current XNU kernel version (e.g., ``13.0.0``).
    * Under Windows, the current Windows kernel version (e.g., ``10.0.10240``).
    * Under all other platforms, the string returned by the
      :func:`platform.release` function.
    '''

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

    # Kernel version specifier to be returned.
    kernel_version = None

    # If the current platform is Windows, defer to the platform.version()
    # function. For only this platform, this function returns the desired
    # fine-grained Windows kernel version:
    #
    #     # Under Windows 10...
    #     >>> import platform
    #     >>> platform.release()
    #     "10"
    #     >>> platform.version()
    #     "10.0.10240"
    #     >>> platform.win32_ver()[1]
    #     "6.2.9200"
    #
    # However, note that the above does *NOT* generalize to any other platform:
    #
    #     # Under Gentoo Linux...
    #     >>> import platform
    #     >>> platform.release()
    #     "4.19.27-gentoo-r1"
    #     >>> platform.version()
    #     "#1 SMP Fri Apr 26 20:12:45 EDT 2019"
    #
    # Version specifier to be returned, defaulting to that returned by
    # platform.release(). While this typically corresponds to the low-level
    # version of the current kernel (e.g., "4.1.15" under Linux, "13.0.0" under
    # macOS), this is *NOT* necessarily the case (e.g., "8" rather than
    # "6.2.9200" under Windows). Hence, this is only a fallback.
    if windows.is_windows():
        kernel_version = platform.version()
    # Else, the current platform is *NOT* Windows. In this case, prefer the
    # version specifier returned by the platform.release() function to the
    # irrelevant timestamp returned by the platform.version() function.
    # Ironically, the platform.version() function does *NOT* actually return a
    # version specifier under most platforms. (Shaking my head.)
    else:
        kernel_version = platform.release()

    # Return this version.
    return kernel_version
Пример #4
0
def is_parent_command(command_basename: str) -> bool:
    '''
    ``True`` only if the parent process of the active Python interpreter is
    running an external command with the passed basename optionally suffixed by
    a platform-specific filetype (e.g., ``.exe`` under Windows).

    Parameters
    ----------
    command_basename : str
        Basename of the command to test this process' parent process against.

    Raises
    ------
    BetseOSException
        If the optional :mod:`psutil` dependency is unimportable *and* the
        current platform is neither Linux, macOS, or Windows.
    BetseFunctionUnimplementedException
        If the optional :mod:`psutil` dependency is unimportable.
    BetseProcessNotFoundException
        If either:

        * The optional :mod:`psutil` dependency is importable *and* either:

          * The current process has *no* parent process.
          * The current process had a parent process that has since died.

    Returns
    ----------
    bool
        ``True`` only if the parent process of the active Python interpreter is
        running an external command with the passed basename.
    '''

    # Avoid circular import dependencies.
    from betse.lib import libs
    from betse.util.os import oses
    from betse.util.os.brand import linux, macos, windows

    # If the optional "psutil" dependency is importable, this dependency
    # typically implements more efficient *AND* portable solutions than all
    # alternatives and is hence preferred. In this case, defer to "psutil".
    if libs.is_runtime_optional('psutil'):
        return _is_parent_command_psutil(command_basename)
    # Else if the current platform is Linux, defer to a Linux-specific tester.
    elif linux.is_linux():
        return _is_parent_command_linux(command_basename)
    # Else if the current platform is macOS, defer to a macOS-specific tester.
    elif macos.is_macos():
        return _is_parent_command_macos(command_basename)
    # Else if the current platform is Windows, defer to a Windows-specific
    # tester.
    elif windows.is_windows():
        return _is_parent_command_windows(command_basename)

    # Else, raise an exception.
    oses.die_if_unsupported()
Пример #5
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)
Пример #6
0
def _is_parent_command_psutil(command_basename: str) -> bool:
    '''
    ``True`` only if the parent process of the active Python interpreter is
    running an external command with the passed basename, implemented in terms
    of the optional :mod:`psutil` dependency.

    See Also
    ----------
    https://stackoverflow.com/a/2241047/2809027
        StackOverflow answer strongly inspiring this implementation.
    :func:`is_parent_command`
        Further details.
    '''

    # Avoid circular import dependencies.
    from betse.lib import libs
    from betse.util.os.brand import windows
    from betse.util.path import pathnames

    # Optional "psutil" dependency.
    psutil = libs.import_runtime_optional('psutil')

    # Object encapsulating the current process.
    current_proc = psutil.Process()

    # Object encapsulating the parent process of this process if any *OR*
    # "None" otherwise.
    parent_proc = current_proc.parent()

    # If the current process has *NO* parent process, raise an exception.
    #
    # In theory, all processes except the initial "init" process should
    # *ALWAYS* have a parent process on POSIX-compatible platforms. In
    # practice, "psutil" documentation explicitly states that the
    # psutil.Process.parent() method returns "None" in edge cases. *sigh*
    if parent_proc is None:
        raise BetseProcessNotFoundException(
            'Current process {} parent not found.'.format(current_proc))
    # Else, the current process has a parent process.

    # If...
    if (
        # The current platform is Windows *AND*...
        windows.is_windows() and
        # This command basename is *NOT* suffixed by a filetype...
        not pathnames.is_filetype(command_basename)
    # Then suffix this basename by ".exe". This is required, as Windows
    # executables are *ALWAYS* suffixed by such a filetype.
    ):
        command_basename += '.exe'

    # Attempt to...
    try:
        # Dictionary of all metadata required to implement this tester.
        #
        # Note that this call:
        #
        # * Efficiently retrieves this metadata with one method call rather
        #   than inefficiently distributing this retrieval across multiple
        #   method calls.
        # * Transparently sets the values of all metadata whose retrieval
        #   raises an "AccessDenied" or "ZombieProcess" exception to "None."
        #   Sadly, the "NoSuchProcess" exception is *NOT* handled similarly and
        #   *MUST* thus be explicitly caught below.
        parent_proc_metadata = parent_proc.as_dict(
            attrs=('name', 'exe', 'cmdline'))

        # List of one or more strings comprising the command line invoking
        # this parent process *OR* "None" if retrieving this metadata
        # raised an exception above.
        parent_proc_cmdline = parent_proc_metadata['cmdline']

        # If either...
        return (
            # This command basename is this parent process' name *OR*...
            #
            # Note that this metadata typically requires the least privelages
            # on most platforms *AND* is typically the most efficient such
            # metadata to access. So, this metadata is tested first.
            command_basename == parent_proc_metadata['name'] or
            # This command basename is that of this parent process *OR*...
            command_basename == pathnames.get_basename(
                parent_proc_metadata['exe']) or
            (
                # This parent process' command line was retrievable *AND*...
                parent_proc_cmdline and
                # This command basename is that of the first item of this line.
                command_basename == pathnames.get_basename(
                    parent_proc_cmdline[0])
            )
        )
    # If this parent process died during metadata access, raise an exception.
    except psutil.NoSuchProcess as exception:
        raise BetseProcessNotFoundException(
            'Current process {} parent {} killed.'.format(
                current_proc, parent_proc)
        ) from exception
Пример #7
0
def _is_headless() -> bool:
    '''
    ``True`` only if the active Python interpreter is running **headless**
    (i.e., with *no* access to a GUI display, often due to running remotely
    over an SSH-encrypted connection supporting only CLI input and output).

    Specifically, this function returns:

    * If the :func:`set_headless` function has been called at least once, the
      boolean passed to the most recent call to that function. Ergo, that
      function overrides the intelligent detection performed by this function.
    * Else, ``True`` only if none of the following conditions apply:

      * The current platform is Microsoft Windows. While a small subset of
        server-specific variants of Windows can and often are run headless
        (e.g., Windows Nano Server), there appears to be no known means of
        reliably distinguishing a headless from headfull Windows environment in
        pure Python. For safety, we assume the latter.
      * The current platform is not Microsoft Windows (and hence is
        POSIX-compatible) *and* either:

        * The ``${DISPLAY}`` environment variable required by the
          POSIX-compatible X11 display server is set under the current
          environment (typically due to being inherited from the parent
          environment). Note that *all* POSIX-compatible platforms of interest
          including macOS support this server.
        * The current platform is macOS *and* the :func:`macos.is_aqua` returns
          ``True``.
        * The current platform is Linux *and* either:

          * The ``${MIR_SOCKET}`` environment variable required by the
            Linux-specific Mir display server is set under this environment.
          * The ``${WAYLAND_DISPLAY}`` environment variable required by the
            Linux-specific Wayland display server is set under this
            environment.
    '''

    # Avoid circular import dependencies.
    from betse.util.os.brand import linux, macos, posix, windows

    # The active Python interpreter is headfull if and only if either...
    is_os_headfull = (
        # This is Windows, in which case this interpreter is usually headfull.
        # While certain server-specific variants of Windows can and often are
        # run headless (e.g., Windows Nano Server), there appears to be no
        # known means of reliably distinguishing a headless from headfull
        # Windows environment in pure Python. For safety, assume the latter.
        windows.is_windows() or

        # Else, this is a POSIX-compatible platform.
        #
        # Since all POSIX-compatible platforms of interest support the popular
        # X11 display server, detect this server first.
        posix.is_x11() or

        # Else, all possible alternative display servers specific to the
        # current platform *MUST* be iteratively tested for.
        #
        # If Linux, the only remaining display servers are Mir and Wayland.
        (linux.is_linux() and (linux.is_wayland() or linux.is_mir())) or

        # If macOS, the only remaining display server is Aqua.
        (macos.is_macos() and macos.is_aqua())

        # Else, this platform is unrecognized. For safety, this platform is
        # assumed to be headless.
    )

    # Return true only if this interpreter is *NOT* headfull.
    #
    # Note that the "is_os_headfull" boolean intentionally contains the core
    # detection logic, as detecting headfull environments is fundamentally
    # more intuitive than detecting the converse.
    return not is_os_headfull
Пример #8
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.brand import macos, posix, windows
        from betse.util.os.shell import shellenv
        from betse.util.path import dirs, pathnames

        # Absolute dirname of this directory.
        dot_dirname = None

        # If macOS, prefer a macOS-specific directory.
        if macos.is_macos():
            dot_dirname = pathnames.join(
                pathnames.get_home_dirname(),
                'Library',
                'Application Support',
                self.package_name,
            )
        # If Windows, prefer a Windows-specific directory.
        elif windows.is_windows():
            dot_dirname = pathnames.join(
                shellenv.get_var('APPDATA'), self.package_name)
        # Else...
        else:
            # If this platform is POSIX-incompatible, raise an exception.
            posix.die_unless_posix()

            # Prefer a POSIX-compatible directory.
            dot_dirname = pathnames.join(
                pathnames.get_home_dirname(), '.' + self.package_name)

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

        # Return this dirname.
        return dot_dirname