Module tf.advanced.find

Expand source code Browse git
import sys
import os
from importlib import util
import yaml

from ..parameters import (
    APP_CONFIG,
    APP_CONFIG_OLD,
    APP_DISPLAY,
    API_VERSION as avTf,
)
from ..core.helpers import console, normpath
from .helpers import getLocalDir


def findAppConfig(
    appName,
    appPath,
    commit,
    release,
    local,
    backend,
    org=None,
    repo=None,
    version=None,
    straight=False,
):
    """Find the config information of an app.

    If there is a `config.yaml` file, read it and check the compatibility
    of the config settings with the current version of Text-Fabric.

    If there is no such file but a `config.py` is present,
    conclude that this is an older app, not compatible with TF v8 or higher.

    If there are no such config files, fill in a few basic settings.

    See Also
    --------
    tf.advanced.settings: options allowed in `config.yaml`
    """

    appPath = normpath(appPath)
    configPath = f"{appPath}/{APP_CONFIG}"
    configPathOld = f"{appPath}/{APP_CONFIG_OLD}"
    cssPath = f"{appPath}/{APP_DISPLAY}"

    checkApiVersion = True

    isCompatible = None

    if os.path.exists(configPath):
        with open(configPath) as fh:
            cfg = yaml.load(fh, Loader=yaml.FullLoader)
    else:
        cfg = {}
        checkApiVersion = False
        if os.path.exists(configPathOld):
            isCompatible = False
    if straight:
        return cfg

    cfg.update(
        appName=appName, appPath=appPath, commit=commit, release=release, local=local
    )

    if version is None:
        version = cfg.setdefault("provenanceSpec", {}).get("version", None)
    else:
        cfg.setdefault("provenanceSpec", {})["version"] = version

    if org is None:
        org = cfg.get("provenanceSpec", {}).get("org", None)
    else:
        cfg["provenanceSpec"]["org"] = org

    if repo is None:
        repo = cfg.get("provenanceSpec", {}).get("repo", None)
    else:
        cfg["provenanceSpec"]["repo"] = repo

    if os.path.exists(cssPath):
        with open(cssPath, encoding="utf8") as fh:
            cfg["css"] = fh.read()
    else:
        cfg["css"] = ""

    cfg["local"] = local
    cfg["localDir"] = getLocalDir(backend, cfg, local, version)

    avA = cfg.get("apiVersion", None)
    if isCompatible is None and checkApiVersion:
        isCompatible = avA is not None and avA == avTf
    if not isCompatible:
        if isCompatible is None:
            pass
        elif avA is None or avA < avTf:
            console(
                f"""
App `{appName}` requires API version {avA or 0} but Text-Fabric provides {avTf}.
Your copy of the TF app `{appName}` is outdated for this version of TF.
Recommendation: obtain a newer version of `{appName}`.
Hint: load the app in one of the following ways:

    {org}/{repo}
    {org}/{repo}:latest
    {org}/{repo}:hot

    For example:

    The Text-Fabric browser:

        text-fabric {org}/{repo}:latest

    In a program/notebook:

        A = use('{org}/{repo}:latest', hoist=globals())

""",
                error=True,
            )
        else:
            console(
                f"""
App `{appName}` or rather `{org}/{repo}` requires API version {avA or 0}
but Text-Fabric provides {avTf}.
Your Text-Fabric is outdated and cannot use this version of the TF app `{org}/{repo}`.
Recommendation: upgrade Text-Fabric.
Hint:

    pip install --upgrade text-fabric

""",
                error=True,
            )

    cfg["isCompatible"] = isCompatible
    return cfg


def findAppClass(appName, appPath):
    """Find the class definition of an app.

    The file `app.py` in the app directory will be looked up,
    if it exists, it will be loaded as a Python module, and from
    this module we try to get the class `TfApp`.

    Returns
    -------
    class | None
        If `TfApp` can be found and imported, it is the result.
        Otherwise we return `None`.
    """

    appPath = normpath(appPath)
    appClass = None
    moduleName = f"tf.apps.{appName}.app"
    filePath = f"{appPath}/app.py"
    if not os.path.exists(filePath):
        return None

    try:
        spec = util.spec_from_file_location(moduleName, f"{appPath}/app.py")
        code = util.module_from_spec(spec)
        sys.path.insert(0, appPath)
        spec.loader.exec_module(code)
        sys.path.pop(0)
        appClass = code.TfApp
    except Exception as e:
        console(f"findAppClass: {str(e)}", error=True)
        console(f'findAppClass: Api for "{appName}" not loaded')
        appClass = None
    return appClass


def loadModule(moduleName, *args):
    """Load a module dynamically, by name.

    Parameters
    ----------
    moduleName: string
        Name of a module under a TF-app that needs to be imported.
    args: mixed
        The same list of arguments that is passed to `tf.advanced.app.App`
        of which only the `appName` and the `appPath` are used.
    """

    (appName, appPath) = args[1:3]
    appPath = normpath(appPath)
    try:
        spec = util.spec_from_file_location(
            f"tf.apps.{appName}.{moduleName}",
            f"{appPath}/{moduleName}.py",
        )
        module = util.module_from_spec(spec)
        spec.loader.exec_module(module)
    except Exception as e:
        console(f"loadModule: {str(e)}", error=True)
        console(f'loadModule: {moduleName} in "{appName}" not found')
    return module

Functions

def findAppClass(appName, appPath)

Find the class definition of an app.

The file app.py in the app directory will be looked up, if it exists, it will be loaded as a Python module, and from this module we try to get the class TfApp.

Returns

