Skip to content

Commit

Permalink
3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
spacemanspiff2007 committed Jul 17, 2023
1 parent 697a825 commit 7f3ba32
Show file tree
Hide file tree
Showing 34 changed files with 828 additions and 217 deletions.
14 changes: 11 additions & 3 deletions ,pyup.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# autogenerated pyup.io config file
# see https://pyup.io/docs/configuration/ for all available options

schedule: 'every month'
update: false
pin: False
# set the default branch
# default: empty, the default branch on GitHub
branch: Develop

requirements:
- docs/requirements.txt:
update: False
- requirements.txt:
update: False
- requirements_setup.txt:
update: False
2 changes: 1 addition & 1 deletion .github/workflows/run_tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on: [push, pull_request]

jobs:
pre-commit:
name:
name: pre-commit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@

.mypy_cache
__pycache__

make.bat
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ repos:
hooks:
- id: check-hooks-apply
- id: check-useless-excludes

- repo: https://github.com/asottile/pyupgrade
rev: v3.9.0
hooks:
- id: pyupgrade
args: ["--py38-plus"]
30 changes: 30 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py

# Build documentation with MkDocs
#mkdocs:
# configuration: mkdocs.yml

# Optionally build your docs in additional formats such as PDF and ePub
formats: all

build:
os: ubuntu-22.04
tools:
python: "3.10"

# Optionally set the version of Python and requirements required to build your docs
python:
install:
- requirements: requirements_setup.txt
- requirements: docs/requirements.txt
- method: setuptools
path: .
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
extensions = [
'sphinx.ext.autodoc',
'sphinx_autodoc_typehints',
'sphinx_exec_code'
]

# Add any paths that contain templates here, relative to this directory.
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Welcome to the easyconfig documentation!
:maxdepth: 2
:caption: Contents:

usage
class_reference


Expand Down
7 changes: 4 additions & 3 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Packages required to build the documentation
sphinx >= 5.3, < 6
sphinx-autodoc-typehints >= 1.20.1, < 2
sphinx_rtd_theme >= 1.1.1, < 2
sphinx == 6.2.1
sphinx-autodoc-typehints == 1.23.0
sphinx_rtd_theme == 1.2.2
sphinx-exec-code == 0.10
224 changes: 224 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
**************************************
Usage
**************************************

Create your models as you did before. Then pass an instance of the model to the easyconfig function.
It will create a mutable object from the model that holds the same values.

Easyconfig also provides some mixin classes, so you can have type hints for the file load functions.
These mixins are not required, they are just there to provide type hints in the IDE.

For convenience reasons you can also import ``AppBaseModel`` and ``BaseModel`` from ``easyconfig`` so you don't have to
inherit from the mixins yourself.


Simple example
--------------------------------------
.. exec_code::
:language_output: yaml
:caption_output: Generated yaml file

from pydantic import BaseModel
from easyconfig import AppConfigMixin, create_app_config


class MySimpleAppConfig(BaseModel, AppConfigMixin):
retries: int = 5
url: str = 'localhost'
port: int = 443


# Create a global variable which then can be used throughout your code
CONFIG = create_app_config(MySimpleAppConfig())

# Use with type hints and auto complete
CONFIG.port

# Load configuration file from disk.
# If the file does not exist it will be created
# Loading will also change all values of CONFIG accordingly
# ------------ skip: start ------------
CONFIG.load_config_file('/my/configuration/file.yml')
# ------------ skip: stop -------------
# ------------ hide: start -------------
print(CONFIG.generate_default_yaml())
# ------------ hide: stop -------------


Nested example
--------------------------------------
Nested example with the convenience base classes from easyconfig.

.. exec_code::
:language_output: yaml
:caption_output: Generated yaml file

from pydantic import Field
from easyconfig import AppBaseModel, BaseModel, create_app_config


class HttpConfig(BaseModel):
retries: int = 5
url: str = 'localhost'
port: int = 443


class MySimpleAppConfig(AppBaseModel):
run_at: int = Field(12, alias='run at') # use alias to load from/create a different key
http: HttpConfig = HttpConfig()


CONFIG = create_app_config(MySimpleAppConfig())
# ------------ skip: start ------------
CONFIG.load_config_file('/my/configuration/file.yml')
# ------------ skip: stop -------------
# ------------ hide: start -------------
print(CONFIG.generate_default_yaml())
# ------------ hide: stop -------------


Description and comments
--------------------------------------
It's possible to specify a description through the pydantic ``Field``.
The description will be created as a comment in the .yml file.
Note that the comments will be aligned properly

.. exec_code::
:language_output: yaml
:caption_output: Generated yaml file

