mirror of
https://github.com/Steffo99/cfig.git
synced 2024-11-24 08:54:20 +00:00
💥 yay more things
This commit is contained in:
parent
aeb382fcde
commit
775c8c8a38
15 changed files with 324 additions and 26 deletions
14
cfig.iml
14
cfig.iml
|
@ -5,8 +5,22 @@
|
|||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/cfig" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/cfig/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/docs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/docs/_extra" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/docs/_static" type="java-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/docs/_build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pytest_cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/cfig/.pytest_cache" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyNamespacePackagesService">
|
||||
<option name="namespacePackageFolders">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/cfig/sources" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</module>
|
|
@ -4,11 +4,27 @@ This package provides a simple but powerful configuration manager for Python app
|
|||
A goal is to allow easy integration of an application with multiple configuration standards, such as environment
|
||||
variables, dotenv files, and Docker Secrets files.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@config.required()
|
||||
def SECRET_KEY(val: str) -> str:
|
||||
"Secret string used to manage tokens."
|
||||
return val
|
||||
|
||||
Another goal is to provide informative error messages to the user who is configuring the application, so that they may
|
||||
understand what they are doing wrong and fix it immediately.
|
||||
|
||||
The final goal is for the package to be fully typed, so that useful information can be received by the developer
|
||||
programming the consumption the configuration files.
|
||||
.. code-block:: console
|
||||
|
||||
$ python -m cfig.sample
|
||||
=== Configuration ===
|
||||
|
||||
SECRET_KEY → Required, but not set.
|
||||
Secret string used to manage HTTP session tokens.
|
||||
|
||||
HIDDEN_STRING = 'amogus'
|
||||
A string which may be provided to silently print a string to the console.
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
@ -46,8 +62,8 @@ application::
|
|||
return sqlalchemy.create_engine(val)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# TODO: If the configuration file is executed as main, handle the call and display a user-friendly CLI interface.
|
||||
config()
|
||||
# If the configuration file is executed as main, handle the call and display a user-friendly CLI interface.
|
||||
config.cli()
|
||||
|
||||
Values can later be accessed by the program by importing the configuration file:
|
||||
|
||||
|
@ -66,11 +82,11 @@ Values can later be accessed by the program by importing the configuration file:
|
|||
Terminology
|
||||
===========
|
||||
|
||||
In this documentation, some terminology is used repeatedly:
|
||||
In this documentation, the following terms are used:
|
||||
|
||||
Configuration key
|
||||
Key
|
||||
The name of a configuration value, usually in SCREAMING_SNAKE_CASE.
|
||||
For example, `PATH`, the name of the environment variable.
|
||||
For example, ``PATH``, the name of the environment variable.
|
||||
|
||||
Value
|
||||
A single non-processed configuration value in :class:`str` form.
|
||||
|
|
|
@ -131,7 +131,7 @@ class Configuration:
|
|||
log.debug("Item created successfully!")
|
||||
|
||||
log.debug("Registering item in the configuration...")
|
||||
self.register(key, item, doc or configurable.__doc__)
|
||||
self.register(key, item, doc if doc is not None else configurable.__doc__)
|
||||
log.debug("Registered successfully!")
|
||||
|
||||
# Return the created item, so it will take the place of the decorated function
|
||||
|
@ -168,7 +168,7 @@ class Configuration:
|
|||
log.debug("Item created successfully!")
|
||||
|
||||
log.debug("Registering item in the configuration...")
|
||||
self.register(key, item, doc or configurable.__doc__)
|
||||
self.register(key, item, doc if doc is not None else configurable.__doc__)
|
||||
log.debug("Registered successfully!")
|
||||
|
||||
# Return the created item, so it will take the place of the decorated function
|
||||
|
@ -215,11 +215,8 @@ class Configuration:
|
|||
val = self._retrieve_value_optional(key)
|
||||
log.debug("Retrieved value successfully!")
|
||||
|
||||
if val is None:
|
||||
log.debug(f"Not running user-defined configurable function since value is {val!r}.")
|
||||
else:
|
||||
log.debug("Running user-defined configurable function...")
|
||||
val = resolver(val)
|
||||
log.debug("Running user-defined configurable function...")
|
||||
val = resolver(val)
|
||||
|
||||
return val
|
||||
|
||||
|
@ -237,7 +234,7 @@ class Configuration:
|
|||
else:
|
||||
raise errors.MissingValueError(key)
|
||||
|
||||
def _create_proxy_required(self, key: str, f: ct.ResolverRequired) -> lazy_object_proxy.Proxy:
|
||||
def _create_proxy_required(self, key: str, resolver: ct.ResolverRequired) -> lazy_object_proxy.Proxy:
|
||||
"""
|
||||
Create, from a resolver, a proxy intolerant about non-specified values.
|
||||
"""
|
||||
|
@ -249,7 +246,7 @@ class Configuration:
|
|||
log.debug("Retrieved val successfully!")
|
||||
|
||||
log.debug("Running user-defined configurable function...")
|
||||
val = f(val)
|
||||
val = resolver(val)
|
||||
|
||||
return val
|
||||
|
||||
|
@ -262,7 +259,7 @@ class Configuration:
|
|||
:param key: The configuration key to register the proxy to.
|
||||
:param proxy: The proxy to register in :attr:`.proxies`.
|
||||
:param doc: The docstring to register in :attr:`.docs`.
|
||||
:raises .errors.DuplicateProxyNameError` if the key already exists in either :attr:`.proxies` or :attr:`.docs`.
|
||||
:raises .errors.DuplicateProxyNameError`: if the key already exists in either :attr:`.proxies` or :attr:`.docs`.
|
||||
"""
|
||||
|
||||
if key in self.proxies:
|
||||
|
|
|
@ -21,7 +21,7 @@ def MY_FAVOURITE_STRING(val: str) -> str:
|
|||
@config.optional()
|
||||
def MY_OPTIONAL_STRING(val: typing.Optional[str]) -> str:
|
||||
"""
|
||||
Your favourite string, including the empty one!
|
||||
Your favourite string, but optional!
|
||||
"""
|
||||
return val or ""
|
||||
|
||||
|
@ -51,5 +51,13 @@ def MY_FAVOURITE_EVEN_INT(val: str) -> int:
|
|||
return n
|
||||
|
||||
|
||||
@config.required(key="KEY_NAME")
|
||||
def VAR_NAME(val: str) -> str:
|
||||
"""
|
||||
This config value looks for a key in the configuration sources but is available at a different key to the programmer.
|
||||
"""
|
||||
return val
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config.cli()
|
||||
|
|
|
@ -8,7 +8,7 @@ class EnvironmentSource(Source):
|
|||
A source which gets values from environment variables.
|
||||
"""
|
||||
|
||||
def __init__(self, *, prefix: str = "", suffix: str = "", environment=os.environ):
|
||||
def __init__(self, *, prefix: str = "", suffix: str = "", environment=None):
|
||||
self.prefix: str = prefix
|
||||
"""
|
||||
The prefix to be prepended to all environment variable names.
|
||||
|
@ -23,7 +23,7 @@ class EnvironmentSource(Source):
|
|||
For example, ``_VAL`` for raw values.
|
||||
"""
|
||||
|
||||
self.environment = environment
|
||||
self.environment = environment if environment is not None else os.environ
|
||||
"""
|
||||
The environment to retrieve variable values from.
|
||||
|
||||
|
@ -36,4 +36,3 @@ class EnvironmentSource(Source):
|
|||
def get(self, key: str) -> t.Optional[str]:
|
||||
key = self._process_key(key)
|
||||
return self.environment.get(key)
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import os
|
||||
import typing as t
|
||||
from cfig.sources.env import EnvironmentSource
|
||||
|
||||
|
@ -6,9 +5,11 @@ from cfig.sources.env import EnvironmentSource
|
|||
class EnvironmentFileSource(EnvironmentSource):
|
||||
"""
|
||||
A source which gets values from files at paths specified in environment variables.
|
||||
|
||||
Useful for example with Docker Secrets.
|
||||
"""
|
||||
|
||||
def __init__(self, *, prefix: str = "", suffix: str = "_FILE", environment=os.environ):
|
||||
def __init__(self, *, prefix: str = "", suffix: str = "_FILE", environment=None):
|
||||
super().__init__(prefix=prefix, suffix=suffix, environment=environment)
|
||||
|
||||
def get(self, key: str) -> t.Optional[str]:
|
||||
|
|
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
0
docs/_extra/.gitignore
vendored
Normal file
0
docs/_extra/.gitignore
vendored
Normal file
0
docs/_static/.gitignore
vendored
Normal file
0
docs/_static/.gitignore
vendored
Normal file
0
docs/_templates/.gitignore
vendored
Normal file
0
docs/_templates/.gitignore
vendored
Normal file
145
docs/conf.py
Normal file
145
docs/conf.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
# Extended Sphinx configuration
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
|
||||
###########
|
||||
# Imports #
|
||||
###########
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
###########################
|
||||
# Developer configuration #
|
||||
###########################
|
||||
# Alter these to reflect the nature of your project!
|
||||
|
||||
# Project name
|
||||
project = 'cfig'
|
||||
# Project author
|
||||
author = 'Stefano Pigozzi'
|
||||
# Project copyright
|
||||
project_copyright = f'{datetime.date.today().year}, {author}'
|
||||
|
||||
# Sphinx language
|
||||
language = "en"
|
||||
|
||||
# Configuration for the theme
|
||||
html_theme_options = {
|
||||
# Set this to the main color of your project
|
||||
"style_nav_header_background": "#b72f37",
|
||||
}
|
||||
|
||||
|
||||
##########################
|
||||
# Advanced configuration #
|
||||
##########################
|
||||
# Change these options only if you need further customization
|
||||
|
||||
# Sphinx extensions
|
||||
extensions = [
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.autosectionlabel",
|
||||
"sphinx.ext.todo",
|
||||
]
|
||||
|
||||
# Source files encoding
|
||||
source_encoding = "UTF-8"
|
||||
# Source file extensions
|
||||
source_suffix = {
|
||||
".rst": "restructuredtext",
|
||||
}
|
||||
# Source files parsers
|
||||
source_parsers = {}
|
||||
|
||||
# The doc from which to start rendering
|
||||
root_doc = "index"
|
||||
# Files to ignore when rendering
|
||||
exclude_patterns = [
|
||||
"build",
|
||||
"_build",
|
||||
"Thumbs.db",
|
||||
".DS_Store",
|
||||
]
|
||||
# Sphinx template files
|
||||
templates_path = [
|
||||
'_templates',
|
||||
]
|
||||
|
||||
# Prologue of all rst files
|
||||
rst_prolog = ""
|
||||
# Epilogue of all rst files
|
||||
rst_epilog = ""
|
||||
|
||||
# Default domain
|
||||
primary_domain = "py"
|
||||
# Default role
|
||||
default_role = "any"
|
||||
|
||||
# Print warnings on the page
|
||||
keep_warnings = False
|
||||
# Display more warnings than usual
|
||||
nitpicky = False
|
||||
|
||||
# Intersphinx URLs
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3.10/", None),
|
||||
"lazy-object-proxy": ("https://python-lazy-object-proxy.readthedocs.io/en/latest/", None),
|
||||
"click": ("https://click.palletsprojects.com/en/latest/", None),
|
||||
}
|
||||
# Manpages URL
|
||||
manpages_url = "https://man.archlinux.org/"
|
||||
|
||||
# HTML builder theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
# Title of the HTML page
|
||||
html_title = f"{project}"
|
||||
# Short title of the HTML page
|
||||
html_short_title = f"{project}"
|
||||
# Path of the documentation static files
|
||||
html_static_path = [
|
||||
"_static",
|
||||
]
|
||||
# Path of extra files to add to the build
|
||||
html_extra_path = [
|
||||
"_extra",
|
||||
]
|
||||
# Disable additional indexes
|
||||
html_domain_indices = False
|
||||
|
||||
# LaTeX rendering engine to use
|
||||
latex_engine = "lualatex"
|
||||
# LaTeX top level title type
|
||||
latex_toplevel_sectioning = "chapter"
|
||||
# LaTeX URLs rendering
|
||||
latex_show_urls = "footnote"
|
||||
# LaTeX theme
|
||||
latex_theme = "manual"
|
||||
|
||||
# TODOs
|
||||
todo_include_todos = True
|
||||
todo_emit_warnings = True
|
||||
todo_link_only = False
|
||||
|
||||
# Smartquotes
|
||||
smartquotes_excludes = {
|
||||
"languages": [
|
||||
# Smartquotes is completely broken in italian!
|
||||
"it",
|
||||
# Keep the default, just in case
|
||||
"ja",
|
||||
],
|
||||
"builders": [
|
||||
"man",
|
||||
"text",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# Autodoc
|
||||
autodoc_member_order = "bysource"
|
||||
autodoc_default_options = {
|
||||
"members": True,
|
||||
"undoc-members": True,
|
||||
}
|
63
docs/index.rst
Normal file
63
docs/index.rst
Normal file
|
@ -0,0 +1,63 @@
|
|||
####
|
||||
cfig
|
||||
####
|
||||
|
||||
|
||||
.. automodule:: cfig
|
||||
|
||||
|
||||
Classes
|
||||
=======
|
||||
|
||||
:mod:`cfig.config`
|
||||
------------------
|
||||
|
||||
.. automodule:: cfig.config
|
||||
|
||||
|
||||
:mod:`cfig.errors`
|
||||
------------------
|
||||
|
||||
.. automodule:: cfig.errors
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Built-in sources
|
||||
----------------
|
||||
|
||||
:mod:`cfig.sources.base`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: cfig.sources.base
|
||||
|
||||
|
||||
:mod:`cfig.sources.env`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: cfig.sources.env
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
:mod:`cfig.sources.envfile`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: cfig.sources.envfile
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Table of contents
|
||||
=================
|
||||
|
||||
.. error::
|
||||
|
||||
I think I broke Sphinx, since the table of contents doesn't seem to show up...
|
||||
|
||||
.. toctree::
|
||||
|
||||
|
||||
Other tables and links
|
||||
======================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
|
@ -0,0 +1,35 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
4
poetry.lock
generated
4
poetry.lock
generated
|
@ -383,12 +383,12 @@ secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "cer
|
|||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[extras]
|
||||
cli = ["click"]
|
||||
cli = ["click", "colorama"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "9003e7beceb925ef6b11961ef2a2bb818fbd8f659b312345dd58d86b786e589f"
|
||||
content-hash = "5616faafcbdebee63818206fa4529a7dfad7b388a04e3ce34de995046a756e5b"
|
||||
|
||||
[metadata.files]
|
||||
alabaster = [
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "cfig"
|
||||
version = "0.1.0"
|
||||
description = "The ultimate configuration manager"
|
||||
description = "A configuration manager for Python"
|
||||
authors = ["Stefano Pigozzi <me@steffo.eu>"]
|
||||
license = "MIT"
|
||||
|
||||
|
|
Loading…
Reference in a new issue