summaryrefslogtreecommitdiffhomepage
path: root/libs/dynaconf/utils/files.py
blob: ec6fbd851334761b15c9c609853df9fdf58186bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from __future__ import annotations

import inspect
import io
import os

from dynaconf.utils import deduplicate


def _walk_to_root(path, break_at=None):
    """
    Directories starting from the given directory up to the root or break_at
    """
    if not os.path.exists(path):  # pragma: no cover
        raise OSError("Starting path not found")

    if os.path.isfile(path):  # pragma: no cover
        path = os.path.dirname(path)

    last_dir = None
    current_dir = os.path.abspath(path)
    paths = []
    while last_dir != current_dir:
        paths.append(current_dir)
        paths.append(os.path.join(current_dir, "config"))
        if break_at and current_dir == os.path.abspath(break_at):  # noqa
            break
        parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
        last_dir, current_dir = current_dir, parent_dir
    return paths


SEARCHTREE = []


def find_file(filename=".env", project_root=None, skip_files=None, **kwargs):
    """Search in increasingly higher folders for the given file
    Returns path to the file if found, or an empty string otherwise.

    This function will build a `search_tree` based on:

    - Project_root if specified
    - Invoked script location and its parents until root
    - Current working directory

    For each path in the `search_tree` it will also look for an
    additional `./config` folder.
    """
    search_tree = []
    try:
        work_dir = os.getcwd()
    except FileNotFoundError:
        return ""
    skip_files = skip_files or []

    # If filename is an absolute path and exists, just return it
    # if the absolute path does not exist, return empty string so
    # that it can be joined and avoid IoError
    if os.path.isabs(filename):
        return filename if os.path.exists(filename) else ""

    if project_root is not None:
        search_tree.extend(_walk_to_root(project_root, break_at=work_dir))

    script_dir = os.path.dirname(os.path.abspath(inspect.stack()[-1].filename))

    # Path to invoked script and recursively to root with its ./config dirs
    search_tree.extend(_walk_to_root(script_dir))

    # Path to where Python interpreter was invoked and recursively to root
    search_tree.extend(_walk_to_root(work_dir))

    # Don't look the same place twice
    search_tree = deduplicate(search_tree)

    global SEARCHTREE
    SEARCHTREE[:] = search_tree

    for dirname in search_tree:
        check_path = os.path.join(dirname, filename)
        if check_path in skip_files:
            continue
        if os.path.exists(check_path):
            return check_path  # First found will return

    # return empty string if not found so it can still be joined in os.path
    return ""


def read_file(path, **kwargs):
    content = ""
    with open(path, **kwargs) as open_file:
        content = open_file.read().strip()
    return content


def get_local_filename(filename):
    """Takes a filename like `settings.toml` and returns `settings.local.toml`

    Arguments:
        filename {str} -- The filename or complete path

    Returns:
        [str] -- The same name or path with `.local.` added.
    """
    name, _, extension = os.path.basename(str(filename)).rpartition(
        os.path.extsep
    )

    return os.path.join(
        os.path.dirname(str(filename)), f"{name}.local.{extension}"
    )