|
| 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