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