mooch.settings 0.0.1.dev4__tar.gz → 1.0.1__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.
Files changed (26) hide show
  1. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/PKG-INFO +16 -9
  2. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/README.md +14 -7
  3. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/pyproject.toml +6 -2
  4. mooch_settings-1.0.1/src/mooch/settings/utils.py +53 -0
  5. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch.settings.egg-info/PKG-INFO +16 -9
  6. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch.settings.egg-info/SOURCES.txt +0 -1
  7. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/tests/test_settings.py +29 -0
  8. mooch_settings-1.0.1/tests/test_utils.py +107 -0
  9. mooch_settings-0.0.1.dev4/settings.toml +0 -4
  10. mooch_settings-0.0.1.dev4/src/mooch/settings/utils.py +0 -24
  11. mooch_settings-0.0.1.dev4/tests/test_utils.py +0 -57
  12. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  13. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/.github/workflows/publish.yml +0 -0
  14. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/.github/workflows/run_tests.yml +0 -0
  15. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/.gitignore +0 -0
  16. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/LICENSE +0 -0
  17. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/setup.cfg +0 -0
  18. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch/__init__.py +0 -0
  19. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch/settings/__init__.py +0 -0
  20. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch/settings/filehandler.py +0 -0
  21. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch/settings/settings.py +0 -0
  22. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch.settings.egg-info/dependency_links.txt +0 -0
  23. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch.settings.egg-info/requires.txt +0 -0
  24. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/src/mooch.settings.egg-info/top_level.txt +0 -0
  25. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/tests/test_file.py +0 -0
  26. {mooch_settings-0.0.1.dev4 → mooch_settings-1.0.1}/uv.lock +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mooch.settings
3
- Version: 0.0.1.dev4
3
+ Version: 1.0.1
4
4
  Summary: Python settings management package (mooch.settings). Uses a TOML file.
5
5
  Author-email: Nick Stuer <nickstuer@duck.com>
6
6
  Project-URL: Homepage, https://github.com/nickstuer/mooch.settings
7
7
  Project-URL: Issues, https://github.com/nickstuer/mooch.settings/issues