from pydantic import Field
from easyconfig import AppBaseModel, create_app_config


class MySimpleAppConfig(AppBaseModel):
retries: int = Field(5, description='Amount of retries on error')
url: str = Field('localhost', description='Url used for connection')
port: int = 443


CONFIG = create_app_config(MySimpleAppConfig())
# ------------ skip: start ------------
CONFIG.load_config_file('/my/configuration/file.yml')
# ------------ skip: stop -------------
# ------------ hide: start -------------
print(CONFIG.generate_default_yaml())
# ------------ hide: stop -------------


Expansion and docker secrets
--------------------------------------
It's possible to use environment variable or files for expansion.
To expand an environment variable or file use ``${NAME}`` or ``${NAME:DEFAULT}`` to specify an additional default if the
value under ``NAME`` is not set.
To load the content from a file, e.g. a docker secret specify an absolute file name.

Environment variables::

MY_USER =USER_NAME
MY_GROUP=USER: ${MY_USER}, GROUP: GROUP_NAME
ENV_{_SIGN = CURLY_OPEN_WORKS
ENV_}_SIGN = CURLY_CLOSE_WORKS


yaml file

.. exec_code::
:language_output: yaml
:hide_code:

a = """
env_var: "${MY_USER}"
env_var_recursive: "${MY_GROUP}"
env_var_not_found: Does not exist -> "${INVALID_NAME}"
env_var_default: Does not exist -> "${INVALID_NAME:DEFAULT_VALUE}"
file: "${/my_file/path.txt}"
escaped: |
Brackets {} or $ signs can be used as expected.
Use $${BLA} to escape the whole expansion.
Use $} to escape the closing bracket, e.g. use "${ENV_$}_SIGN}" for "ENV_}_SIGN"
The { does not need to be escaped, e.g. use "${ENV_{_SIGN}" for "ENV_{_SIGN"
"""

print(a)


.. exec_code::
:language_output: yaml
:hide_code:
:caption_output: After expansion


from io import StringIO
from easyconfig.yaml import cmap_from_model, write_aligned_yaml, yaml_rt
from easyconfig.expansion import expand_obj
from easyconfig.expansion import load_file as load_file_module
from os import environ


a = """
env_var: "${MY_USER}"
env_var_recursive: "${MY_GROUP}"
env_var_not_found: Does not exist -> "${INVALID_NAME}"
env_var_default: Does not exist -> "${INVALID_NAME:DEFAULT_VALUE}"
file: "${/my_file/path.txt}"
escaped: |
Brackets {} or $ signs can be used as expected.
Use $${BLA} to escape the whole expansion.
Use $} to escape the closing bracket, e.g. use "${ENV_$}_SIGN}" for "ENV_}_SIGN"
The { does not need to be escaped, e.g. use "${ENV_{_SIGN}" for "ENV_{_SIGN"
"""

load_file_module.read_file = lambda x: "<SECRET_CONTENT_FROM_FILE>"
environ['MY_USER'] = 'USER_NAME'
environ['MY_GROUP'] = 'USER: ${MY_USER}, GROUP: GROUP_NAME'
environ['ENV_{_SIGN'] = 'CURLY_OPEN_WORKS'
environ['ENV_}_SIGN'] = 'CURLY_CLOSE_WORKS'

file = StringIO(a)
cfg = yaml_rt.load(file)
expand_obj(cfg)

out = StringIO()
yaml_rt.dump(cfg, out)
print(out.getvalue())


Callbacks
--------------------------------------

It's possible to register callbacks that will get executed when a value changes or
when the configuration gets loaded for the first time.
This is especially useful feature if the application allows dynamic reloading of the configuration file
(e.g. through a file watcher).

.. exec_code::
:language_output: yaml
:caption_output: Generated yaml file

from easyconfig import AppBaseModel, create_app_config

class MySimpleAppConfig(AppBaseModel):
retries: int = 5
url: str = 'localhost'
port: int = 443

# A function that does the setup
def setup_http():
# some internal function
create_my_http_client(CONFIG.url, CONFIG.port)

CONFIG = create_app_config(MySimpleAppConfig())

# setup_http will be automatically called if a value changes in the MyAppSimpleConfig
# during a subsequent call to CONFIG.load_file() or
# when the config gets loaded for the first time
sub = CONFIG.subscribe_for_changes(setup_http)

# It's possible to cancel the subscription again
sub.cancel()

# ------------ skip: start ------------
# This will trigger the callback
CONFIG.load_config_file('/my/configuration/file.yml')
# ------------ skip: stop -------------
Loading

0 comments on commit 7f3ba32

Please sign in to comment.