We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ENH: "local" config file upward lookup
### Bug report

**Bug summary**

Since yt 4.0 we support two locations to store a configuration file `yt.toml`, namely `$XDG_CONFIG/yt/yt.toml` (this is the global configuration) and `./yt.toml` (local)

Now, assuming a data exploration project organised into subfolders, for instance

```bash
.
├── scripts
│   ├── exp1
│   │   ├── t1.py
│   │   ├── t2.py
│   │   └── t3.py
│   └── exp2
│       ├── t1.py
│       ├── t2.py
│       └── t3.py
└── yt.toml
```

The results of any script will differ depending on wether it's launched from the top level of the project (where `yt.toml` lives) or from within their respective containing directories.

To solve this, we could implement an upward lookup routine to check for `yt.toml` files in all parents directories until it is found (or we reach root `/`).

There is a precedent to the proposed behaviour: many tools already implement this mechanism, for instance
 - flake8 
 https://github.com/PyCQA/flake8/blob/ca573a7ccf2d4a1c7df0b577bb6d3455c941e828/src/flake8/options/config.py#L17
- black https://github.com/psf/black/blob/1af29fbfa507daa8166e7aac659e9b2ff2b47a3c/src/black/files.py#L84

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `yt/utilities/configure.py`
Content:
```
1 import os
2 import sys
3 import warnings
4 from pathlib import Path
5 from typing import Callable, List
6 
7 import tomli_w
8 from more_itertools import always_iterable
9 
10 from yt.utilities.configuration_tree import ConfigLeaf, ConfigNode
11 
12 if sys.version_info >= (3, 11):
13     import tomllib
14 else:
15     import tomli as tomllib
16 
17 configuration_callbacks: List[Callable[["YTConfig"], None]] = []
18 
19 
20 def config_dir():
21     config_root = os.environ.get(
22         "XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config")
23     )
24     conf_dir = os.path.join(config_root, "yt")
25     return conf_dir
26 
27 
28 class YTConfig:
29     def __init__(self, defaults=None):
30         if defaults is None:
31             defaults = {}
32         self.config_root = ConfigNode(None)
33 
34     def get(self, section, *keys, callback=None):
35         node_or_leaf = self.config_root.get(section, *keys)
36         if isinstance(node_or_leaf, ConfigLeaf):
37             if callback is not None:
38                 return callback(node_or_leaf)
39             return node_or_leaf.value
40         return node_or_leaf
41 
42     def get_most_specific(self, section, *keys, **kwargs):
43         use_fallback = "fallback" in kwargs
44         fallback = kwargs.pop("fallback", None)
45         try:
46             return self.config_root.get_deepest_leaf(section, *keys)
47         except KeyError as err:
48             if use_fallback:
49                 return fallback
50             else:
51                 raise err
52 
53     def update(self, new_values, metadata=None):
54         if metadata is None:
55             metadata = {}
56         self.config_root.update(new_values, metadata)
57 
58     def has_section(self, section):
59         try:
60             self.config_root.get_child(section)
61             return True
62         except KeyError:
63             return False
64 
65     def add_section(self, section):
66         self.config_root.add_child(section)
67 
68     def remove_section(self, section):
69         if self.has_section(section):
70             self.config_root.remove_child(section)
71             return True
72         else:
73             return False
74 
75     def set(self, *args, metadata=None):
76         section, *keys, value = args
77         if metadata is None:
78             metadata = {"source": "runtime"}
79         self.config_root.upsert_from_list(
80             [section] + list(keys), value, extra_data=metadata
81         )
82 
83     def remove(self, *args):
84         self.config_root.pop_leaf(args)
85 
86     def read(self, file_names):
87         file_names_read = []
88         for fname in always_iterable(file_names):
89             if not os.path.exists(fname):
90                 continue
91             metadata = {"source": f"file: {fname}"}
92             try:
93                 with open(fname, "rb") as fh:
94                     data = tomllib.load(fh)
95             except tomllib.TOMLDecodeError as exc:
96                 warnings.warn(
97                     f"Could not load configuration file {fname} (invalid TOML: {exc})"
98                 )
99             else:
100                 self.update(data, metadata=metadata)
101                 file_names_read.append(fname)
102 
103         return file_names_read
104 
105     def write(self, file_handler):
106         value = self.config_root.as_dict()
107         config_as_str = tomli_w.dumps(value)
108 
109         try:
110             file_path = Path(file_handler)
111         except TypeError:
112             if not hasattr(file_handler, "write"):
113                 raise TypeError(
114                     f"Expected a path to a file, or a writable object, got {file_handler}"
115                 ) from None
116             file_handler.write(config_as_str)
117         else:
118             pdir = file_path.parent
119             if not pdir.exists():
120                 warnings.warn(f"{pdir!s} does not exist, creating it (recursively)")
121                 os.makedirs(pdir)
122             file_path.write_text(config_as_str)
123 
124     @staticmethod
125     def get_global_config_file():
126         return os.path.join(config_dir(), "yt.toml")
127 
128     @staticmethod
129     def get_local_config_file():
130         return os.path.join(os.path.abspath(os.curdir), "yt.toml")
131 
132     def __setitem__(self, args, value):
133         section, *keys = always_iterable(args)
134         self.set(section, *keys, value, metadata=None)
135 
136     def __getitem__(self, key):
137         section, *keys = always_iterable(key)
138         return self.get(section, *keys)
139 
140     def __contains__(self, item):
141         return item in self.config_root
142 
143     # Add support for IPython rich display
144     # see https://ipython.readthedocs.io/en/stable/config/integrating.html
145     def _repr_json_(self):
146         return self.config_root._repr_json_()
147 
148 
149 CONFIG = YTConfig()
150 
151 
152 def _cast_bool_helper(value):
153     if value == "True":
154         return True
155     elif value == "False":
156         return False
157     else:
158         raise ValueError("Cannot safely cast to bool")
159 
160 
161 def _expand_all(s):
162     return os.path.expandvars(os.path.expanduser(s))
163 
164 
165 def _cast_value_helper(value, types=(_cast_bool_helper, int, float, _expand_all)):
166     for t in types:
167         try:
168             retval = t(value)
169             return retval
170         except ValueError:
171             pass
172 
173 
174 def get_config(section, option):
175     *option_path, option_name = option.split(".")
176     return CONFIG.get(section, *option_path, option_name)
177 
178 
179 def set_config(section, option, value, config_file):
180     if not CONFIG.has_section(section):
181         CONFIG.add_section(section)
182 
183     option_path = option.split(".")
184     CONFIG.set(section, *option_path, _cast_value_helper(value))
185     write_config(config_file)
186 
187 
188 def write_config(config_file):
189     CONFIG.write(config_file)
190 
191 
192 def rm_config(section, option, config_file):
193     option_path = option.split(".")
194     CONFIG.remove(section, *option_path)
195     write_config(config_file)
196
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/yt/utilities/configure.py b/yt/utilities/configure.py
--- a/yt/utilities/configure.py
+++ b/yt/utilities/configure.py
@@ -127,6 +127,14 @@
 
     @staticmethod
     def get_local_config_file():
+        path = Path.cwd()
+        while path.parent is not path:
+            candidate = path.joinpath("yt.toml")
+            if candidate.is_file():
+                return os.path.abspath(candidate)
+            else:
+                path = path.parent
+
         return os.path.join(os.path.abspath(os.curdir), "yt.toml")
 
     def __setitem__(self, args, value):

{"golden_diff": "diff --git a/yt/utilities/configure.py b/yt/utilities/configure.py\n--- a/yt/utilities/configure.py\n+++ b/yt/utilities/configure.py\n@@ -127,6 +127,14 @@\n \n     @staticmethod\n     def get_local_config_file():\n+        path = Path.cwd()\n+        while path.parent is not path:\n+            candidate = path.joinpath(\"yt.toml\")\n+            if candidate.is_file():\n+                return os.path.abspath(candidate)\n+            else:\n+                path = path.parent\n+\n         return os.path.join(os.path.abspath(os.curdir), \"yt.toml\")\n \n     def __setitem__(self, args, value):\n", "issue": "ENH: \"local\" config file upward lookup\n### Bug report\r\n\r\n**Bug summary**\r\n\r\nSince yt 4.0 we support two locations to store a configuration file `yt.toml`, namely `$XDG_CONFIG/yt/yt.toml` (this is the global configuration) and `./yt.toml` (local)\r\n\r\nNow, assuming a data exploration project organised into subfolders, for instance\r\n\r\n```bash\r\n.\r\n\u251c\u2500\u2500 scripts\r\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 exp1\r\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 t1.py\r\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 t2.py\r\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 t3.py\r\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 exp2\r\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 t1.py\r\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 t2.py\r\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 t3.py\r\n\u2514\u2500\u2500 yt.toml\r\n```\r\n\r\nThe results of any script will differ depending on wether it's launched from the top level of the project (where `yt.toml` lives) or from within their respective containing directories.\r\n\r\nTo solve this, we could implement an upward lookup routine to check for `yt.toml` files in all parents directories until it is found (or we reach root `/`).\r\n\r\nThere is a precedent to the proposed behaviour: many tools already implement this mechanism, for instance\r\n - flake8 \r\n https://github.com/PyCQA/flake8/blob/ca573a7ccf2d4a1c7df0b577bb6d3455c941e828/src/flake8/options/config.py#L17\r\n- black https://github.com/psf/black/blob/1af29fbfa507daa8166e7aac659e9b2ff2b47a3c/src/black/files.py#L84\n", "before_files": [{"content": "import os\nimport sys\nimport warnings\nfrom pathlib import Path\nfrom typing import Callable, List\n\nimport tomli_w\nfrom more_itertools import always_iterable\n\nfrom yt.utilities.configuration_tree import ConfigLeaf, ConfigNode\n\nif sys.version_info >= (3, 11):\n    import tomllib\nelse:\n    import tomli as tomllib\n\nconfiguration_callbacks: List[Callable[[\"YTConfig\"], None]] = []\n\n\ndef config_dir():\n    config_root = os.environ.get(\n        \"XDG_CONFIG_HOME\", os.path.join(os.path.expanduser(\"~\"), \".config\")\n    )\n    conf_dir = os.path.join(config_root, \"yt\")\n    return conf_dir\n\n\nclass YTConfig:\n    def __init__(self, defaults=None):\n        if defaults is None:\n            defaults = {}\n        self.config_root = ConfigNode(None)\n\n    def get(self, section, *keys, callback=None):\n        node_or_leaf = self.config_root.get(section, *keys)\n        if isinstance(node_or_leaf, ConfigLeaf):\n            if callback is not None:\n                return callback(node_or_leaf)\n            return node_or_leaf.value\n        return node_or_leaf\n\n    def get_most_specific(self, section, *keys, **kwargs):\n        use_fallback = \"fallback\" in kwargs\n        fallback = kwargs.pop(\"fallback\", None)\n        try:\n            return self.config_root.get_deepest_leaf(section, *keys)\n        except KeyError as err:\n            if use_fallback:\n                return fallback\n            else:\n                raise err\n\n    def update(self, new_values, metadata=None):\n        if metadata is None:\n            metadata = {}\n        self.config_root.update(new_values, metadata)\n\n    def has_section(self, section):\n        try:\n            self.config_root.get_child(section)\n            return True\n        except KeyError:\n            return False\n\n    def add_section(self, section):\n        self.config_root.add_child(section)\n\n    def remove_section(self, section):\n        if self.has_section(section):\n            self.config_root.remove_child(section)\n            return True\n        else:\n            return False\n\n    def set(self, *args, metadata=None):\n        section, *keys, value = args\n        if metadata is None:\n            metadata = {\"source\": \"runtime\"}\n        self.config_root.upsert_from_list(\n            [section] + list(keys), value, extra_data=metadata\n        )\n\n    def remove(self, *args):\n        self.config_root.pop_leaf(args)\n\n    def read(self, file_names):\n        file_names_read = []\n        for fname in always_iterable(file_names):\n            if not os.path.exists(fname):\n                continue\n            metadata = {\"source\": f\"file: {fname}\"}\n            try:\n                with open(fname, \"rb\") as fh:\n                    data = tomllib.load(fh)\n            except tomllib.TOMLDecodeError as exc:\n                warnings.warn(\n                    f\"Could not load configuration file {fname} (invalid TOML: {exc})\"\n                )\n            else:\n                self.update(data, metadata=metadata)\n                file_names_read.append(fname)\n\n        return file_names_read\n\n    def write(self, file_handler):\n        value = self.config_root.as_dict()\n        config_as_str = tomli_w.dumps(value)\n\n        try:\n            file_path = Path(file_handler)\n        except TypeError:\n            if not hasattr(file_handler, \"write\"):\n                raise TypeError(\n                    f\"Expected a path to a file, or a writable object, got {file_handler}\"\n                ) from None\n            file_handler.write(config_as_str)\n        else:\n            pdir = file_path.parent\n            if not pdir.exists():\n                warnings.warn(f\"{pdir!s} does not exist, creating it (recursively)\")\n                os.makedirs(pdir)\n            file_path.write_text(config_as_str)\n\n    @staticmethod\n    def get_global_config_file():\n        return os.path.join(config_dir(), \"yt.toml\")\n\n    @staticmethod\n    def get_local_config_file():\n        return os.path.join(os.path.abspath(os.curdir), \"yt.toml\")\n\n    def __setitem__(self, args, value):\n        section, *keys = always_iterable(args)\n        self.set(section, *keys, value, metadata=None)\n\n    def __getitem__(self, key):\n        section, *keys = always_iterable(key)\n        return self.get(section, *keys)\n\n    def __contains__(self, item):\n        return item in self.config_root\n\n    # Add support for IPython rich display\n    # see https://ipython.readthedocs.io/en/stable/config/integrating.html\n    def _repr_json_(self):\n        return self.config_root._repr_json_()\n\n\nCONFIG = YTConfig()\n\n\ndef _cast_bool_helper(value):\n    if value == \"True\":\n        return True\n    elif value == \"False\":\n        return False\n    else:\n        raise ValueError(\"Cannot safely cast to bool\")\n\n\ndef _expand_all(s):\n    return os.path.expandvars(os.path.expanduser(s))\n\n\ndef _cast_value_helper(value, types=(_cast_bool_helper, int, float, _expand_all)):\n    for t in types:\n        try:\n            retval = t(value)\n            return retval\n        except ValueError:\n            pass\n\n\ndef get_config(section, option):\n    *option_path, option_name = option.split(\".\")\n    return CONFIG.get(section, *option_path, option_name)\n\n\ndef set_config(section, option, value, config_file):\n    if not CONFIG.has_section(section):\n        CONFIG.add_section(section)\n\n    option_path = option.split(\".\")\n    CONFIG.set(section, *option_path, _cast_value_helper(value))\n    write_config(config_file)\n\n\ndef write_config(config_file):\n    CONFIG.write(config_file)\n\n\ndef rm_config(section, option, config_file):\n    option_path = option.split(\".\")\n    CONFIG.remove(section, *option_path)\n    write_config(config_file)\n", "path": "yt/utilities/configure.py"}], "after_files": [{"content": "import os\nimport sys\nimport warnings\nfrom pathlib import Path\nfrom typing import Callable, List\n\nimport tomli_w\nfrom more_itertools import always_iterable\n\nfrom yt.utilities.configuration_tree import ConfigLeaf, ConfigNode\n\nif sys.version_info >= (3, 11):\n    import tomllib\nelse:\n    import tomli as tomllib\n\nconfiguration_callbacks: List[Callable[[\"YTConfig\"], None]] = []\n\n\ndef config_dir():\n    config_root = os.environ.get(\n        \"XDG_CONFIG_HOME\", os.path.join(os.path.expanduser(\"~\"), \".config\")\n    )\n    conf_dir = os.path.join(config_root, \"yt\")\n    return conf_dir\n\n\nclass YTConfig:\n    def __init__(self, defaults=None):\n        if defaults is None:\n            defaults = {}\n        self.config_root = ConfigNode(None)\n\n    def get(self, section, *keys, callback=None):\n        node_or_leaf = self.config_root.get(section, *keys)\n        if isinstance(node_or_leaf, ConfigLeaf):\n            if callback is not None:\n                return callback(node_or_leaf)\n            return node_or_leaf.value\n        return node_or_leaf\n\n    def get_most_specific(self, section, *keys, **kwargs):\n        use_fallback = \"fallback\" in kwargs\n        fallback = kwargs.pop(\"fallback\", None)\n        try:\n            return self.config_root.get_deepest_leaf(section, *keys)\n        except KeyError as err:\n            if use_fallback:\n                return fallback\n            else:\n                raise err\n\n    def update(self, new_values, metadata=None):\n        if metadata is None:\n            metadata = {}\n        self.config_root.update(new_values, metadata)\n\n    def has_section(self, section):\n        try:\n            self.config_root.get_child(section)\n            return True\n        except KeyError:\n            return False\n\n    def add_section(self, section):\n        self.config_root.add_child(section)\n\n    def remove_section(self, section):\n        if self.has_section(section):\n            self.config_root.remove_child(section)\n            return True\n        else:\n            return False\n\n    def set(self, *args, metadata=None):\n        section, *keys, value = args\n        if metadata is None:\n            metadata = {\"source\": \"runtime\"}\n        self.config_root.upsert_from_list(\n            [section] + list(keys), value, extra_data=metadata\n        )\n\n    def remove(self, *args):\n        self.config_root.pop_leaf(args)\n\n    def read(self, file_names):\n        file_names_read = []\n        for fname in always_iterable(file_names):\n            if not os.path.exists(fname):\n                continue\n            metadata = {\"source\": f\"file: {fname}\"}\n            try:\n                with open(fname, \"rb\") as fh:\n                    data = tomllib.load(fh)\n            except tomllib.TOMLDecodeError as exc:\n                warnings.warn(\n                    f\"Could not load configuration file {fname} (invalid TOML: {exc})\"\n                )\n            else:\n                self.update(data, metadata=metadata)\n                file_names_read.append(fname)\n\n        return file_names_read\n\n    def write(self, file_handler):\n        value = self.config_root.as_dict()\n        config_as_str = tomli_w.dumps(value)\n\n        try:\n            file_path = Path(file_handler)\n        except TypeError:\n            if not hasattr(file_handler, \"write\"):\n                raise TypeError(\n                    f\"Expected a path to a file, or a writable object, got {file_handler}\"\n                ) from None\n            file_handler.write(config_as_str)\n        else:\n            pdir = file_path.parent\n            if not pdir.exists():\n                warnings.warn(f\"{pdir!s} does not exist, creating it (recursively)\")\n                os.makedirs(pdir)\n            file_path.write_text(config_as_str)\n\n    @staticmethod\n    def get_global_config_file():\n        return os.path.join(config_dir(), \"yt.toml\")\n\n    @staticmethod\n    def get_local_config_file():\n        path = Path.cwd()\n        while path.parent is not path:\n            candidate = path.joinpath(\"yt.toml\")\n            if candidate.is_file():\n                return os.path.abspath(candidate)\n            else:\n                path = path.parent\n\n        return os.path.join(os.path.abspath(os.curdir), \"yt.toml\")\n\n    def __setitem__(self, args, value):\n        section, *keys = always_iterable(args)\n        self.set(section, *keys, value, metadata=None)\n\n    def __getitem__(self, key):\n        section, *keys = always_iterable(key)\n        return self.get(section, *keys)\n\n    def __contains__(self, item):\n        return item in self.config_root\n\n    # Add support for IPython rich display\n    # see https://ipython.readthedocs.io/en/stable/config/integrating.html\n    def _repr_json_(self):\n        return self.config_root._repr_json_()\n\n\nCONFIG = YTConfig()\n\n\ndef _cast_bool_helper(value):\n    if value == \"True\":\n        return True\n    elif value == \"False\":\n        return False\n    else:\n        raise ValueError(\"Cannot safely cast to bool\")\n\n\ndef _expand_all(s):\n    return os.path.expandvars(os.path.expanduser(s))\n\n\ndef _cast_value_helper(value, types=(_cast_bool_helper, int, float, _expand_all)):\n    for t in types:\n        try:\n            retval = t(value)\n            return retval\n        except ValueError:\n            pass\n\n\ndef get_config(section, option):\n    *option_path, option_name = option.split(\".\")\n    return CONFIG.get(section, *option_path, option_name)\n\n\ndef set_config(section, option, value, config_file):\n    if not CONFIG.has_section(section):\n        CONFIG.add_section(section)\n\n    option_path = option.split(\".\")\n    CONFIG.set(section, *option_path, _cast_value_helper(value))\n    write_config(config_file)\n\n\ndef write_config(config_file):\n    CONFIG.write(config_file)\n\n\ndef rm_config(section, option, config_file):\n    option_path = option.split(\".\")\n    CONFIG.remove(section, *option_path)\n    write_config(config_file)\n", "path": "yt/utilities/configure.py"}]}