8
- Classifier: Development Status :: 2 - Pre-Alpha
8
+ Classifier: Development Status :: 5 - Production/Stable
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
@@ -28,7 +28,9 @@ Dynamic: license-file
28
28
  [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
29
29
  [![license](https://img.shields.io/github/license/nickstuer/mooch.settings.svg)](LICENSE)
30
30
 
31
- This Python package is a collection of useful Python code that is commonly used on all types of Python projects.
31
+ A lightweight, TOML-backed configuration/settings utility that feels like a dictionary.
32
+
33
+ mooch.settings is a Python configuration library designed for simplicity and developer ergonomics. It loads settings data from TOML files and exposes them as standard Python dictionaries — allowing you to work with settings in a familiar, Python way.
32
34
 
33
35
  ## Table of Contents
34
36
 
@@ -40,9 +42,11 @@ This Python package is a collection of useful Python code that is commonly used
40
42
 
41
43
  ## 📖 Features
42
44
 
43
-
44
- ### Settings File
45
- Uses a TOML settings file. Easily get/set settings values. Automatically sets values to defaults if they're not currently saved in the setting file.
45
+ - TOML-powered: Uses toml under the hood for modern, human-friendly config files.
46
+ - Dictionary-like interface: Access and manipulate settings with regular dict operations.
47
+ - Nested access: Supports nested structures and dotted key notation.
48
+ - Safe defaults: Easily provide fallback values or defaults when keys are missing from the config file.
49
+ - Optional dynamic reload: Reloads config file everytime a key is read. (Enabled by default)
46
50
 
47
51
 
48
52
  ## 🛠 Install
@@ -61,17 +65,20 @@ Python 3.9 or greater
61
65
 
62
66
  ## 🎮 Usage
63
67
 
64
- ### settings File
68
+ ### Example
69
+ This will create/use a 'settings.toml' file located in the .mooch directory of the user's home directory.
65
70
  ```python
66
71
  from mooch.settings import Settings
72
+ from pathlib import Path
73
+
67
74
  default_settings = {
68
75
  "settings": {
69
- "name": "MyName,
76
+ "name": "MyName",
70
77
  "mood": "happy",
71
78
  },
72
79
  }
73
80
 
74
- settings = Settings("settings.toml", default_settings)
81
+ settings = Settings(Path.home() / ".mooch/settings.toml", default_settings)
75
82
 
76
83
  print(settings["settings.mood"])
77
84
  settings["settings.mood"] = "angry"
@@ -11,7 +11,9 @@
11
11
  [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
12
12
  [![license](https://img.shields.io/github/license/nickstuer/mooch.settings.svg)](LICENSE)
13
13
 
14
- This Python package is a collection of useful Python code that is commonly used on all types of Python projects.
14
+ A lightweight, TOML-backed configuration/settings utility that feels like a dictionary.
15
+
16
+ mooch.settings is a Python configuration library designed for simplicity and developer ergonomics. It loads settings data from TOML files and exposes them as standard Python dictionaries — allowing you to work with settings in a familiar, Python way.
15
17
 
16
18
  ## Table of Contents
17
19
 
@@ -23,9 +25,11 @@ This Python package is a collection of useful Python code that is commonly used
23
25
 
24
26
  ## 📖 Features
25
27
 
26
-
27
- ### Settings File
28
- Uses a TOML settings file. Easily get/set settings values. Automatically sets values to defaults if they're not currently saved in the setting file.
28
+ - TOML-powered: Uses toml under the hood for modern, human-friendly config files.
29
+ - Dictionary-like interface: Access and manipulate settings with regular dict operations.
30
+ - Nested access: Supports nested structures and dotted key notation.
31
+ - Safe defaults: Easily provide fallback values or defaults when keys are missing from the config file.
32
+ - Optional dynamic reload: Reloads config file everytime a key is read. (Enabled by default)
29
33
 
30
34
 
31
35
  ## 🛠 Install
@@ -44,17 +48,20 @@ Python 3.9 or greater
44
48
 
45
49
  ## 🎮 Usage
46
50
 
47
- ### settings File
51
+ ### Example
52
+ This will create/use a 'settings.toml' file located in the .mooch directory of the user's home directory.
48
53
  ```python
49
54
  from mooch.settings import Settings
55
+ from pathlib import Path
56
+
50
57
  default_settings = {
51
58
  "settings": {
52
- "name": "MyName,
59
+ "name": "MyName",
53
60
  "mood": "happy",
54
61
  },
55
62
  }
56
63
 
57
- settings = Settings("settings.toml", default_settings)
64
+ settings = Settings(Path.home() / ".mooch/settings.toml", default_settings)
58
65
 
59
66
  print(settings["settings.mood"])
60
67
  settings["settings.mood"] = "angry"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mooch.settings"
3
- version = "0.0.1dev4"
3
+ version = "1.0.1"
4
4
  authors = [
5
5
  { name="Nick Stuer", email="nickstuer@duck.com" },
6
6
  ]
@@ -10,8 +10,12 @@ requires-python = ">=3.9"
10
10
  dependencies = [
11
11
  "toml>=0.10.2",
12
12
  ]
13
+ # How mature is this project? Common values are
14
+ # 3 - Alpha
15
+ # 4 - Beta
16
+ # 5 - Production/Stable
13
17
  classifiers = [
14
- "Development Status :: 2 - Pre-Alpha",
18
+ "Development Status :: 5 - Production/Stable",
15
19
  "Programming Language :: Python :: 3",
16
20
  "License :: OSI Approved :: MIT License",
17
21
  "Operating System :: OS Independent",
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Any
5
+
6
+
7
+ def is_valid_key(key: str) -> bool:
8
+ # Bare keys: a-z, A-Z, 0-9, _, -, no quotes or spaces
9
+ # No support for keys with nested quotes
10
+ return bool(re.fullmatch(r"[A-Za-z0-9_-]+", key))
11
+
12
+
13
+ def set_nested(d: dict, key: str, value: Any, sep: str = ".") -> None: # noqa: ANN401
14
+ """Set a nested value in a dictionary by key."""
15
+ keys = key.split(sep)
16
+ for k in keys[:-1]:
17
+ if not is_valid_key(k):
18
+ error_msg = (
19
+ f"Invalid key: '{k}'. "
20
+ "Keys must be alphanumeric, underscores, or dashes, "
21
+ "and cannot contain spaces or quotes."
22
+ )
23
+ raise ValueError(error_msg)
24
+
25
+ if k not in d or not isinstance(d[k], dict):
26
+ d[k] = {}
27
+ d = d[k]
28
+ if not is_valid_key(keys[-1]):
29
+ error_msg = (
30
+ f"Invalid key: '{keys[-1]}'. "
31
+ "Keys must be alphanumeric, underscores, or dashes, "
32
+ "and cannot contain spaces or quotes."
33
+ )
34
+ raise ValueError(error_msg)
35
+ d[keys[-1]] = value
36
+
37
+
38
+ def get_nested(d: dict, key: str, sep: str = ".") -> Any | None: # noqa: ANN401
39
+ """Get a nested value from a dictionary by key."""
40
+ keys = key.split(sep)
41
+ for k in keys:
42
+ if not is_valid_key(k):
43
+ error_msg = (
44
+ f"Invalid key: '{k}'. "
45
+ "Keys must be alphanumeric, underscores, or dashes, "
46
+ "and cannot contain spaces or quotes."
47
+ )
48
+ raise ValueError(error_msg)
49
+ if isinstance(d, dict) and k in d:
50
+ d = d[k]
51
+ else:
52
+ return None
53
+ return d
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mooch.settings
3
- Version: 0.0.1.dev4
3
+ Version: 1.0.1
4
4
  Summary: Python settings management package (mooch.settings). Uses a TOML file.
5
5
  Author-email: Nick Stuer <nickstuer@duck.com>
6
6
  Project-URL: Homepage, https://github.com/nickstuer/mooch.settings
7
7
  Project-URL: Issues, https://github.com/nickstuer/mooch.settings/issues
8
- Classifier: Development Status :: 2 - Pre-Alpha
8
+ Classifier: Development Status :: 5 - Production/Stable
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
@@ -28,7 +28,9 @@ Dynamic: license-file
28
28
  [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
29
29
  [![license](https://img.shields.io/github/license/nickstuer/mooch.settings.svg)](LICENSE)
30
30
 
31
- This Python package is a collection of useful Python code that is commonly used on all types of Python projects.
31
+ A lightweight, TOML-backed configuration/settings utility that feels like a dictionary.
32
+
33
+ mooch.settings is a Python configuration library designed for simplicity and developer ergonomics. It loads settings data from TOML files and exposes them as standard Python dictionaries — allowing you to work with settings in a familiar, Python way.
32
34
 
33
35
  ## Table of Contents
34
36
 
@@ -40,9 +42,11 @@ This Python package is a collection of useful Python code that is commonly used
40
42
 
41
43
  ## 📖 Features
42
44
 
43
-
44
- ### Settings File
45
- Uses a TOML settings file. Easily get/set settings values. Automatically sets values to defaults if they're not currently saved in the setting file.
45
+ - TOML-powered: Uses toml under the hood for modern, human-friendly config files.
46
+ - Dictionary-like interface: Access and manipulate settings with regular dict operations.
47
+ - Nested access: Supports nested structures and dotted key notation.
48
+ - Safe defaults: Easily provide fallback values or defaults when keys are missing from the config file.
49
+ - Optional dynamic reload: Reloads config file everytime a key is read. (Enabled by default)
46
50
 
47
51
 
48
52
  ## 🛠 Install
@@ -61,17 +65,20 @@ Python 3.9 or greater
61
65
 
62
66
  ## 🎮 Usage
63
67
 
64
- ### settings File
68
+ ### Example
69
+ This will create/use a 'settings.toml' file located in the .mooch directory of the user's home directory.
65
70
  ```python
66
71
  from mooch.settings import Settings
72
+ from pathlib import Path
73
+
67
74
  default_settings = {
68
75
  "settings": {
69
- "name": "MyName,
76
+ "name": "MyName",
70
77
  "mood": "happy",
71
78
  },
72
79
  }
73
80
 
74
- settings = Settings("settings.toml", default_settings)
81
+ settings = Settings(Path.home() / ".mooch/settings.toml", default_settings)
75
82
 
76
83
  print(settings["settings.mood"])
77
84
  settings["settings.mood"] = "angry"
@@ -2,7 +2,6 @@
2
2
  LICENSE
3
3
  README.md
4
4
  pyproject.toml
5
- settings.toml
6
5
  uv.lock
7
6
  .github/ISSUE_TEMPLATE/bug_report.md
8
7
  .github/workflows/publish.yml
@@ -174,6 +174,35 @@ def test_settings_saves_settings_to_file(settings_filepath: Path):
174
174
  assert new_settings.get("mood") == "TestMood"
175
175
 
176
176
 
177
+ def test_settings_with_different_cases_in_key(settings_filepath: Path):
178
+ settings = Settings(settings_filepath, default_settings)
179
+ settings["caseCheck"] = "value"
180
+
181
+ assert settings["caseCheck"] == "value"
182
+ assert settings["casecheck"] is None
183
+
184
+
185
+ def test_settings_adds_metadata_after_init_with_default_settings(settings_filepath: Path):
186
+ settings = Settings(settings_filepath, default_settings)
187
+
188
+ # Check if metadata is added
189
+ assert settings["metadata.notice"] is not None
190
+ assert settings.get("metadata.notice") is not None
191
+ assert settings["metadata.created"] is not None
192
+ assert settings["metadata.updated"] is not None
193
+ assert settings["metadata.NotReal"] is None
194
+
195
+
196
+ def test_settings_adds_metadata_after_init_without_default_settings(settings_filepath: Path):
197
+ settings = Settings(settings_filepath)
198
+
199
+ # Check if metadata is added
200
+ assert settings["metadata.notice"] is not None
201
+ assert settings["metadata.created"] is not None
202
+ assert settings["metadata.updated"] is not None
203
+ assert settings["metadata.NotReal"] is None
204
+
205
+
177
206
  def test_settings_no_default_settings(settings_filepath: Path):
178
207
  # Test with no default settings
179
208
  settings = Settings(settings_filepath)
@@ -0,0 +1,107 @@
1
+ import pytest
2
+
3
+ from mooch.settings.utils import get_nested, is_valid_key, set_nested
4
+
5
+
6
+ @pytest.mark.parametrize(
7
+ ("key", "expected"),
8
+ [
9
+ ("DEBUG", True),
10
+ ("debug-mode", True),
11
+ ("db.host", False),
12
+ ('"db.host"', False),
13
+ ("db host", False),
14
+ ("'db host'", False),
15
+ ('"bad"key"', False),
16
+ ("'bad'key'", False),
17
+ ("simple_key", True),
18
+ ("123key", True),
19
+ ("key-with-dash", True),
20
+ ("key with space", False),
21
+ ('"key with space in nested quotes"', False),
22
+ ('"key with \n newline"', False),
23
+ ("'single'quote'", False),
24
+ ("'valid key'", False),
25
+ ('"valid key"', False),
26
+ ('""', False),
27
+ ("", False),
28
+ ],
29
+ )
30
+ def test_is_valid_key(key, expected):
31
+ assert is_valid_key(key) == expected
32
+
33
+
34
+ def test_get_nested_invalid_key():
35
+ d = {"a": {"b": {"c": 1}}}
36
+ with pytest.raises(ValueError): # noqa: PT011
37
+ get_nested(d, "a.b ")
38
+ with pytest.raises(ValueError): # noqa: PT011
39
+ get_nested(d, "b ")
40
+ with pytest.raises(ValueError): # noqa: PT011
41
+ get_nested(d, "b .c.d.a")
42
+
43
+
44
+ def test_set_nested_invalid_key():
45
+ d = {"a": {"b": {"c": 1}}}
46
+ with pytest.raises(ValueError):
47
+ set_nested(d, "a.b .c", 2)
48
+ with pytest.raises(ValueError):
49
+ set_nested(d, "b ", 2)
50
+ with pytest.raises(ValueError):
51
+ set_nested(d, "b .c.d.a", 2)
52
+ with pytest.raises(ValueError):
53
+ set_nested(d, "a.b.c ", 2)
54
+
55
+
56
+ @pytest.mark.parametrize(
57
+ "initial, key, value, expected",
58
+ [
59
+ ({}, "a.b.c", 1, {"a": {"b": {"c": 1}}}),
60
+ ({"a": {}}, "a.b", 2, {"a": {"b": 2}}),
61
+ ({"a": {"b": 3}}, "a.b", 4, {"a": {"b": 4}}),
62
+ ({}, "x", 5, {"x": 5}),
63
+ ({"a": {"b": {"c": 1}}}, "a.b.d", 6, {"a": {"b": {"c": 1, "d": 6}}}),
64
+ ],
65
+ )
66
+ def test_set_nested(initial, key, value, expected):
67
+ d = initial.copy()
68
+ set_nested(d, key, value)
69
+ assert d == expected
70
+
71
+
72
+ @pytest.mark.parametrize(
73
+ "d, key, expected",
74
+ [
75
+ ({"a": {"b": {"c": 1}}}, "a.b.c", 1),
76
+ ({"a": {"b": 2}}, "a.b", 2),
77
+ ({"x": 5}, "x", 5),
78
+ ({"a": {"b": {"c": 1}}}, "a.b", {"c": 1}),
79
+ ({"a": {"b": {"c": 1}}}, "a.b.c.d", None),
80
+ ({}, "foo.bar", None),
81
+ ({"a": 1}, "a.b", None),
82
+ ],
83
+ )
84
+ def test_get_nested(d, key, expected):
85
+ assert get_nested(d, key) == expected
86
+
87
+
88
+ def test_set_nested_with_custom_separator():
89
+ d = {}
90
+ set_nested(d, "a|b|c", 10, sep="|")
91
+ assert d == {"a": {"b": {"c": 10}}}
92
+
93
+
94
+ def test_get_nested_with_custom_separator():
95
+ d = {"a": {"b": {"c": 42}}}
96
+ assert get_nested(d, "a|b|c", sep="|") == 42
97
+
98
+
99
+ def test_set_nested_overwrites_non_dict():
100
+ d = {"a": 1}
101
+ set_nested(d, "a.b", 2)
102
+ assert d == {"a": {"b": 2}}
103
+
104
+
105
+ def test_get_nested_returns_none_for_non_dict():
106
+ d = {"a": 1}
107
+ assert get_nested(d, "a.b") is None
@@ -1,4 +0,0 @@
1
- [metadata]
2
- notice = "This file was created by mooch.settings."
3
- created = "2025-06-30T04:10:17.433854+00:00"
4
- updated = "2025-06-30T04:10:17.433860+00:00"
@@ -1,24 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
-
6
- def set_nested(d: dict, key: str, value: Any, sep: str = ".") -> None: # noqa: ANN401
7
- """Set a nested value in a dictionary by key."""
8
- keys = key.split(sep)
9
- for k in keys[:-1]:
10
- if k not in d or not isinstance(d[k], dict):
11
- d[k] = {}
12
- d = d[k]
13
- d[keys[-1]] = value
14
-
15
-
16
- def get_nested(d: dict, key: str, sep: str = ".") -> Any | None: # noqa: ANN401
17
- """Get a nested value from a dictionary by key."""
18
- keys = key.split(sep)
19
- for k in keys:
20
- if isinstance(d, dict) and k in d:
21
- d = d[k]
22
- else:
23
- return None
24
- return d
@@ -1,57 +0,0 @@
1
- import pytest
2
-
3
- from mooch.settings.utils import get_nested, set_nested
4
-
5
-
6
- @pytest.mark.parametrize(
7
- "initial, key, value, expected",
8
- [
9
- ({}, "a.b.c", 1, {"a": {"b": {"c": 1}}}),
10
- ({"a": {}}, "a.b", 2, {"a": {"b": 2}}),
11
- ({"a": {"b": 3}}, "a.b", 4, {"a": {"b": 4}}),
12
- ({}, "x", 5, {"x": 5}),
13
- ({"a": {"b": {"c": 1}}}, "a.b.d", 6, {"a": {"b": {"c": 1, "d": 6}}}),
14
- ],
15
- )
16
- def test_set_nested(initial, key, value, expected):
17
- d = initial.copy()
18
- set_nested(d, key, value)
19
- assert d == expected
20
-
21
-
22
- @pytest.mark.parametrize(
23
- "d, key, expected",
24
- [
25
- ({"a": {"b": {"c": 1}}}, "a.b.c", 1),
26
- ({"a": {"b": 2}}, "a.b", 2),
27
- ({"x": 5}, "x", 5),
28
- ({"a": {"b": {"c": 1}}}, "a.b", {"c": 1}),
29
- ({"a": {"b": {"c": 1}}}, "a.b.c.d", None),
30
- ({}, "foo.bar", None),
31
- ({"a": 1}, "a.b", None),
32
- ],
33
- )
34
- def test_get_nested(d, key, expected):
35
- assert get_nested(d, key) == expected
36
-
37
-
38
- def test_set_nested_with_custom_separator():
39
- d = {}
40
- set_nested(d, "a|b|c", 10, sep="|")
41
- assert d == {"a": {"b": {"c": 10}}}
42
-
43
-
44
- def test_get_nested_with_custom_separator():
45
- d = {"a": {"b": {"c": 42}}}
46
- assert get_nested(d, "a|b|c", sep="|") == 42
47
-
48
-
49
- def test_set_nested_overwrites_non_dict():
50
- d = {"a": 1}
51
- set_nested(d, "a.b", 2)
52
- assert d == {"a": {"b": 2}}
53
-
54
-
55
- def test_get_nested_returns_none_for_non_dict():
56
- d = {"a": 1}
57
- assert get_nested(d, "a.b") is None