@@ -0,0 +1,160 @@ | |||||
# Byte-compiled / optimized / DLL files | |||||
__pycache__/ | |||||
*.py[cod] | |||||
*$py.class | |||||
# C extensions | |||||
*.so | |||||
# Distribution / packaging | |||||
.Python | |||||
build/ | |||||
develop-eggs/ | |||||
dist/ | |||||
downloads/ | |||||
eggs/ | |||||
.eggs/ | |||||
lib/ | |||||
lib64/ | |||||
parts/ | |||||
sdist/ | |||||
var/ | |||||
wheels/ | |||||
share/python-wheels/ | |||||
*.egg-info/ | |||||
.installed.cfg | |||||
*.egg | |||||
MANIFEST | |||||
# PyInstaller | |||||
# Usually these files are written by a python script from a template | |||||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | |||||
*.manifest | |||||
*.spec | |||||
# Installer logs | |||||
pip-log.txt | |||||
pip-delete-this-directory.txt | |||||
# Unit test / coverage reports | |||||
htmlcov/ | |||||
.tox/ | |||||
.nox/ | |||||
.coverage | |||||
.coverage.* | |||||
.cache | |||||
nosetests.xml | |||||
coverage.xml | |||||
*.cover | |||||
*.py,cover | |||||
.hypothesis/ | |||||
.pytest_cache/ | |||||
cover/ | |||||
# Translations | |||||
*.mo | |||||
*.pot | |||||
# Django stuff: | |||||
*.log | |||||
local_settings.py | |||||
db.sqlite3 | |||||
db.sqlite3-journal | |||||
# Flask stuff: | |||||
instance/ | |||||
.webassets-cache | |||||
# Scrapy stuff: | |||||
.scrapy | |||||
# Sphinx documentation | |||||
docs/_build/ | |||||
# PyBuilder | |||||
.pybuilder/ | |||||
target/ | |||||
# Jupyter Notebook | |||||
.ipynb_checkpoints | |||||
# IPython | |||||
profile_default/ | |||||
ipython_config.py | |||||
# pyenv | |||||
# For a library or package, you might want to ignore these files since the code is | |||||
# intended to run in multiple environments; otherwise, check them in: | |||||
# .python-version | |||||
# pipenv | |||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | |||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies | |||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not | |||||
# install all needed dependencies. | |||||
#Pipfile.lock | |||||
# poetry | |||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. | |||||
# This is especially recommended for binary packages to ensure reproducibility, and is more | |||||
# commonly ignored for libraries. | |||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control | |||||
#poetry.lock | |||||
# pdm | |||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. | |||||
#pdm.lock | |||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it | |||||
# in version control. | |||||
# https://pdm.fming.dev/#use-with-ide | |||||
.pdm.toml | |||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm | |||||
__pypackages__/ | |||||
# Celery stuff | |||||
celerybeat-schedule | |||||
celerybeat.pid | |||||
# SageMath parsed files | |||||
*.sage.py | |||||
# Environments | |||||
.env | |||||
.venv | |||||
env/ | |||||
venv/ | |||||
ENV/ | |||||
env.bak/ | |||||
venv.bak/ | |||||
# Spyder project settings | |||||
.spyderproject | |||||
.spyproject | |||||
# Rope project settings | |||||
.ropeproject | |||||
# mkdocs documentation | |||||
/site | |||||
# mypy | |||||
.mypy_cache/ | |||||
.dmypy.json | |||||
dmypy.json | |||||
# Pyre type checker | |||||
.pyre/ | |||||
# pytype static type analyzer | |||||
.pytype/ | |||||
# Cython debug symbols | |||||
cython_debug/ | |||||
# PyCharm | |||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can | |||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore | |||||
# and can be added to the global gitignore or merged into this file. For a more nuclear | |||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder. | |||||
#.idea/ |
@@ -0,0 +1,13 @@ | |||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |||||
Version 2, December 2004 | |||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> | |||||
Everyone is permitted to copy and distribute verbatim or modified | |||||
copies of this license document, and changing it is allowed as long | |||||
as the name is changed. | |||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |||||
0. You just DO WHAT THE FUCK YOU WANT TO. |
@@ -0,0 +1,22 @@ | |||||
A really simple library for accessing dictionary items using paths. Meant to make working with configuration files easier. | |||||
## quick-n-dirty documentalation | |||||
The class you want is `confthing.Config(data, delimiter = "/", defaults = {}, requirements = [], **misc_options)`. | |||||
- `data` is the dict you want to wrap the class around. | |||||
- `delimiter` is whatever you want to use as a path delimiter. | |||||
- `defaults` is a dict whose keys are paths and values are the default values for each path. | |||||
- `requirements` is a list of paths that must be defined, otherwise MissingRequirementError is raised. | |||||
- Miscellaneous options are as follows: | |||||
- `strict_subscript`: when True, enables strict mode when getting items via subscript. Default is False. | |||||
- `clobber_subscript`: when True, enables clobber mode when setting items via subscript. Default is False. | |||||
### Methods | |||||
#### `get(path, fallback = None, strict = False)` | |||||
Tries to return the value at the specified path, otherwise returns the fallback value (or raises PathNotFoundError if `strict` is True). | |||||
#### `set(path, value, clobber = False)` | |||||
Tries to set the value at the specified path. If it encounters an item that isn't a dict, it'll raise NotDictError if `clobber` is False, or it'll replace that with an empty dict. | |||||
#### Subscripting | |||||
Config objects are subscriptable, calling `get` and `set` as appropriate. The two `subscript` options control what `strict` and `clobber` are set to when subscripting. |
@@ -0,0 +1,21 @@ | |||||
[build-system] | |||||
requires = ["hatchling"] | |||||
build-backend = "hatchling.build" | |||||
[project] | |||||
name = "confthing" | |||||
version = "0.1" | |||||
authors = [ | |||||
{ name="whut", email="watewhut@aaathats3as.com" }, | |||||
] | |||||
description = "Allows working with dicts using path strings" | |||||
readme = "README.md" | |||||
requires-python = ">=3.7" | |||||
classifiers = [ | |||||
"Programming Language :: Python :: 3", | |||||
"License :: Other/Proprietary License", | |||||
"Operating System :: OS Independent", | |||||
] | |||||
[project.urls] | |||||
"Homepage" = "https://git.lain.church/whut/confthing" |
@@ -0,0 +1,13 @@ | |||||
alpha: | |||||
aleph: | |||||
a: 12345 | |||||
b: abcde | |||||
c: ["i","ro","ha"] | |||||
bet: 123 | |||||
gimel: "abc" | |||||
dalet: | |||||
a: | |||||
b: | |||||
c: | |||||
d: "hey" | |||||
@@ -0,0 +1,70 @@ | |||||
class PathError(Exception): | |||||
def __init__(self, path): | |||||
self.path = path | |||||
super().__init__(self.path) | |||||
class PathNotFoundError(PathError): | |||||
pass | |||||
class MissingRequirementError(PathError): | |||||
pass | |||||
class NotDictError(PathError): | |||||
pass | |||||
class Config: | |||||
def __init__(self, data, delimiter = "/", defaults = {}, requirements = [], **misc_options): | |||||
self.data = data | |||||
self.delimiter = delimiter | |||||
self.strict_subscript = misc_options.get("strict_subscript", False) | |||||
self.clobber_subscript = misc_options.get("clobber_subscript", False) | |||||
for r in requirements: | |||||
if not self.get(r): raise MissingRequirementError(r) | |||||
for d in defaults: | |||||
try: | |||||
self.get(d, strict = True) | |||||
except PathNotFoundError: | |||||
self.set(d, defaults[d]) | |||||
def split_path(self, path): | |||||
return path.split(self.delimiter) | |||||
def get(self, path, fallback = None, strict = False): | |||||
elementsAll = self.split_path(path) | |||||
def g(elems = elementsAll, obj = self.data): | |||||
if not elems or not elems[0] or type(obj) != dict: return obj | |||||
head, tail = elems[0], elems[1:] | |||||
if strict and head not in obj: | |||||
raise PathNotFoundError(path) | |||||
return g(tail, obj.get(head, fallback)) | |||||
return g() | |||||
def set(self, path, value, clobber = False): | |||||
elementsAll = self.split_path(path) | |||||
def s(elems = elementsAll, obj = self.data): | |||||
head, tail = elems[0], elems[1:] | |||||
# YANDEV MODE ACTIVATE!!! | |||||
if not tail: | |||||
obj[head] = value | |||||
else: | |||||
if head not in obj or type(obj[head]) != dict: | |||||
if clobber: | |||||
obj[head] = {} | |||||
else: | |||||
raise NotDictError(self.delimiter.join(elementsAll[:-len(tail)])) | |||||
s(tail, obj[head]) | |||||
s() | |||||
def __getitem__(self, path): | |||||
return self.get(path, self.strict_subscript) | |||||
def __setitem__(self, path, value): | |||||
self.set(path, value, self.clobber_subscript) | |||||
@@ -0,0 +1,69 @@ | |||||
import yaml | |||||
class PathError(Exception): | |||||
def __init__(self, path): | |||||
self.path = path | |||||
super().__init__(self.path) | |||||
class PathNotFoundError(PathError): | |||||
pass | |||||
class MissingRequirementError(PathError): | |||||
pass | |||||
class NotDictError(PathError): | |||||
pass | |||||
class Config: | |||||
def __init__(self, data, delimiter = "/", defaults = {}, requirements = [], **misc_options): | |||||
self.data = yaml.safe_load(open(data)) if type(data) != dict else data | |||||
self.delimiter = delimiter | |||||
self.strict_subscript = misc_options.get("strict_subscript", False) | |||||
self.clobber_subscript = misc_options.get("clobber_subscript", False) | |||||
for r in requirements: | |||||
if not self.get(r): raise MissingRequirementError(r) | |||||
for d in defaults: | |||||
if not self.get(d): self.set(d, defaults[d]) | |||||
def split_path(self, path): | |||||
return path.split(self.delimiter) | |||||
def get(self, path, fallback = None, strict = False): | |||||
elementsAll = self.split_path(path) | |||||
def g(elems = elementsAll, obj = self.data): | |||||
if not elems or not elems[0] or type(obj) != dict: return obj | |||||
head, tail = elems[0], elems[1:] | |||||
if strict and head not in obj: | |||||
raise PathNotFoundError(path) | |||||
return g(tail, obj.get(head, fallback)) | |||||
return g() | |||||
def set(self, path, value, clobber = False): | |||||
elementsAll = self.split_path(path) | |||||
def s(elems = elementsAll, obj = self.data): | |||||
head, tail = elems[0], elems[1:] | |||||
# YANDEV MODE ACTIVATE!!! | |||||
if not tail: | |||||
obj[head] = value | |||||
else: | |||||
if head not in obj or type(obj[head]) != dict: | |||||
if clobber: | |||||
obj[head] = {} | |||||
else: | |||||
raise NotDictError(self.delimiter.join(elementsAll[:-len(tail)])) | |||||
s(tail, obj[head]) | |||||
s() | |||||
def __getitem__(self, path): | |||||
return self.get(path, self.strict_subscript) | |||||
def __setitem__(self, path, value): | |||||
self.set(path, value, self.clobber_subscript) | |||||
@@ -0,0 +1,17 @@ | |||||
#!/usr/bin/env python3 | |||||
import confthing, yaml | |||||
conf = confthing.Config(yaml.safe_load(open("config-test.yaml")), defaults = { | |||||
"alpha/aleph/llll": 1337 | |||||
}, requirements = [ | |||||
"alpha/bet" | |||||
], clobber_subscript = True) | |||||
test = lambda n: print("\"%s\"\n\t%s\n" % (n, conf[n])) | |||||
test("") | |||||
test("alpha/aleph") | |||||
conf["alpha/aleph"] = {"i": "now set to a string"} | |||||
test("alpha/aleph/i") | |||||
conf["alpha/aleph/i/one/two/three"] = 12345 | |||||
test("alpha/aleph/i") |