conveoconfi 0.1.0__py3-none-any.whl

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
+ """Public compatibility entrypoint for conveoconfi."""
2
+
3
+ from .config_files import (
4
+ append_config_file,
5
+ complete_config_file,
6
+ config_file_exists,
7
+ config_file_path,
8
+ create_and_read_config_file,
9
+ get_param,
10
+ overwrite_config_file,
11
+ )
12
+
13
+ __all__ = [
14
+ "append_config_file",
15
+ "complete_config_file",
16
+ "config_file_exists",
17
+ "config_file_path",
18
+ "create_and_read_config_file",
19
+ "get_param",
20
+ "overwrite_config_file",
21
+ ]
@@ -0,0 +1,192 @@
1
+ """Legacy-compatible config file API surface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import yaml
9
+
10
+
11
+ def _not_implemented(function_name: str) -> None:
12
+ raise NotImplementedError(f"{function_name} is not implemented yet")
13
+
14
+
15
+ def _read_yaml_file(path: Path) -> Any:
16
+ with path.open("r", encoding="utf-8") as file:
17
+ return yaml.safe_load(file)
18
+
19
+
20
+ def _write_yaml_file(path: Path, data: Any) -> None:
21
+ path.parent.mkdir(parents=True, exist_ok=True)
22
+ with path.open("w", encoding="utf-8") as file:
23
+ yaml.safe_dump(data, file, sort_keys=False, allow_unicode=True)
24
+
25
+
26
+ def _default_template_path(
27
+ file_name: str,
28
+ default_files_dir: str | Path | None = None,
29
+ default_template_dir: str | Path | None = None,
30
+ ) -> Path:
31
+ if default_files_dir is not None and default_template_dir is not None:
32
+ raise ValueError(
33
+ "Pass only one of default_files_dir or default_template_dir, not both"
34
+ )
35
+
36
+ template_dir = default_files_dir if default_files_dir is not None else default_template_dir
37
+ if template_dir is None:
38
+ raise FileNotFoundError(
39
+ "A default template directory is required. Pass default_files_dir "
40
+ "or default_template_dir."
41
+ )
42
+
43
+ template_path = Path(template_dir).expanduser() / file_name
44
+ if not template_path.is_file():
45
+ raise FileNotFoundError(
46
+ f"Default template for {file_name!r} was not found at {template_path}"
47
+ )
48
+
49
+ return template_path
50
+
51
+
52
+ def _read_default_template(
53
+ file_name: str,
54
+ default_files_dir: str | Path | None = None,
55
+ default_template_dir: str | Path | None = None,
56
+ ) -> Any:
57
+ template_path = _default_template_path(
58
+ file_name,
59
+ default_files_dir=default_files_dir,
60
+ default_template_dir=default_template_dir,
61
+ )
62
+ data = _read_yaml_file(template_path)
63
+ if data is None:
64
+ raise ValueError(f"Default template {template_path} is empty")
65
+ return data
66
+
67
+
68
+ def _complete_config_data(current_data: Any, default_data: Any) -> Any:
69
+ if not isinstance(current_data, dict) or not isinstance(default_data, dict):
70
+ return current_data
71
+
72
+ completed_data = dict(current_data)
73
+ for key, default_value in default_data.items():
74
+ if key not in completed_data:
75
+ completed_data[key] = default_value
76
+ continue
77
+
78
+ current_value = completed_data[key]
79
+ if isinstance(current_value, dict) and isinstance(default_value, dict):
80
+ completed_data[key] = _complete_config_data(current_value, default_value)
81
+
82
+ return completed_data
83
+
84
+
85
+ def create_and_read_config_file(
86
+ file_name: str,
87
+ default_app_dir: str | Path,
88
+ force_default: bool = False,
89
+ complete_file: bool = True,
90
+ default_files_dir: str | Path | None = None,
91
+ default_template_dir: str | Path | None = None,
92
+ ):
93
+ """Create a missing config file from a YAML template and return parsed data."""
94
+ path = config_file_path(file_name, default_app_dir)
95
+ if force_default or not path.exists():
96
+ data = _read_default_template(
97
+ file_name,
98
+ default_files_dir=default_files_dir,
99
+ default_template_dir=default_template_dir,
100
+ )
101
+ _write_yaml_file(path, data)
102
+ return data
103
+
104
+ data = _read_yaml_file(path)
105
+ if data is None:
106
+ data = _read_default_template(
107
+ file_name,
108
+ default_files_dir=default_files_dir,
109
+ default_template_dir=default_template_dir,
110
+ )
111
+ _write_yaml_file(path, data)
112
+ elif complete_file:
113
+ data = complete_config_file(
114
+ file_name,
115
+ default_app_dir,
116
+ default_files_dir=default_files_dir,
117
+ default_template_dir=default_template_dir,
118
+ )
119
+ return data
120
+
121
+
122
+ def complete_config_file(
123
+ file_name: str,
124
+ default_app_dir: str | Path,
125
+ default_files_dir: str | Path | None = None,
126
+ default_template_dir: str | Path | None = None,
127
+ ):
128
+ """Complete a config file with missing values from its default template."""
129
+ path = config_file_path(file_name, default_app_dir)
130
+ data = _read_yaml_file(path)
131
+ default_data = _read_default_template(
132
+ file_name,
133
+ default_files_dir=default_files_dir,
134
+ default_template_dir=default_template_dir,
135
+ )
136
+
137
+ if data is None:
138
+ _write_yaml_file(path, default_data)
139
+ return default_data
140
+
141
+ completed_data = _complete_config_data(data, default_data)
142
+ if completed_data != data:
143
+ _write_yaml_file(path, completed_data)
144
+ return completed_data
145
+
146
+
147
+ def overwrite_config_file(file_name: str, default_app_dir: str | Path, data: Any) -> None:
148
+ """Overwrite a config file with YAML data."""
149
+ _write_yaml_file(config_file_path(file_name, default_app_dir), data)
150
+
151
+
152
+ def append_config_file(file_name: str, default_app_dir: str | Path, data: Any) -> Any:
153
+ """Append YAML data to a config file, then rewrite normalized YAML."""
154
+ path = config_file_path(file_name, default_app_dir)
155
+ with path.open("a", encoding="utf-8") as file:
156
+ yaml.safe_dump(data, file, sort_keys=False, allow_unicode=True)
157
+
158
+ normalized_data = _read_yaml_file(path)
159
+ _write_yaml_file(path, normalized_data)
160
+ return normalized_data
161
+
162
+
163
+ def get_param(
164
+ parent_param: str,
165
+ param: str,
166
+ default_app_dir: str | Path,
167
+ default_files_dir: str | Path | None = None,
168
+ default_template_dir: str | Path | None = None,
169
+ ):
170
+ """Read a child parameter from the root config.yaml file."""
171
+ data = create_and_read_config_file(
172
+ "config.yaml",
173
+ default_app_dir,
174
+ default_files_dir=default_files_dir,
175
+ default_template_dir=default_template_dir,
176
+ )
177
+ try:
178
+ return data[parent_param][param]
179
+ except (KeyError, TypeError):
180
+ raise Exception(
181
+ f"Parameter {param!r} was not found in {parent_param!r}"
182
+ ) from None
183
+
184
+
185
+ def config_file_exists(file_name: str, default_app_dir: str | Path) -> bool:
186
+ return config_file_path(file_name, default_app_dir).is_file()
187
+
188
+
189
+ def config_file_path(file_name: str, default_app_dir: str | Path) -> Path:
190
+ app_dir = Path(default_app_dir).expanduser()
191
+ app_dir.mkdir(parents=True, exist_ok=True)
192
+ return app_dir / file_name
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: conveoconfi
3
+ Version: 0.1.0
4
+ Summary: Reusable YAML template-backed configuration file helpers.
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: pyyaml>=6.0
7
+ Provides-Extra: test
8
+ Requires-Dist: pytest>=8.0; extra == 'test'
9
+ Description-Content-Type: text/markdown
10
+
11
+ # conveoconfi
12
+
13
+ Reusable YAML template-backed configuration helpers for Python applications.
14
+
15
+ `conveoconfi` provides a small function-based API for convention-over-
16
+ configuration behavior:
17
+
18
+ - create missing application config directories
19
+ - create missing YAML config files from application-owned default templates
20
+ - complete existing YAML files with newly added defaults
21
+ - preserve existing user-provided values and extra keys
22
+ - read child parameters from `config.yaml`
23
+
24
+ ## Installation
25
+
26
+ Install the package with pip, or declare it in your project's dependency
27
+ metadata.
28
+
29
+ ```bash
30
+ pip install conveoconfi
31
+ ```
32
+
33
+ `conveoconfi` declares `PyYAML` as a runtime dependency, so applications do not
34
+ need to add a separate YAML dependency for these helpers.
35
+
36
+ ## Default Templates
37
+
38
+ Applications keep their own YAML default templates, such as `config.yaml`,
39
+ `logging.yaml`, or `feature_flags.yaml`. Pass that template directory explicitly
40
+ when reading or completing config files.
41
+
42
+ ```python
43
+ from pathlib import Path
44
+
45
+ from conveoconfi import create_and_read_config_file
46
+
47
+
48
+ APP_DIR = Path.home() / ".myapp"
49
+ DEFAULT_FILES_DIR = Path(__file__).parent / "default_files"
50
+
51
+ config = create_and_read_config_file(
52
+ "config.yaml",
53
+ APP_DIR,
54
+ default_files_dir=DEFAULT_FILES_DIR,
55
+ )
56
+ ```
57
+
58
+ The equivalent keyword `default_template_dir` is also supported. Pass only one
59
+ of `default_files_dir` or `default_template_dir`.
60
+
61
+ Template lookup failures are explicit. If a default directory is omitted, or the
62
+ requested template file is missing, `conveoconfi` raises a `FileNotFoundError`
63
+ that names the lookup problem.
64
+
65
+ ## Template Discovery Decision
66
+
67
+ The public API requires applications to pass their template directory
68
+ explicitly with `default_files_dir` or `default_template_dir`. `conveoconfi`
69
+ does not search for templates in its own package directory because those files
70
+ belong to the consuming application, not to this reusable dependency.
71
+
72
+ Explicit template paths keep behavior predictable in tests, work with any
73
+ project layout, and fail with a direct `FileNotFoundError` when templates are
74
+ not configured. Projects that want a shorter call site can wrap `conveoconfi`
75
+ once in their own code and bind the app's template path there.
76
+
77
+ ## Public API
78
+
79
+ The public API exposes these function names from the package root:
80
+
81
+ - `create_and_read_config_file(file_name, default_app_dir, force_default=False, complete_file=True, default_files_dir=None, default_template_dir=None)`
82
+ - `complete_config_file(file_name, default_app_dir, default_files_dir=None, default_template_dir=None)`
83
+ - `overwrite_config_file(file_name, default_app_dir, data)`
84
+ - `append_config_file(file_name, default_app_dir, data)`
85
+ - `get_param(parent_param, param, default_app_dir, default_files_dir=None, default_template_dir=None)`
86
+ - `config_file_exists(file_name, default_app_dir)`
87
+ - `config_file_path(file_name, default_app_dir)`
88
+
89
+ `config_file_path` creates the application config directory before returning the
90
+ path. `get_param` reads from `config.yaml`.
91
+
92
+ ## Completion Behavior
93
+
94
+ By default, `create_and_read_config_file` completes existing config files from
95
+ the matching default template and writes the completed result back to disk.
96
+ Completion is recursive for dictionaries:
97
+
98
+ ```yaml
99
+ # Existing user config
100
+ notifications:
101
+ email:
102
+ enabled: false
103
+
104
+ # Default template
105
+ notifications:
106
+ email:
107
+ enabled: true
108
+ sender: hello@example.com
109
+ ```
110
+
111
+ The completed file keeps the user's `enabled: false` value and adds only the
112
+ missing `sender` value. User-defined extra keys are preserved. If a
113
+ current value and default value disagree on shape, such as a scalar versus a
114
+ dictionary, the current user value is preserved.
115
+
116
+ Use `force_default=True` to deliberately replace an existing config file with
117
+ template defaults. Use `complete_file=False` to read an existing file without
118
+ mutating it.
@@ -0,0 +1,5 @@
1
+ conveoconfi/__init__.py,sha256=7oz23y3d-zeHyTwhN60Jyuu4dESC4haP4p6V3iPbiFk,457
2
+ conveoconfi/config_files.py,sha256=C2NF-feI5Nlz7bXrfNR3xp5Cg9tVeVh1O1c08Ae3ZbY,6259
3
+ conveoconfi-0.1.0.dist-info/METADATA,sha256=mPRTkYediebZYxo8et99NjcpiNEcpSnxU19M89Zhz70,4113
4
+ conveoconfi-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ conveoconfi-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any