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