Skip to content

Commit 7f3ba32

Browse files
3.0
1 parent 697a825 commit 7f3ba32

34 files changed

+828
-217
lines changed

,pyup.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# autogenerated pyup.io config file
22
# see https://pyup.io/docs/configuration/ for all available options
33

4-
schedule: 'every month'
5-
update: false
6-
pin: False
4+
# set the default branch
5+
# default: empty, the default branch on GitHub
6+
branch: Develop
7+
8+
requirements:
9+
- docs/requirements.txt:
10+
update: False
11+
- requirements.txt:
12+
update: False
13+
- requirements_setup.txt:
14+
update: False

.github/workflows/run_tox.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on: [push, pull_request]
44

55
jobs:
66
pre-commit:
7-
name:
7+
name: pre-commit
88
runs-on: ubuntu-latest
99
steps:
1010
- uses: actions/checkout@v3

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44

55
.mypy_cache
66
__pycache__
7+
8+
make.bat

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@ repos:
3131
hooks:
3232
- id: check-hooks-apply
3333
- id: check-useless-excludes
34+
35+
- repo: https://github.com/asottile/pyupgrade
36+
rev: v3.9.0
37+
hooks:
38+
- id: pyupgrade
39+
args: ["--py38-plus"]

.readthedocs.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# .readthedocs.yml
2+
# Read the Docs configuration file
3+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4+
5+
# Required
6+
version: 2
7+
8+
# Build documentation in the docs/ directory with Sphinx
9+
sphinx:
10+
configuration: docs/conf.py
11+
12+
# Build documentation with MkDocs
13+
#mkdocs:
14+
# configuration: mkdocs.yml
15+
16+
# Optionally build your docs in additional formats such as PDF and ePub
17+
formats: all
18+
19+
build:
20+
os: ubuntu-22.04
21+
tools:
22+
python: "3.10"
23+
24+
# Optionally set the version of Python and requirements required to build your docs
25+
python:
26+
install:
27+
- requirements: requirements_setup.txt
28+
- requirements: docs/requirements.txt
29+
- method: setuptools
30+
path: .

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
extensions = [
3535
'sphinx.ext.autodoc',
3636
'sphinx_autodoc_typehints',
37+
'sphinx_exec_code'
3738
]
3839

3940
# Add any paths that contain templates here, relative to this directory.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Welcome to the easyconfig documentation!
77
:maxdepth: 2
88
:caption: Contents:
99

10+
usage
1011
class_reference
1112

1213

docs/requirements.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Packages required to build the documentation
2-
sphinx >= 5.3, < 6
3-
sphinx-autodoc-typehints >= 1.20.1, < 2
4-
sphinx_rtd_theme >= 1.1.1, < 2
2+
sphinx == 6.2.1
3+
sphinx-autodoc-typehints == 1.23.0
4+
sphinx_rtd_theme == 1.2.2
5+
sphinx-exec-code == 0.10

