File: files.py

package info (click to toggle)
python-dynaconf 3.2.12-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,900 kB
  • sloc: python: 21,464; sh: 9; makefile: 4
file content (146 lines) | stat: -rw-r--r-- 4,211 bytes parent folder | download
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
from __future__ import annotations

import inspect
import os
import re
import sys
from glob import glob as python_glob

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.
    """
    # 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 ""

    search_tree = []
    try:
        work_dir = os.getcwd()
    except FileNotFoundError:  # pragma: no cover
        return ""
    skip_files = skip_files or []

    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}"
    )


magic_check = re.compile("([*?[])")
magic_check_bytes = re.compile(b"([*?[])")


def has_magic(s):
    """Taken from python glob module"""
    if isinstance(s, bytes):
        match = magic_check_bytes.search(s)
    else:
        match = magic_check.search(s)
    return match is not None


def glob(
    pathname,
    *,
    root_dir=None,
    dir_fd=None,
    recursive=True,
    include_hidden=True,
):
    """Redefined std glob assuming some defaults.
    and fallback for diffente python versions."""
    glob_args = {"recursive": recursive}
    if sys.version_info >= (3, 10):
        glob_args["root_dir"] = root_dir
        glob_args["dir_fd"] = dir_fd
    if sys.version_info >= (3, 11):
        glob_args["include_hidden"] = include_hidden
    return python_glob(pathname, **glob_args)