Skip to content

Helpers

This is a series of helper functions that could be useful for you.

Notably, the get_db and get_elastic_client could help with writing an archivy plugin.

config_diff(curr_key, curr_val, parent_dict, defaults)

This function diffs the user config with the defaults to only save what is actually different.

Returns 1 if the current element or its nested elements are different and have been preserved.

Source code in archivy/helpers.py
def config_diff(curr_key, curr_val, parent_dict, defaults):
    """
    This function diffs the user config with the defaults to only save what is actually different.

    Returns 1 if the current element or its nested elements are different and have been preserved.
    """
    if type(curr_val) is dict:
        # the any call here diffs all nested children of the current dict and returns whether any have modifications
        if not any(
            [
                config_diff(k, v, curr_val, defaults[curr_key])
                for k, v in list(curr_val.items())
            ]
        ):
            parent_dict.pop(curr_key)
            return 0
    else:
        if defaults[curr_key] == curr_val:
            parent_dict.pop(curr_key)
            return 0
    return 1

create_plugin_dir(name)

Creates a sample plugin directory

Source code in archivy/helpers.py
def create_plugin_dir(name):
    """Creates a sample plugin directory"""
    raw_name = name.replace("archivy_", "").replace("archivy-", "")
    try:
        os.makedirs(f"{name}/{name}")

        # Creates requirements.txt.
        with open(f"{name}/requirements.txt", "w") as fp:
            fp.writelines(["archivy", "\nclick"])

        # Creates an empty readme file to be filled
        with open(f"{name}/README.md", "w+") as fp:
            fp.writelines(
                [
                    f"# {name}",
                    "\n\n## Install",
                    "\n\nYou need to have `archivy` already installed.",
                    f"\n\nRun `pip install archivy_{name}`",
                    "\n\n## Usage",
                ]
            )

        # Creates a setup.py file
        with open(f"{name}/setup.py", "w") as setup_f:
            setup_f.writelines(
                [
                    "from setuptools import setup, find_packages",
                    '\n\nwith open("README.md", "r") as fh:',
                    "\n\tlong_description = fh.read()",
                    '\n\nwith open("requirements.txt", encoding="utf-8") as f:',
                    '\n\tall_reqs = f.read().split("\\n")',
                    "\n\tinstall_requires = [x.strip() for x in all_reqs]",
                    "\n\n#Fill in the details below for distribution purposes"
                    f'\nsetup(\n\tname="{name}",',
                    '\n\tversion="0.0.1",',
                    '\n\tauthor="",',
                    '\n\tauthor_email="",',
                    '\n\tdescription="",',
                    "\n\tlong_description=long_description,",
                    '\n\tlong_description_content_type="text/markdown",',
                    '\n\tclassifiers=["Programming Language :: Python :: 3"],'
                    "\n\tpackages=find_packages(),",
                    "\n\tinstall_requires=install_requires,",
                    f'\n\tentry_points="""\n\t\t[archivy.plugins]'
                    f'\n\t\t{raw_name}={name}:{raw_name}"""\n)',
                ]
            )

        # Creating a basic __init__.py file where the main function of the plugin goes
        with open(f"{name}/{name}/__init__.py", "w") as fp:
            fp.writelines(
                [
                    "import archivy",
                    "\nimport click",
                    "\n\n# Fill in the functionality for the commands (see https://archivy.github.io/plugins/)",
                    "\n@click.group()",
                    f"\ndef {raw_name}():",
                    "\n\tpass",
                    f"\n\n@{raw_name}.command()",
                    "\ndef command1():",
                    "\n\tpass",
                    f"\n\n@{raw_name}.command()",
                    "\ndef command2():",
                    "\n\tpass",
                ]
            )

        return True
    except FileExistsError:
        return False

get_db(force_reconnect=False)

Returns the database object that you can use to store data persistently