class | None
If TfApp can be found and imported, it is the result. Otherwise we return None.
Expand source code Browse git
def findAppClass(appName, appPath):
    """Find the class definition of an app.

    The file `app.py` in the app directory will be looked up,
    if it exists, it will be loaded as a Python module, and from
    this module we try to get the class `TfApp`.

    Returns
    -------
    class | None
        If `TfApp` can be found and imported, it is the result.
        Otherwise we return `None`.
    """

    appPath = normpath(appPath)
    appClass = None
    moduleName = f"tf.apps.{appName}.app"
    filePath = f"{appPath}/app.py"
    if not os.path.exists(filePath):
        return None

    try:
        spec = util.spec_from_file_location(moduleName, f"{appPath}/app.py")
        code = util.module_from_spec(spec)
        sys.path.insert(0, appPath)
        spec.loader.exec_module(code)
        sys.path.pop(0)
        appClass = code.TfApp
    except Exception as e:
        console(f"findAppClass: {str(e)}", error=True)
        console(f'findAppClass: Api for "{appName}" not loaded')
        appClass = None
    return appClass
def findAppConfig(appName, appPath, commit, release, local, backend, org=None, repo=None, version=None, straight=False)

Find the config information of an app.

If there is a config.yaml file, read it and check the compatibility of the config settings with the current version of Text-Fabric.

If there is no such file but a config.py is present, conclude that this is an older app, not compatible with TF v8 or higher.

If there are no such config files, fill in a few basic settings.

See Also

tf.advanced.settings
options allowed in config.yaml
Expand source code Browse git
def findAppConfig(
    appName,
    appPath,
    commit,
    release,
    local,
    backend,
    org=None,
    repo=None,
    version=None,
    straight=False,
):
    """Find the config information of an app.

    If there is a `config.yaml` file, read it and check the compatibility
    of the config settings with the current version of Text-Fabric.

    If there is no such file but a `config.py` is present,
    conclude that this is an older app, not compatible with TF v8 or higher.

    If there are no such config files, fill in a few basic settings.

    See Also
    --------
    tf.advanced.settings: options allowed in `config.yaml`
    """

    appPath = normpath(appPath)
    configPath = f"{appPath}/{APP_CONFIG}"
    configPathOld = f"{appPath}/{APP_CONFIG_OLD}"
    cssPath = f"{appPath}/{APP_DISPLAY}"

    checkApiVersion = True

    isCompatible = None

    if os.path.exists(configPath):
        with open(configPath) as fh:
            cfg = yaml.load(fh, Loader=yaml.FullLoader)
    else:
        cfg = {}
        checkApiVersion = False
        if os.path.exists(configPathOld):
            isCompatible = False
    if straight:
        return cfg

    cfg.update(
        appName=appName, appPath=appPath, commit=commit, release=release, local=local
    )

    if version is None:
        version = cfg.setdefault("provenanceSpec", {}).get("version", None)
    else:
        cfg.setdefault("provenanceSpec", {})["version"] = version

    if org is None:
        org = cfg.get("provenanceSpec", {}).get("org", None)
    else:
        cfg["provenanceSpec"]["org"] = org

    if repo is None:
        repo = cfg.get("provenanceSpec", {}).get("repo", None)
    else:
        cfg["provenanceSpec"]["repo"] = repo

    if os.path.exists(cssPath):
        with open(cssPath, encoding="utf8") as fh:
            cfg["css"] = fh.read()
    else:
        cfg["css"] = ""

    cfg["local"] = local
    cfg["localDir"] = getLocalDir(backend, cfg, local, version)

    avA = cfg.get("apiVersion", None)
    if isCompatible is None and checkApiVersion:
        isCompatible = avA is not None and avA == avTf
    if not isCompatible:
        if isCompatible is None:
            pass
        elif avA is None or avA < avTf:
            console(
                f"""
App `{appName}` requires API version {avA or 0} but Text-Fabric provides {avTf}.
Your copy of the TF app `{appName}` is outdated for this version of TF.
Recommendation: obtain a newer version of `{appName}`.
Hint: load the app in one of the following ways:

    {org}/{repo}
    {org}/{repo}:latest
    {org}/{repo}:hot

    For example:

    The Text-Fabric browser:

        text-fabric {org}/{repo}:latest

    In a program/notebook:

        A = use('{org}/{repo}:latest', hoist=globals())

""",
                error=True,
            )
        else:
            console(
                f"""
App `{appName}` or rather `{org}/{repo}` requires API version {avA or 0}
but Text-Fabric provides {avTf}.
Your Text-Fabric is outdated and cannot use this version of the TF app `{org}/{repo}`.
Recommendation: upgrade Text-Fabric.
Hint:

    pip install --upgrade text-fabric

""",
                error=True,
            )

    cfg["isCompatible"] = isCompatible
    return cfg
def loadModule(moduleName, *args)

Load a module dynamically, by name.

Parameters

moduleName : string
Name of a module under a TF-app that needs to be imported.
args : mixed
The same list of arguments that is passed to App of which only the appName and the appPath are used.
Expand source code Browse git
def loadModule(moduleName, *args):
    """Load a module dynamically, by name.

    Parameters
    ----------
    moduleName: string
        Name of a module under a TF-app that needs to be imported.
    args: mixed
        The same list of arguments that is passed to `tf.advanced.app.App`
        of which only the `appName` and the `appPath` are used.
    """

    (appName, appPath) = args[1:3]
    appPath = normpath(appPath)
    try:
        spec = util.spec_from_file_location(
            f"tf.apps.{appName}.{moduleName}",
            f"{appPath}/{moduleName}.py",
        )
        module = util.module_from_spec(spec)
        spec.loader.exec_module(module)
    except Exception as e:
        console(f"loadModule: {str(e)}", error=True)
        console(f'loadModule: {moduleName} in "{appName}" not found')
    return module