yi-config-starter 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 <Yunus Indori>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: yi-config-starter
3
+ Version: 0.1.3
4
+ Summary: Config starter for my local projects
5
+ Author: Yunus Indori
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 <Yunus Indori>
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Requires-Python: >=3.9
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: PyYAML>=6.0
32
+ Dynamic: license-file
33
+
34
+ # yi-config-starter
35
+
36
+ A lightweight YAML configuration loader with automatic config discovery, placeholder substitution, and a thread-safe singleton interface.
37
+
38
+ This library is intended to centralize configuration loading logic while keeping application code clean and portable across environments.
39
+
40
+ ---
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install yi-config-starter
46
+ ````
47
+
48
+ ---
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from yi_config_starter import ApplicationConfiguration
54
+
55
+ cfg = ApplicationConfiguration.get_instance()
56
+ config = cfg.get_config()
57
+
58
+ db_host = cfg.get_config_value("db.datasource.config.host")
59
+ print(db_host)
60
+ ```
61
+
62
+ You may also initialize it explicitly (still resolves to a singleton internally):
63
+
64
+ ```python
65
+ from yi_config_starter import ApplicationConfiguration
66
+ cfg = ApplicationConfiguration(
67
+ filename="config.yml",
68
+ env_var="MY_APP_CONFIG",
69
+ app_name="my-app",
70
+ # path="/absolute/or/~/path/to/config.yml",
71
+ # extra_placeholders={"PROJECT_ROOT": "/some/path"},
72
+ )
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Configuration File Discovery
78
+
79
+ By default, the loader searches for `config.yml` in the following order:
80
+
81
+ 1. Explicit path passed to the constructor (`path=...`)
82
+ 2. Path provided via environment variable
83
+ 3. Current working directory, then parent directories (walking upward)
84
+ 4. User configuration directory
85
+
86
+ * Windows: `%APPDATA%\<app_name>\config.yml`
87
+ * Linux/macOS: `~/.config/<app_name>/config.yml`
88
+ 5. Entries on `sys.path` (recursive search)
89
+
90
+ If no configuration file is found, a `FileNotFoundError` is raised.
91
+
92
+ ---
93
+
94
+ ## Placeholder Substitution
95
+
96
+ Before parsing YAML, placeholders in the configuration file are substituted.
97
+
98
+ ### Built-in Placeholders
99
+
100
+ | Placeholder | Description |
101
+ | ------------------ | ---------------------------------------------- |
102
+ | `{{HOME}}` | User home directory |
103
+ | `{{separator}}` | OS path separator (`/` or `\`) |
104
+ | `{{APPDATA}}` | Windows `%APPDATA%` or fallback to `~/.config` |
105
+ | `{{XDG_CONFIG}}` | `$XDG_CONFIG_HOME` or `~/.config` |
106
+ | `{{ENV:VAR_NAME}}` | Value of environment variable `VAR_NAME` |
107
+
108
+ Custom placeholders may be supplied via `extra_placeholders`.
109
+
110
+ ### Example
111
+
112
+ ```yaml
113
+ paths:
114
+ data_dir: "{{HOME}}{{separator}}data"
115
+ cache_dir: "{{XDG_CONFIG}}{{separator}}my-app{{separator}}cache"
116
+ api_key: "{{ENV:MY_API_KEY}}"
117
+ ```
118
+
119
+ ---
120
+
121
+ ## API Reference
122
+
123
+ ### `ApplicationConfiguration.get_instance(...)`
124
+
125
+ Returns the singleton configuration instance (thread-safe).
126
+
127
+ ---
128
+
129
+ ### `cfg.get_config() -> dict`
130
+
131
+ Returns the entire configuration as a dictionary.
132
+
133
+ ---
134
+
135
+ ### `cfg.get_config_value("a.b.c")`
136
+
137
+ Returns a nested configuration value using dot-separated keys.
138
+
139
+ ---
140
+
141
+ ### `ApplicationConfiguration.get_value_from_config(config: dict, key: str)`
142
+
143
+ Static helper for retrieving nested values from an arbitrary dictionary.
144
+
145
+ ---
146
+
147
+ ## Versioning
148
+
149
+ The package version is resolved dynamically from:
150
+
151
+ ```
152
+ import yi_config_starter
153
+ yi_config_starter.__version__
154
+ ```
155
+
156
+ Defined in:
157
+
158
+ ```
159
+ /yi_config_starter/__init__.py
160
+ ```
161
+
162
+ ---
163
+
164
+ ## License
165
+
166
+ MIT License — see the `LICENSE` file.
167
+
@@ -0,0 +1,134 @@
1
+ # yi-config-starter
2
+
3
+ A lightweight YAML configuration loader with automatic config discovery, placeholder substitution, and a thread-safe singleton interface.
4
+
5
+ This library is intended to centralize configuration loading logic while keeping application code clean and portable across environments.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install yi-config-starter
13
+ ````
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from yi_config_starter import ApplicationConfiguration
21
+
22
+ cfg = ApplicationConfiguration.get_instance()
23
+ config = cfg.get_config()
24
+
25
+ db_host = cfg.get_config_value("db.datasource.config.host")
26
+ print(db_host)
27
+ ```
28
+
29
+ You may also initialize it explicitly (still resolves to a singleton internally):
30
+
31
+ ```python
32
+ from yi_config_starter import ApplicationConfiguration
33
+ cfg = ApplicationConfiguration(
34
+ filename="config.yml",
35
+ env_var="MY_APP_CONFIG",
36
+ app_name="my-app",
37
+ # path="/absolute/or/~/path/to/config.yml",
38
+ # extra_placeholders={"PROJECT_ROOT": "/some/path"},
39
+ )
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Configuration File Discovery
45
+
46
+ By default, the loader searches for `config.yml` in the following order:
47
+
48
+ 1. Explicit path passed to the constructor (`path=...`)
49
+ 2. Path provided via environment variable
50
+ 3. Current working directory, then parent directories (walking upward)
51
+ 4. User configuration directory
52
+
53
+ * Windows: `%APPDATA%\<app_name>\config.yml`
54
+ * Linux/macOS: `~/.config/<app_name>/config.yml`
55
+ 5. Entries on `sys.path` (recursive search)
56
+
57
+ If no configuration file is found, a `FileNotFoundError` is raised.
58
+
59
+ ---
60
+
61
+ ## Placeholder Substitution
62
+
63
+ Before parsing YAML, placeholders in the configuration file are substituted.
64
+
65
+ ### Built-in Placeholders
66
+
67
+ | Placeholder | Description |
68
+ | ------------------ | ---------------------------------------------- |
69
+ | `{{HOME}}` | User home directory |
70
+ | `{{separator}}` | OS path separator (`/` or `\`) |
71
+ | `{{APPDATA}}` | Windows `%APPDATA%` or fallback to `~/.config` |
72
+ | `{{XDG_CONFIG}}` | `$XDG_CONFIG_HOME` or `~/.config` |
73
+ | `{{ENV:VAR_NAME}}` | Value of environment variable `VAR_NAME` |
74
+
75
+ Custom placeholders may be supplied via `extra_placeholders`.
76
+
77
+ ### Example
78
+
79
+ ```yaml
80
+ paths:
81
+ data_dir: "{{HOME}}{{separator}}data"
82
+ cache_dir: "{{XDG_CONFIG}}{{separator}}my-app{{separator}}cache"
83
+ api_key: "{{ENV:MY_API_KEY}}"
84
+ ```
85
+
86
+ ---
87
+
88
+ ## API Reference
89
+
90
+ ### `ApplicationConfiguration.get_instance(...)`
91
+
92
+ Returns the singleton configuration instance (thread-safe).
93
+
94
+ ---
95
+
96
+ ### `cfg.get_config() -> dict`
97
+
98
+ Returns the entire configuration as a dictionary.
99
+
100
+ ---
101
+
102
+ ### `cfg.get_config_value("a.b.c")`
103
+
104
+ Returns a nested configuration value using dot-separated keys.
105
+
106
+ ---
107
+
108
+ ### `ApplicationConfiguration.get_value_from_config(config: dict, key: str)`
109
+
110
+ Static helper for retrieving nested values from an arbitrary dictionary.
111
+
112
+ ---
113
+
114
+ ## Versioning
115
+
116
+ The package version is resolved dynamically from:
117
+
118
+ ```
119
+ import yi_config_starter
120
+ yi_config_starter.__version__
121
+ ```
122
+
123
+ Defined in:
124
+
125
+ ```
126
+ /yi_config_starter/__init__.py
127
+ ```
128
+
129
+ ---
130
+
131
+ ## License
132
+
133
+ MIT License — see the `LICENSE` file.
134
+
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "yi-config-starter"
7
+ dynamic=["version"]
8
+ description = "Config starter for my local projects"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Yunus Indori" }]
13
+
14
+ # This replaces install_requires from setup.py
15
+ dependencies = [
16
+ "PyYAML>=6.0"
17
+ ]
18
+
19
+ [tool.setuptools.packages.find]
20
+ where = ["."]
21
+ include = ["yi_config_starter*"]
22
+ exclude = ["tests", "build", "dist"]
23
+
24
+ [tool.setuptools.dynamic]
25
+ version = {attr = "yi_config_starter.__init__.__version__"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ import logging
2
+
3
+ from yi_config_starter.config_starter import ApplicationConfiguration
4
+
5
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
6
+
7
+ __version__ = "0.1.3"
8
+ __all__ = ["ApplicationConfiguration"]
@@ -0,0 +1,254 @@
1
+ """
2
+ Startup myutils
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import logging
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+ from threading import RLock
11
+ from typing import Optional, Tuple, Dict
12
+
13
+ import yaml # pip install pyyaml
14
+
15
+
16
+ class ApplicationConfiguration:
17
+ """
18
+ ApplicationConfiguration: Singleton class, use constructor or static method get_instance.
19
+ Pass environment='prod', 'real' or 'production' for loading prod environment config, else sandbox config will be
20
+ loaded.
21
+ """
22
+ __instance = None
23
+ __lock = RLock()
24
+
25
+ # noinspection PyUnusedLocal
26
+ def __init__(self, *args, **kwargs):
27
+ if not hasattr(ApplicationConfiguration.__instance, 'inited'):
28
+ self.inited = True
29
+ self._logger = logging.getLogger(__name__)
30
+ self.__config_dict = {}
31
+ config_params = {}
32
+ if kwargs.get('filename'):
33
+ config_params['filename'] = kwargs.get('filename')
34
+ if kwargs.get('env_var'):
35
+ config_params['env_var'] = kwargs.get('env_var')
36
+ if kwargs.get('path'):
37
+ config_params['path'] = kwargs.get('path')
38
+ if kwargs.get('app_name'):
39
+ config_params['app_name'] = kwargs.get('app_name')
40
+ if kwargs.get('extra_placeholders'):
41
+ config_params['extra_placeholders'] = kwargs.get('extra_placeholders')
42
+ self.config_path, self.__config = self.__find_config(**config_params)
43
+
44
+ def __new__(cls, *args, **kwargs):
45
+ if not cls.__instance:
46
+ cls.__lock.acquire()
47
+ if not cls.__instance:
48
+ cls.__instance = super().__new__(cls)
49
+ return cls.__instance
50
+
51
+ @staticmethod
52
+ def get_placeholder_values():
53
+ """
54
+ Utility value for replacing values in config.yml template
55
+ @return:
56
+ """
57
+ return {
58
+ 'HOME': os.path.expanduser("~"),
59
+ 'separator': os.path.sep,
60
+ }
61
+
62
+ @staticmethod
63
+ def get_instance(*args, **kwargs):
64
+ """
65
+ Get singleton instance of this class
66
+
67
+ @param args:
68
+ @param kwargs:
69
+ @return:
70
+ """
71
+ if not ApplicationConfiguration.__instance:
72
+ ApplicationConfiguration.__lock.acquire()
73
+ if not ApplicationConfiguration.__instance:
74
+ ApplicationConfiguration.__instance = ApplicationConfiguration(*args, **kwargs)
75
+ return ApplicationConfiguration.__instance
76
+
77
+ def __find_config(
78
+ self,
79
+ *,
80
+ filename: str = "config.yml",
81
+ env_var: str = "TRADIER_CONFIG",
82
+ path: Optional[str | Path] = None,
83
+ app_name: str = "tradier",
84
+ extra_placeholders: Optional[Dict[str, str]] = None,
85
+ ) -> Tuple[Path, dict]:
86
+ """
87
+ Find and load the first config YAML from these locations (in order):
88
+ 1) explicit `path=`
89
+ 2) env var path `env_var`
90
+ 3) cwd -> parents (filename)
91
+ 4) user config (~/.config/<app_name>/<filename> or %APPDATA%\\<app_name>\\)
92
+ 5) anywhere on sys.path (recursive)
93
+ Returns (path, data_dict). Preprocesses {{TOKENS}} before parsing.
94
+ """
95
+ # 1) explicit path
96
+ if path:
97
+ p = Path(path).expanduser()
98
+ if not p.is_file():
99
+ raise FileNotFoundError(f"Config not found at explicit path: {p}")
100
+ return p, self._read_yaml(p, extra_placeholders)
101
+
102
+ # 2) env var path
103
+ env_val = os.getenv(env_var)
104
+ if env_val:
105
+ p = Path(env_val).expanduser()
106
+ if p.is_file():
107
+ return p, self._read_yaml(p, extra_placeholders)
108
+
109
+ # 3) cwd -> parents
110
+ p = self._walk_up_for_file(Path.cwd(), filename)
111
+ if p:
112
+ return p, self._read_yaml(p, extra_placeholders)
113
+
114
+ # 4) user config dir
115
+ uc = self._user_config_candidate(app_name) / filename
116
+ if uc.is_file():
117
+ return uc, self._read_yaml(uc, extra_placeholders)
118
+
119
+ # 5) sys.path
120
+ sp = self._search_sys_path(filename)
121
+ if sp:
122
+ return sp, self._read_yaml(sp, extra_placeholders)
123
+
124
+ raise FileNotFoundError(
125
+ f"Could not find {filename!r}. Set {env_var} to a file path or pass `path=`."
126
+ )
127
+
128
+ # noinspection PyMethodMayBeStatic
129
+ def _search_sys_path(self, filename: str) -> Optional[Path]:
130
+ filename = Path(filename).name
131
+ for entry in sys.path:
132
+ if not entry:
133
+ continue
134
+ p = Path(entry)
135
+ if p.is_dir():
136
+ cand = p / filename
137
+ if cand.is_file():
138
+ return cand
139
+ for root, _, files in os.walk(p):
140
+ if filename in files:
141
+ return Path(root) / filename
142
+ return None
143
+
144
+ # noinspection PyMethodMayBeStatic
145
+ def _user_config_candidate(self, app_name: str) -> Path:
146
+ if os.name == "nt":
147
+ base = os.environ.get("APPDATA") or (Path.home() / "AppData" / "Roaming")
148
+ return Path(base) / app_name
149
+ return Path.home() / ".config" / app_name
150
+
151
+ # noinspection PyMethodMayBeStatic
152
+ def _walk_up_for_file(self, start: Path, filename: str) -> Optional[Path]:
153
+ start = start.resolve()
154
+ for p in (start, *start.parents):
155
+ cand = p / filename
156
+ if cand.is_file():
157
+ return cand
158
+ return None
159
+
160
+ # noinspection PyMethodMayBeStatic
161
+ def _preprocess_yaml_text(self, text: str, extra_placeholders: Optional[Dict[str, str]] = None) -> str:
162
+ """
163
+ Replace {{PLACEHOLDER}} tokens with platform-specific values *before* YAML parsing.
164
+ Built-ins:
165
+ {{HOME}} -> str(Path.home())
166
+ {{separator}} -> os.sep
167
+ {{APPDATA}} -> Windows %APPDATA% or "~/.config" fallback
168
+ {{XDG_CONFIG}} -> $XDG_CONFIG_HOME or "~/.config"
169
+ {{ENV:VAR_NAME}} -> value of environment variable VAR_NAME (empty if missing)
170
+ You can pass extra_placeholders to override or add tokens.
171
+ """
172
+ # base mapping
173
+ appdata = os.environ.get("APPDATA") or str(Path.home() / ".config")
174
+ xdg_config = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
175
+ mapping = {
176
+ "HOME": str(Path.home()),
177
+ "separator": os.sep,
178
+ "APPDATA": appdata,
179
+ "XDG_CONFIG": xdg_config,
180
+ }
181
+ if extra_placeholders:
182
+ mapping.update(extra_placeholders)
183
+
184
+ # Fast path if no tokens at all
185
+ if "{{" not in text:
186
+ return text
187
+
188
+ # Replace simple {{KEY}} tokens
189
+ for k, v in mapping.items():
190
+ text = text.replace(f"{{{{{k}}}}}", v)
191
+
192
+ # Replace {{ENV:NAME}} tokens
193
+ # Simple scan to avoid regex: find all occurrences of {{ENV:...}}
194
+ start = 0
195
+ while True:
196
+ i = text.find("{{ENV:", start)
197
+ if i == -1:
198
+ break
199
+ j = text.find("}}", i + 6)
200
+ if j == -1:
201
+ break # unmatched; let YAML error out naturally
202
+ env_key = text[i + 6: j].strip() # after "ENV:"
203
+ env_val = os.environ.get(env_key, "")
204
+ text = text[:i] + env_val + text[j + 2:]
205
+ start = i + len(env_val)
206
+
207
+ return text
208
+
209
+ def _read_yaml(self, path: Path, extra_placeholders: Optional[Dict[str, str]] = None) -> dict:
210
+ raw = path.read_text(encoding="utf-8")
211
+ pre = self._preprocess_yaml_text(raw, extra_placeholders)
212
+ data = yaml.safe_load(pre) or {}
213
+ if not isinstance(data, dict):
214
+ raise ValueError(f"YAML at {path} is not a mapping/object")
215
+ return data
216
+
217
+ def get_config(self):
218
+ """
219
+ Get the global config
220
+
221
+ @return:
222
+ """
223
+ return self.__config
224
+
225
+ def get_config_value(self, key: str):
226
+ """
227
+ Pass in a dot (.) separated string and this will fetch the property value
228
+ """
229
+ keys = key.split('.')
230
+ last_val = self.__config
231
+ for k in keys:
232
+ try:
233
+ last_val = last_val[k]
234
+ except Exception:
235
+ self._logger.error(f'Key error: {k}')
236
+ raise
237
+ return last_val
238
+
239
+ @staticmethod
240
+ def get_value_from_config(config: dict, key: str):
241
+ """
242
+ Takes a config (dict) and a key as dot delimited string and returns its value.
243
+ Use get_config function to read the global config.
244
+ If just reading from the global config, use the get_value function.
245
+ """
246
+ keys = key.split('.')
247
+ last_val = config
248
+ for k in keys:
249
+ try:
250
+ last_val = last_val[k]
251
+ except Exception:
252
+ logging.error(f'Key error: {k}')
253
+ raise
254
+ return last_val
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: yi-config-starter
3
+ Version: 0.1.3
4
+ Summary: Config starter for my local projects
5
+ Author: Yunus Indori
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 <Yunus Indori>
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Requires-Python: >=3.9
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: PyYAML>=6.0
32
+ Dynamic: license-file
33
+
34
+ # yi-config-starter
35
+
36
+ A lightweight YAML configuration loader with automatic config discovery, placeholder substitution, and a thread-safe singleton interface.
37
+
38
+ This library is intended to centralize configuration loading logic while keeping application code clean and portable across environments.
39
+
40
+ ---
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install yi-config-starter
46
+ ````
47
+
48
+ ---
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from yi_config_starter import ApplicationConfiguration
54
+
55
+ cfg = ApplicationConfiguration.get_instance()
56
+ config = cfg.get_config()
57
+
58
+ db_host = cfg.get_config_value("db.datasource.config.host")
59
+ print(db_host)
60
+ ```
61
+
62
+ You may also initialize it explicitly (still resolves to a singleton internally):
63
+
64
+ ```python
65
+ from yi_config_starter import ApplicationConfiguration
66
+ cfg = ApplicationConfiguration(
67
+ filename="config.yml",
68
+ env_var="MY_APP_CONFIG",
69
+ app_name="my-app",
70
+ # path="/absolute/or/~/path/to/config.yml",
71
+ # extra_placeholders={"PROJECT_ROOT": "/some/path"},
72
+ )
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Configuration File Discovery
78
+
79
+ By default, the loader searches for `config.yml` in the following order:
80
+
81
+ 1. Explicit path passed to the constructor (`path=...`)
82
+ 2. Path provided via environment variable
83
+ 3. Current working directory, then parent directories (walking upward)
84
+ 4. User configuration directory
85
+
86
+ * Windows: `%APPDATA%\<app_name>\config.yml`
87
+ * Linux/macOS: `~/.config/<app_name>/config.yml`
88
+ 5. Entries on `sys.path` (recursive search)
89
+
90
+ If no configuration file is found, a `FileNotFoundError` is raised.
91
+
92
+ ---
93
+
94
+ ## Placeholder Substitution
95
+
96
+ Before parsing YAML, placeholders in the configuration file are substituted.
97
+
98
+ ### Built-in Placeholders
99
+
100
+ | Placeholder | Description |
101
+ | ------------------ | ---------------------------------------------- |
102
+ | `{{HOME}}` | User home directory |
103
+ | `{{separator}}` | OS path separator (`/` or `\`) |
104
+ | `{{APPDATA}}` | Windows `%APPDATA%` or fallback to `~/.config` |
105
+ | `{{XDG_CONFIG}}` | `$XDG_CONFIG_HOME` or `~/.config` |
106
+ | `{{ENV:VAR_NAME}}` | Value of environment variable `VAR_NAME` |
107
+
108
+ Custom placeholders may be supplied via `extra_placeholders`.
109
+
110
+ ### Example
111
+
112
+ ```yaml
113
+ paths:
114
+ data_dir: "{{HOME}}{{separator}}data"
115
+ cache_dir: "{{XDG_CONFIG}}{{separator}}my-app{{separator}}cache"
116
+ api_key: "{{ENV:MY_API_KEY}}"
117
+ ```
118
+
119
+ ---
120
+
121
+ ## API Reference
122
+
123
+ ### `ApplicationConfiguration.get_instance(...)`
124
+
125
+ Returns the singleton configuration instance (thread-safe).
126
+
127
+ ---
128
+
129
+ ### `cfg.get_config() -> dict`
130
+
131
+ Returns the entire configuration as a dictionary.
132
+
133
+ ---
134
+
135
+ ### `cfg.get_config_value("a.b.c")`
136
+
137
+ Returns a nested configuration value using dot-separated keys.
138
+
139
+ ---
140
+
141
+ ### `ApplicationConfiguration.get_value_from_config(config: dict, key: str)`
142
+
143
+ Static helper for retrieving nested values from an arbitrary dictionary.
144
+
145
+ ---
146
+
147
+ ## Versioning
148
+
149
+ The package version is resolved dynamically from:
150
+
151
+ ```
152
+ import yi_config_starter
153
+ yi_config_starter.__version__
154
+ ```
155
+
156
+ Defined in:
157
+
158
+ ```
159
+ /yi_config_starter/__init__.py
160
+ ```
161
+
162
+ ---
163
+
164
+ ## License
165
+
166
+ MIT License — see the `LICENSE` file.
167
+
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ yi_config_starter/__init__.py
5
+ yi_config_starter/config_starter.py
6
+ yi_config_starter.egg-info/PKG-INFO
7
+ yi_config_starter.egg-info/SOURCES.txt
8
+ yi_config_starter.egg-info/dependency_links.txt
9
+ yi_config_starter.egg-info/requires.txt
10
+ yi_config_starter.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ yi_config_starter