1
Fork 0
mirror of https://github.com/Steffo99/cfig.git synced 2024-10-16 14:27:38 +00:00

💥 yay more things

This commit is contained in:
Steffo 2022-04-19 04:09:05 +02:00
parent aeb382fcde
commit 775c8c8a38
Signed by: steffo
GPG key ID: 6965406171929D01
15 changed files with 324 additions and 26 deletions

View file

@ -5,8 +5,22 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/cfig" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/cfig" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/cfig/tests" isTestSource="true" /> <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> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PyNamespacePackagesService">
<option name="namespacePackageFolders">
<list>
<option value="$MODULE_DIR$/cfig/sources" />
</list>
</option>
</component>
</module> </module>

View file

@ -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 A goal is to allow easy integration of an application with multiple configuration standards, such as environment
variables, dotenv files, and Docker Secrets files. 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 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. 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 .. code-block:: console
programming the consumption the configuration files.
$ 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 Example
======= =======
@ -46,8 +62,8 @@ application::
return sqlalchemy.create_engine(val) return sqlalchemy.create_engine(val)
if __name__ == "__main__": if __name__ == "__main__":
# TODO: If the configuration file is executed as main, handle the call and display a user-friendly CLI interface. # If the configuration file is executed as main, handle the call and display a user-friendly CLI interface.
config() config.cli()
Values can later be accessed by the program by importing the configuration file: 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 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. 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 Value
A single non-processed configuration value in :class:`str` form. A single non-processed configuration value in :class:`str` form.

View file

@ -131,7 +131,7 @@ class Configuration:
log.debug("Item created successfully!") log.debug("Item created successfully!")
log.debug("Registering item in the configuration...") 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!") log.debug("Registered successfully!")
# Return the created item, so it will take the place of the decorated function # 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("Item created successfully!")
log.debug("Registering item in the configuration...") 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!") log.debug("Registered successfully!")
# Return the created item, so it will take the place of the decorated function # 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) val = self._retrieve_value_optional(key)
log.debug("Retrieved value successfully!") log.debug("Retrieved value successfully!")
if val is None: log.debug("Running user-defined configurable function...")
log.debug(f"Not running user-defined configurable function since value is {val!r}.") val = resolver(val)
else:
log.debug("Running user-defined configurable function...")
val = resolver(val)
return val return val
@ -237,7 +234,7 @@ class Configuration:
else: else:
raise errors.MissingValueError(key) 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. Create, from a resolver, a proxy intolerant about non-specified values.
""" """
@ -249,7 +246,7 @@ class Configuration:
log.debug("Retrieved val successfully!") log.debug("Retrieved val successfully!")
log.debug("Running user-defined configurable function...") log.debug("Running user-defined configurable function...")
val = f(val) val = resolver(val)
return val return val
@ -262,7 +259,7 @@ class Configuration:
:param key: The configuration key to register the proxy to. :param key: The configuration key to register the proxy to.
:param proxy: The proxy to register in :attr:`.proxies`. :param proxy: The proxy to register in :attr:`.proxies`.
:param doc: The docstring to register in :attr:`.docs`. :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: if key in self.proxies:

View file

@ -21,7 +21,7 @@ def MY_FAVOURITE_STRING(val: str) -> str:
@config.optional() @config.optional()
def MY_OPTIONAL_STRING(val: typing.Optional[str]) -> str: def MY_OPTIONAL_STRING(val: typing.Optional[str]) -> str:
""" """
Your favourite string, including the empty one! Your favourite string, but optional!
""" """
return val or "" return val or ""
@ -51,5 +51,13 @@ def MY_FAVOURITE_EVEN_INT(val: str) -> int:
return n 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__": if __name__ == "__main__":
config.cli() config.cli()

View file

@ -8,7 +8,7 @@ class EnvironmentSource(Source):
A source which gets values from environment variables. 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 self.prefix: str = prefix
""" """
The prefix to be prepended to all environment variable names. The prefix to be prepended to all environment variable names.
@ -23,7 +23,7 @@ class EnvironmentSource(Source):
For example, ``_VAL`` for raw values. 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. The environment to retrieve variable values from.
@ -36,4 +36,3 @@ class EnvironmentSource(Source):
def get(self, key: str) -> t.Optional[str]: def get(self, key: str) -> t.Optional[str]:
key = self._process_key(key) key = self._process_key(key)
return self.environment.get(key) return self.environment.get(key)

View file

@ -1,4 +1,3 @@
import os
import typing as t import typing as t
from cfig.sources.env import EnvironmentSource from cfig.sources.env import EnvironmentSource
@ -6,9 +5,11 @@ from cfig.sources.env import EnvironmentSource
class EnvironmentFileSource(EnvironmentSource): class EnvironmentFileSource(EnvironmentSource):
""" """
A source which gets values from files at paths specified in environment variables. 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) super().__init__(prefix=prefix, suffix=suffix, environment=environment)
def get(self, key: str) -> t.Optional[str]: def get(self, key: str) -> t.Optional[str]:

20
docs/Makefile Normal file
View 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
View file

0
docs/_static/.gitignore vendored Normal file
View file

0
docs/_templates/.gitignore vendored Normal file
View file

145
docs/conf.py Normal file
View 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
View 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
View 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
View file

@ -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)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[extras] [extras]
cli = ["click"] cli = ["click", "colorama"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "9003e7beceb925ef6b11961ef2a2bb818fbd8f659b312345dd58d86b786e589f" content-hash = "5616faafcbdebee63818206fa4529a7dfad7b388a04e3ce34de995046a756e5b"
[metadata.files] [metadata.files]
alabaster = [ alabaster = [

View file

@ -1,7 +1,7 @@
[tool.poetry] [tool.poetry]
name = "cfig" name = "cfig"
version = "0.1.0" version = "0.1.0"
description = "The ultimate configuration manager" description = "A configuration manager for Python"
authors = ["Stefano Pigozzi <me@steffo.eu>"] authors = ["Stefano Pigozzi <me@steffo.eu>"]
license = "MIT" license = "MIT"