docs/usage.rst

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
**************************************
2+
Usage
3+
**************************************
4+
5+
Create your models as you did before. Then pass an instance of the model to the easyconfig function.
6+
It will create a mutable object from the model that holds the same values.
7+
8+
Easyconfig also provides some mixin classes, so you can have type hints for the file load functions.
9+
These mixins are not required, they are just there to provide type hints in the IDE.
10+
11+
For convenience reasons you can also import ``AppBaseModel`` and ``BaseModel`` from ``easyconfig`` so you don't have to
12+
inherit from the mixins yourself.
13+
14+
15+
Simple example
16+
--------------------------------------
17+
.. exec_code::
18+
:language_output: yaml
19+
:caption_output: Generated yaml file
20+
21+
from pydantic import BaseModel
22+
from easyconfig import AppConfigMixin, create_app_config
23+
24+
25+
class MySimpleAppConfig(BaseModel, AppConfigMixin):
26+
retries: int = 5
27+
url: str = 'localhost'
28+
port: int = 443
29+
30+
31+
# Create a global variable which then can be used throughout your code
32+
CONFIG = create_app_config(MySimpleAppConfig())
33+
34+
# Use with type hints and auto complete
35+
CONFIG.port
36+
37+
# Load configuration file from disk.
38+
# If the file does not exist it will be created
39+
# Loading will also change all values of CONFIG accordingly
40+
# ------------ skip: start ------------
41+
CONFIG.load_config_file('/my/configuration/file.yml')
42+
# ------------ skip: stop -------------
43+
# ------------ hide: start -------------
44+
print(CONFIG.generate_default_yaml())
45+
# ------------ hide: stop -------------
46+
47+
48+
Nested example
49+
--------------------------------------
50+
Nested example with the convenience base classes from easyconfig.
51+
52+
.. exec_code::
53+
:language_output: yaml
54+
:caption_output: Generated yaml file
55+
56+
from pydantic import Field
57+
from easyconfig import AppBaseModel, BaseModel, create_app_config
58+
59+
60+
class HttpConfig(BaseModel):
61+
retries: int = 5
62+
url: str = 'localhost'
63+
port: int = 443
64+
65+
66+
class MySimpleAppConfig(AppBaseModel):
67+
run_at: int = Field(12, alias='run at') # use alias to load from/create a different key
68+
http: HttpConfig = HttpConfig()
69+
70+
71+
CONFIG = create_app_config(MySimpleAppConfig())
72+
# ------------ skip: start ------------
73+
CONFIG.load_config_file('/my/configuration/file.yml')
74+
# ------------ skip: stop -------------
75+
# ------------ hide: start -------------
76+
print(CONFIG.generate_default_yaml())
77+
# ------------ hide: stop -------------
78+
79+
80+
Description and comments
81+
--------------------------------------
82+
It's possible to specify a description through the pydantic ``Field``.
83+
The description will be created as a comment in the .yml file.
84+
Note that the comments will be aligned properly
85+
86+
.. exec_code::
87+
:language_output: yaml
88+
:caption_output: Generated yaml file
89+
90+
from pydantic import Field
91+
from easyconfig import AppBaseModel, create_app_config
92+
93+
94+
class MySimpleAppConfig(AppBaseModel):
95+
retries: int = Field(5, description='Amount of retries on error')
96+
url: str = Field('localhost', description='Url used for connection')
97+
port: int = 443
98+
99+
100+
CONFIG = create_app_config(MySimpleAppConfig())
101+
# ------------ skip: start ------------
102+
CONFIG.load_config_file('/my/configuration/file.yml')
103+
# ------------ skip: stop -------------
104+
# ------------ hide: start -------------
105+
print(CONFIG.generate_default_yaml())
106+
# ------------ hide: stop -------------
107+
108+
109+
Expansion and docker secrets
110+
--------------------------------------
111+
It's possible to use environment variable or files for expansion.
112+
To expand an environment variable or file use ``${NAME}`` or ``${NAME:DEFAULT}`` to specify an additional default if the
113+
value under ``NAME`` is not set.
114+
To load the content from a file, e.g. a docker secret specify an absolute file name.
115+
116+
Environment variables::
117+
118+
MY_USER =USER_NAME
119+
MY_GROUP=USER: ${MY_USER}, GROUP: GROUP_NAME
120+
ENV_{_SIGN = CURLY_OPEN_WORKS
121+
ENV_}_SIGN = CURLY_CLOSE_WORKS
122+
123+
124+
yaml file
125+
126+
.. exec_code::
127+
:language_output: yaml
128+
:hide_code:
129+
130+
a = """
131+
env_var: "${MY_USER}"
132+
env_var_recursive: "${MY_GROUP}"
133+
env_var_not_found: Does not exist -> "${INVALID_NAME}"
134+
env_var_default: Does not exist -> "${INVALID_NAME:DEFAULT_VALUE}"
135+
file: "${/my_file/path.txt}"
136+
escaped: |
137+
Brackets {} or $ signs can be used as expected.
138+
Use $${BLA} to escape the whole expansion.
139+
Use $} to escape the closing bracket, e.g. use "${ENV_$}_SIGN}" for "ENV_}_SIGN"
140+
The { does not need to be escaped, e.g. use "${ENV_{_SIGN}" for "ENV_{_SIGN"
141+
"""
142+
143+
print(a)
144+
145+
146+
.. exec_code::
147+
:language_output: yaml
148+
:hide_code:
149+
:caption_output: After expansion
150+
151+
152+
from io import StringIO
153+
from easyconfig.yaml import cmap_from_model, write_aligned_yaml, yaml_rt
154+
from easyconfig.expansion import expand_obj
155+
from easyconfig.expansion import load_file as load_file_module
156+
from os import environ
157+
158+
159+
a = """
160+
env_var: "${MY_USER}"
161+
env_var_recursive: "${MY_GROUP}"
162+
env_var_not_found: Does not exist -> "${INVALID_NAME}"
163+
env_var_default: Does not exist -> "${INVALID_NAME:DEFAULT_VALUE}"
164+
file: "${/my_file/path.txt}"
165+
escaped: |
166+
Brackets {} or $ signs can be used as expected.
167+
Use $${BLA} to escape the whole expansion.
168+
Use $} to escape the closing bracket, e.g. use "${ENV_$}_SIGN}" for "ENV_}_SIGN"
169+
The { does not need to be escaped, e.g. use "${ENV_{_SIGN}" for "ENV_{_SIGN"
170+
"""
171+
172+
load_file_module.read_file = lambda x: "<SECRET_CONTENT_FROM_FILE>"
173+
environ['MY_USER'] = 'USER_NAME'
174+
environ['MY_GROUP'] = 'USER: ${MY_USER}, GROUP: GROUP_NAME'
175+
environ['ENV_{_SIGN'] = 'CURLY_OPEN_WORKS'
176+
environ['ENV_}_SIGN'] = 'CURLY_CLOSE_WORKS'
177+
178+
file = StringIO(a)
179+
cfg = yaml_rt.load(file)
180+
expand_obj(cfg)
181+
182+
out = StringIO()
183+
yaml_rt.dump(cfg, out)
184+
print(out.getvalue())
185+
186+
187+
Callbacks
188+
--------------------------------------
189+
190+
It's possible to register callbacks that will get executed when a value changes or
191+
when the configuration gets loaded for the first time.
192+
This is especially useful feature if the application allows dynamic reloading of the configuration file
193+
(e.g. through a file watcher).
194+
195+
.. exec_code::
196+
:language_output: yaml
197+
:caption_output: Generated yaml file
198+
199+
from easyconfig import AppBaseModel, create_app_config
200+
201+
class MySimpleAppConfig(AppBaseModel):
202+
retries: int = 5
203+
url: str = 'localhost'
204+
port: int = 443
205+
206+
# A function that does the setup
207+
def setup_http():
208+
# some internal function
209+
create_my_http_client(CONFIG.url, CONFIG.port)
210+
211+
CONFIG = create_app_config(MySimpleAppConfig())
212+
213+
# setup_http will be automatically called if a value changes in the MyAppSimpleConfig
214+
# during a subsequent call to CONFIG.load_file() or
215+
# when the config gets loaded for the first time
216+
sub = CONFIG.subscribe_for_changes(setup_http)
217+
218+
# It's possible to cancel the subscription again
219+
sub.cancel()
220+
221+
# ------------ skip: start ------------
222+
# This will trigger the callback
223+
CONFIG.load_config_file('/my/configuration/file.yml')
224+
# ------------ skip: stop -------------

0 commit comments

Comments
 (0)