config2py 0.1.43__tar.gz → 0.1.45__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.
- config2py-0.1.45/.gitattributes +1 -0
- config2py-0.1.45/.github/workflows/ci.yml +171 -0
- config2py-0.1.45/.gitignore +119 -0
- {config2py-0.1.43 → config2py-0.1.45}/PKG-INFO +13 -7
- {config2py-0.1.43 → config2py-0.1.45}/config2py/s_configparser.py +0 -1
- {config2py-0.1.43 → config2py-0.1.45}/config2py/sync_store.py +61 -7
- {config2py-0.1.43 → config2py-0.1.45}/config2py/tools.py +0 -1
- config2py-0.1.45/config_store_test.ini +4 -0
- config2py-0.1.45/misc/config2py - demo.ipynb +568 -0
- config2py-0.1.43/config2py.egg-info/PKG-INFO → config2py-0.1.45/misc/config2py - demo.md +31 -153
- config2py-0.1.45/pyproject.toml +84 -0
- {config2py-0.1.43 → config2py-0.1.45}/setup.cfg +1 -5
- config2py-0.1.43/config2py.egg-info/SOURCES.txt +0 -22
- config2py-0.1.43/config2py.egg-info/dependency_links.txt +0 -1
- config2py-0.1.43/config2py.egg-info/not-zip-safe +0 -1
- config2py-0.1.43/config2py.egg-info/requires.txt +0 -5
- config2py-0.1.43/config2py.egg-info/top_level.txt +0 -1
- {config2py-0.1.43 → config2py-0.1.45}/LICENSE +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/README.md +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/__init__.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/base.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/errors.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/scrap/__init__.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/tests/__init__.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/tests/test_sync_store.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/tests/test_tools.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/tests/utils_for_testing.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/config2py/util.py +0 -0
- {config2py-0.1.43 → config2py-0.1.45}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.ipynb linguist-documentation
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
name: Continuous Integration (Modern)
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
|
|
4
|
+
env:
|
|
5
|
+
PROJECT_NAME: config2py
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
validation:
|
|
9
|
+
name: Validation
|
|
10
|
+
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install Dependencies
|
|
25
|
+
uses: i2mint/wads/actions/install-deps@master
|
|
26
|
+
with:
|
|
27
|
+
dependency-files: pyproject.toml
|
|
28
|
+
extras: dev,test
|
|
29
|
+
# Fallback for projects still using setup.cfg:
|
|
30
|
+
# dependency-files: setup.cfg
|
|
31
|
+
# ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} # Uncomment for private dependencies
|
|
32
|
+
|
|
33
|
+
- name: Format Source Code
|
|
34
|
+
uses: i2mint/wads/actions/ruff-format@master
|
|
35
|
+
with:
|
|
36
|
+
line-length: 88
|
|
37
|
+
target-path: .
|
|
38
|
+
|
|
39
|
+
- name: Lint Validation
|
|
40
|
+
uses: i2mint/wads/actions/ruff-lint@master
|
|
41
|
+
with:
|
|
42
|
+
root-dir: ${{ env.PROJECT_NAME }}
|
|
43
|
+
output-format: github
|
|
44
|
+
# Ruff will read configuration from pyproject.toml
|
|
45
|
+
|
|
46
|
+
- name: Run Tests
|
|
47
|
+
uses: i2mint/wads/actions/run-tests@master
|
|
48
|
+
with:
|
|
49
|
+
root-dir: ${{ env.PROJECT_NAME }}
|
|
50
|
+
exclude: examples,scrap
|
|
51
|
+
coverage: true
|
|
52
|
+
pytest-args: -v --tb=short
|
|
53
|
+
|
|
54
|
+
windows-validation:
|
|
55
|
+
name: Windows Tests (Informational)
|
|
56
|
+
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
57
|
+
runs-on: windows-latest
|
|
58
|
+
continue-on-error: true # Don't fail the entire workflow if Windows tests fail
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@v4
|
|
62
|
+
|
|
63
|
+
- name: Set up Python 3.10
|
|
64
|
+
uses: actions/setup-python@v5
|
|
65
|
+
with:
|
|
66
|
+
python-version: "3.10"
|
|
67
|
+
|
|
68
|
+
- name: Install Dependencies
|
|
69
|
+
uses: i2mint/wads/actions/install-deps@master
|
|
70
|
+
with:
|
|
71
|
+
dependency-files: pyproject.toml
|
|
72
|
+
extras: dev,test
|
|
73
|
+
|
|
74
|
+
- name: Run tests
|
|
75
|
+
id: test
|
|
76
|
+
continue-on-error: true
|
|
77
|
+
run: pytest
|
|
78
|
+
|
|
79
|
+
- name: Report test results
|
|
80
|
+
if: always()
|
|
81
|
+
run: |
|
|
82
|
+
if [ "${{ steps.test.outcome }}" == "failure" ]; then
|
|
83
|
+
echo "::warning::Windows tests failed but workflow continues"
|
|
84
|
+
echo "## ⚠️ Windows Tests Failed" >> $GITHUB_STEP_SUMMARY
|
|
85
|
+
echo "Tests failed on Windows but this is informational only." >> $GITHUB_STEP_SUMMARY
|
|
86
|
+
else
|
|
87
|
+
echo "## ✅ Windows Tests Passed" >> $GITHUB_STEP_SUMMARY
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
publish:
|
|
91
|
+
name: Publish
|
|
92
|
+
if: "!contains(github.event.head_commit.message, '[skip ci]') && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main')"
|
|
93
|
+
needs: validation
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
strategy:
|
|
96
|
+
matrix:
|
|
97
|
+
python-version: ["3.12"]
|
|
98
|
+
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
with:
|
|
102
|
+
fetch-depth: 0
|
|
103
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
104
|
+
|
|
105
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
106
|
+
uses: actions/setup-python@v5
|
|
107
|
+
with:
|
|
108
|
+
python-version: ${{ matrix.python-version }}
|
|
109
|
+
|
|
110
|
+
- name: Format Source Code
|
|
111
|
+
uses: i2mint/wads/actions/ruff-format@master
|
|
112
|
+
|
|
113
|
+
- name: Update Version Number
|
|
114
|
+
id: version
|
|
115
|
+
uses: i2mint/isee/actions/bump-version-number@master
|
|
116
|
+
|
|
117
|
+
- name: Build Distribution
|
|
118
|
+
uses: i2mint/wads/actions/build-dist@master
|
|
119
|
+
with:
|
|
120
|
+
sdist: true
|
|
121
|
+
wheel: true
|
|
122
|
+
# Uncomment for private dependencies:
|
|
123
|
+
# with:
|
|
124
|
+
# ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
125
|
+
|
|
126
|
+
- name: Publish to PyPI
|
|
127
|
+
uses: i2mint/wads/actions/pypi-upload@master
|
|
128
|
+
with:
|
|
129
|
+
pypi-username: ${{ secrets.PYPI_USERNAME }}
|
|
130
|
+
pypi-password: ${{ secrets.PYPI_PASSWORD }}
|
|
131
|
+
skip-existing: false
|
|
132
|
+
|
|
133
|
+
- name: Track Code Metrics
|
|
134
|
+
uses: i2mint/umpyre/actions/track-metrics@master
|
|
135
|
+
continue-on-error: true # Don't fail CI if metrics collection fails
|
|
136
|
+
with:
|
|
137
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
138
|
+
config-path: .github/umpyre-config.yml # Optional: defaults to .umpyre.yml
|
|
139
|
+
|
|
140
|
+
- name: Commit Changes
|
|
141
|
+
uses: i2mint/wads/actions/git-commit@master
|
|
142
|
+
with:
|
|
143
|
+
commit-message: "**CI** Formatted code + Updated version to ${{ env.VERSION }} [skip ci]"
|
|
144
|
+
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
145
|
+
push: true
|
|
146
|
+
|
|
147
|
+
- name: Tag Repository
|
|
148
|
+
uses: i2mint/wads/actions/git-tag@master
|
|
149
|
+
with:
|
|
150
|
+
tag: ${{ env.VERSION }}
|
|
151
|
+
message: "Release version ${{ env.VERSION }}"
|
|
152
|
+
push: true
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
github-pages:
|
|
156
|
+
name: Publish GitHub Pages
|
|
157
|
+
|
|
158
|
+
permissions:
|
|
159
|
+
contents: write
|
|
160
|
+
pages: write
|
|
161
|
+
id-token: write
|
|
162
|
+
|
|
163
|
+
if: "!contains(github.event.head_commit.message, '[skip ci]') && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)"
|
|
164
|
+
needs: publish
|
|
165
|
+
runs-on: ubuntu-latest
|
|
166
|
+
|
|
167
|
+
steps:
|
|
168
|
+
- uses: i2mint/epythet/actions/publish-github-pages@master
|
|
169
|
+
with:
|
|
170
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
171
|
+
ignore: "tests/,scrap/,examples/"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
docs/*
|
|
2
|
+
docsrc/*
|
|
3
|
+
wads_configs.json
|
|
4
|
+
data/wads_configs.json
|
|
5
|
+
wads/data/wads_configs.json
|
|
6
|
+
|
|
7
|
+
# Byte-compiled / optimized / DLL files
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*$py.class
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
.DS_Store
|
|
14
|
+
# C extensions
|
|
15
|
+
*.so
|
|
16
|
+
|
|
17
|
+
# Distribution / packaging
|
|
18
|
+
.Python
|
|
19
|
+
build/
|
|
20
|
+
develop-eggs/
|
|
21
|
+
dist/
|
|
22
|
+
downloads/
|
|
23
|
+
eggs/
|
|
24
|
+
.eggs/
|
|
25
|
+
lib/
|
|
26
|
+
lib64/
|
|
27
|
+
parts/
|
|
28
|
+
sdist/
|
|
29
|
+
var/
|
|
30
|
+
wheels/
|
|
31
|
+
*.egg-info/
|
|
32
|
+
.installed.cfg
|
|
33
|
+
*.egg
|
|
34
|
+
MANIFEST
|
|
35
|
+
_build
|
|
36
|
+
|
|
37
|
+
# PyInstaller
|
|
38
|
+
# Usually these files are written by a python script from a template
|
|
39
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
40
|
+
*.manifest
|
|
41
|
+
*.spec
|
|
42
|
+
|
|
43
|
+
# Installer logs
|
|
44
|
+
pip-log.txt
|
|
45
|
+
pip-delete-this-directory.txt
|
|
46
|
+
|
|
47
|
+
# Unit test / coverage reports
|
|
48
|
+
htmlcov/
|
|
49
|
+
.tox/
|
|
50
|
+
.coverage
|
|
51
|
+
.coverage.*
|
|
52
|
+
.cache
|
|
53
|
+
nosetests.xml
|
|
54
|
+
coverage.xml
|
|
55
|
+
*.cover
|
|
56
|
+
.hypothesis/
|
|
57
|
+
.pytest_cache/
|
|
58
|
+
|
|
59
|
+
# Translations
|
|
60
|
+
*.mo
|
|
61
|
+
*.pot
|
|
62
|
+
|
|
63
|
+
# Django stuff:
|
|
64
|
+
*.log
|
|
65
|
+
local_settings.py
|
|
66
|
+
db.sqlite3
|
|
67
|
+
|
|
68
|
+
# Flask stuff:
|
|
69
|
+
instance/
|
|
70
|
+
.webassets-cache
|
|
71
|
+
|
|
72
|
+
# Scrapy stuff:
|
|
73
|
+
.scrapy
|
|
74
|
+
|
|
75
|
+
# Sphinx documentation
|
|
76
|
+
docs/_build/
|
|
77
|
+
|
|
78
|
+
# PyBuilder
|
|
79
|
+
target/
|
|
80
|
+
|
|
81
|
+
# Jupyter Notebook
|
|
82
|
+
.ipynb_checkpoints
|
|
83
|
+
|
|
84
|
+
# pyenv
|
|
85
|
+
.python-version
|
|
86
|
+
|
|
87
|
+
# celery beat schedule file
|
|
88
|
+
celerybeat-schedule
|
|
89
|
+
|
|
90
|
+
# SageMath parsed files
|
|
91
|
+
*.sage.py
|
|
92
|
+
|
|
93
|
+
# Environments
|
|
94
|
+
.env
|
|
95
|
+
.venv
|
|
96
|
+
env/
|
|
97
|
+
venv/
|
|
98
|
+
ENV/
|
|
99
|
+
env.bak/
|
|
100
|
+
venv.bak/
|
|
101
|
+
|
|
102
|
+
# Spyder project settings
|
|
103
|
+
.spyderproject
|
|
104
|
+
.spyproject
|
|
105
|
+
|
|
106
|
+
# Rope project settings
|
|
107
|
+
.ropeproject
|
|
108
|
+
|
|
109
|
+
# mkdocs documentation
|
|
110
|
+
/site
|
|
111
|
+
|
|
112
|
+
# mypy
|
|
113
|
+
.mypy_cache/
|
|
114
|
+
|
|
115
|
+
# PyCharm
|
|
116
|
+
.idea
|
|
117
|
+
|
|
118
|
+
# Notebook playground
|
|
119
|
+
vf.ipynb
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: config2py
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.45
|
|
4
4
|
Summary: Simplified reading and writing configurations from various sources and formats
|
|
5
|
-
|
|
6
|
-
License:
|
|
7
|
-
Platform: any
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
5
|
+
Project-URL: Homepage, https://github.com/i2mint/config2py
|
|
6
|
+
License: Apache-2.0
|
|
9
7
|
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
10
9
|
Requires-Dist: dol
|
|
11
10
|
Requires-Dist: i2
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
|
|
11
|
+
Requires-Dist: importlib-resources; python_version < '3.9'
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
16
|
+
Provides-Extra: docs
|
|
17
|
+
Requires-Dist: sphinx-rtd-theme>=1.0; extra == 'docs'
|
|
18
|
+
Requires-Dist: sphinx>=6.0; extra == 'docs'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
14
20
|
|
|
15
21
|
# config2py
|
|
16
22
|
|
|
@@ -266,21 +266,38 @@ class FileStore(SyncStore):
|
|
|
266
266
|
dumper: Optional custom dumper (auto-detected from extension if not provided)
|
|
267
267
|
mode: File read mode ('r' for text, 'rb' for binary)
|
|
268
268
|
dump_kwargs: Additional kwargs for dumper
|
|
269
|
+
create_file_content: Optional factory callable that returns initial dict content
|
|
270
|
+
for missing files. If None, FileNotFoundError is raised for missing files.
|
|
271
|
+
create_key_path_content: Optional factory callable that returns initial content
|
|
272
|
+
for missing key_path. If None, KeyError is raised for missing key paths.
|
|
269
273
|
|
|
270
274
|
Example:
|
|
271
275
|
>>> import tempfile
|
|
276
|
+
>>> import os
|
|
277
|
+
>>>
|
|
278
|
+
>>> # Basic usage with existing file
|
|
272
279
|
>>> with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
273
280
|
... _ = f.write('{"section": {"key": "value"}}')
|
|
274
281
|
... temp_file = f.name
|
|
275
282
|
>>>
|
|
276
|
-
>>> # Work with nested section
|
|
277
283
|
>>> section = FileStore(temp_file, key_path='section')
|
|
278
284
|
>>> section['key']
|
|
279
285
|
'value'
|
|
280
286
|
>>> section['new'] = 'data'
|
|
281
|
-
>>>
|
|
282
|
-
>>> import os
|
|
283
287
|
>>> os.unlink(temp_file)
|
|
288
|
+
>>>
|
|
289
|
+
>>> # Auto-create missing file and key_path
|
|
290
|
+
>>> with tempfile.TemporaryDirectory() as tmpdir:
|
|
291
|
+
... new_file = os.path.join(tmpdir, 'config.json')
|
|
292
|
+
... store = FileStore(
|
|
293
|
+
... new_file,
|
|
294
|
+
... key_path='servers',
|
|
295
|
+
... create_file_content=lambda: {},
|
|
296
|
+
... create_key_path_content=lambda: {}
|
|
297
|
+
... )
|
|
298
|
+
... store['myserver'] = {'command': 'python'}
|
|
299
|
+
... 'myserver' in store
|
|
300
|
+
True
|
|
284
301
|
"""
|
|
285
302
|
|
|
286
303
|
def __init__(
|
|
@@ -292,11 +309,15 @@ class FileStore(SyncStore):
|
|
|
292
309
|
dumper: Optional[Callable[[dict], str]] = None,
|
|
293
310
|
mode: str = "r",
|
|
294
311
|
dump_kwargs: Optional[dict] = None,
|
|
312
|
+
create_file_content: Optional[Callable[[], dict]] = None,
|
|
313
|
+
create_key_path_content: Optional[Callable[[], Any]] = None,
|
|
295
314
|
):
|
|
296
315
|
self.filepath = Path(filepath).expanduser()
|
|
297
316
|
self.key_path = _normalize_key_path(key_path)
|
|
298
317
|
self.mode = mode
|
|
299
318
|
self.dump_kwargs = dump_kwargs or {}
|
|
319
|
+
self.create_file_content = create_file_content
|
|
320
|
+
self.create_key_path_content = create_key_path_content
|
|
300
321
|
|
|
301
322
|
# Auto-detect format if not provided
|
|
302
323
|
if loader is None or dumper is None:
|
|
@@ -318,10 +339,43 @@ class FileStore(SyncStore):
|
|
|
318
339
|
|
|
319
340
|
def _load_from_file(self) -> dict:
|
|
320
341
|
"""Read and parse file, returning the section specified by key_path."""
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
342
|
+
# Handle missing file
|
|
343
|
+
if not self.filepath.exists():
|
|
344
|
+
if self.create_file_content is None:
|
|
345
|
+
raise FileNotFoundError(f"File not found: {self.filepath}")
|
|
346
|
+
|
|
347
|
+
# Create file with initial content
|
|
348
|
+
initial_data = self.create_file_content()
|
|
349
|
+
self.filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
350
|
+
content = self._file_dumper(initial_data, **self.dump_kwargs)
|
|
351
|
+
write_mode = "w" if "b" not in self.mode else "wb"
|
|
352
|
+
with open(self.filepath, write_mode) as f:
|
|
353
|
+
f.write(content)
|
|
354
|
+
data = initial_data
|
|
355
|
+
else:
|
|
356
|
+
# Load existing file
|
|
357
|
+
with open(self.filepath, self.mode) as f:
|
|
358
|
+
content = f.read()
|
|
359
|
+
data = self._file_loader(content)
|
|
360
|
+
|
|
361
|
+
# Handle missing key_path
|
|
362
|
+
try:
|
|
363
|
+
return _get_nested(data, self.key_path)
|
|
364
|
+
except (KeyError, TypeError):
|
|
365
|
+
if self.create_key_path_content is None:
|
|
366
|
+
raise KeyError(f"Key path not found: {self.key_path}")
|
|
367
|
+
|
|
368
|
+
# Create key_path with initial content
|
|
369
|
+
initial_content = self.create_key_path_content()
|
|
370
|
+
full_data = _set_nested(data, self.key_path, initial_content)
|
|
371
|
+
|
|
372
|
+
# Write back to file
|
|
373
|
+
content = self._file_dumper(full_data, **self.dump_kwargs)
|
|
374
|
+
write_mode = "w" if "b" not in self.mode else "wb"
|
|
375
|
+
with open(self.filepath, write_mode) as f:
|
|
376
|
+
f.write(content)
|
|
377
|
+
|
|
378
|
+
return initial_content
|
|
325
379
|
|
|
326
380
|
def _dump_to_file(self, section_data: dict) -> None:
|
|
327
381
|
"""Write data to file, updating only the section specified by key_path."""
|