Source code in archivy/helpers.py
def get_db(force_reconnect=False):
    """
    Returns the database object that you can use to
    store data persistently
    """
    if "db" not in g or force_reconnect:
        g.db = TinyDB(str(Path(current_app.config["INTERNAL_DIR"]) / "db.json"))

    return g.db

get_elastic_client(error_if_invalid=True)

Returns the elasticsearch client you can use to search and insert / delete data

Source code in archivy/helpers.py
def get_elastic_client(error_if_invalid=True):
    """Returns the elasticsearch client you can use to search and insert / delete data"""
    if (
        not current_app.config["SEARCH_CONF"]["enabled"]
        or current_app.config["SEARCH_CONF"]["engine"] != "elasticsearch"
    ) and error_if_invalid:
        return None

    auth_setup = (
        current_app.config["SEARCH_CONF"]["es_user"]
        and current_app.config["SEARCH_CONF"]["es_password"]
    )
    if auth_setup:
        es = Elasticsearch(
            current_app.config["SEARCH_CONF"]["url"],
            http_auth=(
                current_app.config["SEARCH_CONF"]["es_user"],
                current_app.config["SEARCH_CONF"]["es_password"],
            ),
        )
    else:
        es = Elasticsearch(current_app.config["SEARCH_CONF"]["url"])
    if error_if_invalid:
        test_es_connection(es)
    else:
        try:
            es.cluster.health()
        except elasticsearch.exceptions.ConnectionError:
            return False
    return es

get_max_id()

Returns the current maximum id of dataobjs in the database.

Source code in archivy/helpers.py
def get_max_id():
    """Returns the current maximum id of dataobjs in the database."""
    db = get_db()
    max_id = db.search(Query().name == "max_id")
    if not max_id:
        db.insert({"name": "max_id", "val": 0})
        return 0
    return max_id[0]["val"]

load_config(path='')

Loads config.yml file safely and deserializes it to a python dict.

Source code in archivy/helpers.py
def load_config(path=""):
    """Loads `config.yml` file safely and deserializes it to a python dict."""
    path = path or current_app.config["INTERNAL_DIR"]
    with (Path(path) / "config.yml").open() as f:
        return yaml.load(f.read(), Loader=yaml.SafeLoader)

set_max_id(val)

Sets a new max_id

Source code in archivy/helpers.py
def set_max_id(val):
    """Sets a new max_id"""
    db = get_db()
    db.update(operations.set("val", val), Query().name == "max_id")

test_es_connection(es)

Tests health and presence of connection to elasticsearch.

Source code in archivy/helpers.py
def test_es_connection(es):
    """Tests health and presence of connection to elasticsearch."""
    try:
        health = es.cluster.health()
    except elasticsearch.exceptions.ConnectionError:
        current_app.logger.error(
            "Elasticsearch does not seem to be running on "
            f"{current_app.config['SEARCH_CONF']['url']}. Please start "
            "it, for example with: sudo service elasticsearch restart"
        )
        current_app.logger.error(
            "You can disable Elasticsearch by modifying the `enabled` variable "
            f"in {str(Path(current_app.config['INTERNAL_DIR']) / 'config.yml')}"
        )
        sys.exit(1)

    if health["status"] not in ("yellow", "green"):
        current_app.logger.warning(
            "Elasticsearch reports that it is not working "
            "properly. Search might not work. You can disable "
            "Elasticsearch by setting ELASTICSEARCH_ENABLED to 0."
        )

write_config(config)

Writes a new config dict to a config.yml file that will override defaults. Compares user config with defaults to only save changes.

Source code in archivy/helpers.py
def write_config(config: dict):
    """
    Writes a new config dict to a `config.yml` file that will override defaults.
    Compares user config with defaults to only save changes.
    """
    defaults = vars(Config())
    for k, v in list(config.items()):
        if k != "SECRET_KEY":
            config_diff(k, v, config, defaults)
    with (Path(current_app.config["INTERNAL_DIR"]) / "config.yml").open("w") as f:
        yaml.dump(config, f)