summaryrefslogtreecommitdiffhomepage
path: root/libs/dynaconf/utils/files.py
diff options
context:
space:
mode:
Diffstat (limited to 'libs/dynaconf/utils/files.py')
-rw-r--r--libs/dynaconf/utils/files.py112
1 files changed, 112 insertions, 0 deletions
diff --git a/libs/dynaconf/utils/files.py b/libs/dynaconf/utils/files.py
new file mode 100644
index 000000000..ec6fbd851
--- /dev/null
+++ b/libs/dynaconf/utils/files.py
@@ -0,0 +1,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}"
+ )