config_class.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. #
  2. # Copyright (c) Contributors to the Open 3D Engine Project.
  3. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. #
  5. # SPDX-License-Identifier: Apache-2.0 OR MIT
  6. #
  7. #
  8. # -------------------------------------------------------------------------
  9. """! A common Class object for DCCsi configs
  10. :file: < DCCsi >/azpy/config_class.py
  11. :Status: Prototype
  12. :Version: 0.0.1
  13. This class reduces boilerplate and should streamline code used across
  14. various config.py files within the DCCsi, by wrapping various functionality,
  15. and providing common methods to inheret and/or extend.
  16. The DCCsi config pattern utilizes a robust configuration and settings
  17. package called dynaconf: https://www.dynaconf.com
  18. """
  19. # -------------------------------------------------------------------------
  20. # standard imports
  21. import os
  22. import site
  23. from box import Box
  24. from pathlib import Path
  25. import logging as _logging
  26. from typing import Union
  27. # -------------------------------------------------------------------------
  28. # -------------------------------------------------------------------------
  29. # global scope
  30. from DccScriptingInterface.azpy import _PACKAGENAME
  31. # we should update pkg, module and logging names to start with dccsi
  32. _MODULENAME = f'{_PACKAGENAME}.config_class'
  33. __all__ = ['ConfigCore']
  34. _LOGGER = _logging.getLogger(_MODULENAME)
  35. _LOGGER.debug(f'Initializing: {_MODULENAME}')
  36. from DccScriptingInterface.azpy import PATH_DCCSIG # root DCCsi path
  37. # DCCsi imports
  38. from DccScriptingInterface.azpy.env_bool import env_bool
  39. from DccScriptingInterface import add_site_dir
  40. from DccScriptingInterface import pathify_list
  41. # internal flag for Very verbose path logging
  42. GSUPPRESS_VERBOSE = False
  43. # -------------------------------------------------------------------------
  44. # -------------------------------------------------------------------------
  45. # constants here
  46. from DccScriptingInterface.constants import DCCSI_DYNAMIC_PREFIX
  47. from DccScriptingInterface.constants import SETTINGS_FILE_SLUG
  48. from DccScriptingInterface.constants import LOCAL_SETTINGS_FILE_SLUG
  49. from DccScriptingInterface.constants import PATH_DCCSIG_SETTINGS
  50. from DccScriptingInterface.constants import PATH_DCCSIG_LOCAL_SETTINGS
  51. from DccScriptingInterface.azpy.constants import ENVAR_PATH_DCCSIG
  52. from DccScriptingInterface.azpy.constants import ENVAR_DCCSI_SYS_PATH
  53. from DccScriptingInterface.azpy.constants import ENVAR_DCCSI_PYTHONPATH
  54. from DccScriptingInterface.globals import *
  55. # -------------------------------------------------------------------------
  56. # -------------------------------------------------------------------------
  57. class ConfigClass(object):
  58. """! Class constructor: makes a DCCsi Config object.
  59. ...
  60. Attributes
  61. ----------
  62. config_name : str
  63. (Optional) name of the config, e.g. Tools.DCC.Substance.config
  64. auto_set : str
  65. If set the settings.setenv() will be set automatically
  66. for example the settings are ensured to be set when pulled
  67. otherwise if not manually set this would assert
  68. _bool_test = _foo_test.add_setting('FOO_IS', True)
  69. if _foo_test.setting.FOO_IS:
  70. # do this code
  71. A suggeston would be, that this needs to be profiled?
  72. It might be slow?
  73. """
  74. def __init__(self,
  75. config_name=None,
  76. auto_set=True,
  77. settings_filepath = PATH_DCCSIG_SETTINGS,
  78. settings_local_filepath = PATH_DCCSIG_LOCAL_SETTINGS,
  79. dyna_prefix: str = DCCSI_DYNAMIC_PREFIX,
  80. *args, **kwargs):
  81. '''! The ConfigClass base class initializer.'''
  82. self._config_name = config_name
  83. self._auto_set = auto_set
  84. self._dyna_prefix = dyna_prefix
  85. # dynaconf, dynamic settings
  86. self._settings = dict()
  87. self._settings_file = settings_filepath
  88. self._settings_local_file = settings_local_filepath
  89. # all settings storage (if tracked)
  90. self._tracked_settings = dict()
  91. # special path storage
  92. self._sys_path = list()
  93. self._pythonpath = list()
  94. # -- properties -------------------------------------------------------
  95. @property
  96. def auto_set(self):
  97. ''':Class property: if true, self.settings.setev() is automatic'''
  98. return self._settings
  99. @auto_set.setter
  100. def auto_set(self, value: bool = True):
  101. ''':param settings: the settings object to store'''
  102. self._auto_set = value
  103. return self._auto_set
  104. @auto_set.getter
  105. def auto_set(self):
  106. ''':return: auto_set bool'''
  107. return self._auto_set
  108. @property
  109. def settings(self):
  110. ''':Class property: storage for dynamic managed settings'''
  111. return self._settings
  112. @settings.setter
  113. def settings(self, settings):
  114. ''':param settings: the settings object to store'''
  115. self._settings = settings
  116. return self._settings
  117. @settings.getter
  118. def settings(self):
  119. '''! If the class property self.auto_set is True, settings.setenv() occurs
  120. :return: settings, dynaconf'''
  121. # now standalone we can validate the config. env, settings.
  122. from dynaconf import settings
  123. self._settings = settings
  124. if self.auto_set:
  125. self._settings.setenv()
  126. return self._settings
  127. def get_settings(self, set_env: bool = True):
  128. '''! this getter will ensure to set the env
  129. @param set_env: bool, defaults to true
  130. :return: settings, dynaconf'''
  131. if set_env:
  132. self.settings.setenv()
  133. return self.settings
  134. @property
  135. def local_settings(self):
  136. ''':Class property: non-managed settings (fully local to this config object)
  137. :return local_settings: dict'''
  138. return self._local_settings
  139. @local_settings.setter
  140. def local_settings(self, new_dict: dict) -> dict:
  141. ''':param new_dict: replace entire local_settings
  142. :return local_settings: dict'''
  143. self._local_settings = new_dict
  144. return self._local_settings
  145. @local_settings.getter
  146. def local_settings(self):
  147. ''':return: local_settings dict'''
  148. return self._local_settings
  149. @property
  150. def sys_path(self):
  151. ''':Class property: for stashing PATHs for managed settings
  152. :return sys_path: list'''
  153. return self._sys_path
  154. @sys_path.setter
  155. def sys_path(self, new_list: list) -> list:
  156. ''':param new_list: replace entire sys_path'''
  157. new_sys_path = list()
  158. for path in new_list:
  159. path = add_site_dir(path)
  160. if (path.as_posix() not in new_sys_path):
  161. new_sys_path.insert(0, path.as_posix())
  162. os.environ[ENVAR_DCCSI_SYS_PATH] = os.pathsep.join(new_sys_path)
  163. dynakey = f'{self._dyna_prefix}_{ENVAR_DCCSI_SYS_PATH}'
  164. os.environ[dynakey] = os.pathsep.join(new_sys_path)
  165. self._sys_path = new_sys_path
  166. return self._sys_path
  167. @sys_path.getter
  168. def sys_path(self) -> list:
  169. ''':return: a list for PATH, sys.path'''
  170. self._sys_path = pathify_list(self._sys_path)
  171. return self._sys_path
  172. @property
  173. def pythonpath(self):
  174. ''':Class property: List for stashing PYTHONPATHs for managed settings'''
  175. return self._pythonpath
  176. @pythonpath.setter
  177. def pythonpath(self, new_list: list) -> list:
  178. ''':param new_list: replace entire pythonpath'''
  179. for path in new_list:
  180. path = Path(path).resolve()
  181. path = self.add_code_path(path,
  182. self._pythonpath,
  183. sys_envar='PYTHONPATH',
  184. local_envar=ENVAR_DCCSI_PYTHONPATH)
  185. dynakey = f'{self._dyna_prefix}_{ENVAR_DCCSI_PYTHONPATH}'
  186. os.environ[dynakey] = os.getenv(ENVAR_DCCSI_PYTHONPATH)
  187. return self._pythonpath
  188. @pythonpath.getter
  189. def pythonpath(self):
  190. ''':return: a list for PYTHONPATH, site.addsitedir()'''
  191. self._pythonpath = pathify_list(self._pythonpath)
  192. return self._pythonpath
  193. @property
  194. def settings_filepath(self):
  195. ''':Class property: filepath for exporting settings, default settings.json'''
  196. return self._settings_file
  197. @settings_filepath.setter
  198. def settings_filepath(self,
  199. filepath: Union[str, Path] = PATH_DCCSIG_SETTINGS) -> Path:
  200. ''':param new_dict: sets the default filepath for this config to
  201. export settings, default settings.json'''
  202. self._settings_file = Path(filepath).resolve()
  203. return self._settings_file
  204. @settings_filepath.getter
  205. def settings_filepath(self) -> Path:
  206. ''':return: settings_filepath, default settings.json'''
  207. return self._settings_file
  208. @property
  209. def settings_local_filepath(self):
  210. ''':Class property: filepath for exporting settings,
  211. default settings.local.json'''
  212. return self._settings_local_file
  213. @settings_local_filepath.setter
  214. def settings_local_filepath(self,
  215. filepath: Union[str, Path] = PATH_DCCSIG_LOCAL_SETTINGS) -> Path:
  216. ''':param new_dict: sets the default filepath for this config to
  217. export settings, settings.local.json'''
  218. self._settings_local_file = Path(filepath).resolve()
  219. return self._settings_local_file
  220. @settings_local_filepath.getter
  221. def settings_local_filepath(self):
  222. ''':return: settings_local_filepath, settings.local.json'''
  223. return self._settings_local_file
  224. # ---------------------------------------------------------------------
  225. # --method-set---------------------------------------------------------
  226. @classmethod
  227. def get_classname(cls):
  228. '''! gets the name of the class type
  229. @ return: cls.__name__
  230. '''
  231. return cls.__name__
  232. def add_code_path(self,
  233. path: Path,
  234. path_list: list = None,
  235. sys_envar: str = 'PATH',
  236. local_envar: str = ENVAR_DCCSI_SYS_PATH,
  237. addsitedir: bool = False) -> Path:
  238. '''! sets up a sys path
  239. @param path: the path to add
  240. @ return: returns the path
  241. '''
  242. if path_list is None:
  243. path_list = self.sys_path
  244. path = add_site_dir(path)
  245. # store and track here for settings export
  246. path_list.append(path) # keep path objects
  247. environ = self.add_path_to_envar(path, envar=sys_envar)
  248. return path
  249. # ---------------------------------------------------------------------
  250. def set_envar(self, key: str, value: str):
  251. '''! sets the envar
  252. @param key: the enavr key as a string
  253. @param value: the enavr value as a string
  254. @return: the os.getenv(key)
  255. Path type objects are specially handled.'''
  256. # standard environment
  257. if isinstance(value, Path):
  258. os.environ[key] = value.as_posix()
  259. else:
  260. os.environ[key] = str(value)
  261. return os.getenv(key)
  262. # ---------------------------------------------------------------------
  263. def add_setting(self,
  264. key: str,
  265. value: Union[int, str, bool, list, dict, Path],
  266. set_dynamic: bool = True,
  267. check_envar: bool = True,
  268. set_envar: bool = False,
  269. set_sys_path: bool = False,
  270. set_pythonpath: bool = False,
  271. posix_path: bool = True):
  272. '''! adds a settings with various configurable options
  273. @param key: the key (str) for the setting/envar
  274. @param value: the stored value for the setting
  275. @param set_dynamic: makes this a dynamic setting (dynaconf)
  276. dynamic settings will use the prefix_ to specify membership
  277. os.environ['DYNACONF_FOO'] = 'foo'
  278. This setting will be present in the dynaconf settings object
  279. from dynaconf import settings
  280. And will be in the dynamic settings
  281. print(settings.FOO)
  282. And the dynamic environment
  283. settings.setenv()
  284. print(os.getenv('FOO'))
  285. @param prefix: specifies the default prefix for the dyanamic env
  286. The default is:
  287. DCCSI_DYNAMIC_PREFIX = 'DYNACONF'
  288. @param check_envar: This will check if this is set in the external
  289. env such that we can retain the external setting as an
  290. override, if it is not externally set we will use the passed
  291. in value
  292. @param set_envar: this will set an envar in the traditional way
  293. os.environ['key'] = value
  294. this is optional, but may be important if ...
  295. you are running other code that relies on this envar
  296. and want or need access, before the following occures:
  297. from dynaconf import settings
  298. settings.setenv()
  299. class_object.get_config_settings(set_env=True)
  300. @param set_sys_path: a list of paths to be added to PATH
  301. This uses traditional direct manipulation of env PATH
  302. @see self.add_path_list_to_envar()
  303. @param set_pythonpath: a list of paths to be added to PYTHONPATH
  304. This uses the site.addsitedir() approach to add sire access
  305. @see self.add_path_list_to_addsitedir()
  306. '''
  307. # check for existing value first
  308. if check_envar:
  309. if isinstance(value, bool):
  310. # this checks and returns value as bool
  311. value = env_bool(key, value)
  312. else:
  313. # if the envar is set, it will override the input value!
  314. value = os.getenv(key, value)
  315. if isinstance(value, Path) or set_sys_path or set_pythonpath:
  316. value = Path(value).resolve()
  317. if posix_path:
  318. value = value.as_posix() # ensure unix style
  319. # these managed lists are handled when settings are retreived
  320. if set_sys_path:
  321. value = self.add_code_path(value,
  322. self.sys_path,
  323. sys_envar='PATH',
  324. local_envar=ENVAR_DCCSI_SYS_PATH)
  325. #value = os.getenv(ENVAR_DCCSI_SYS_PATH)
  326. if set_pythonpath:
  327. value = self.add_code_path(value,
  328. self.pythonpath,
  329. sys_envar='PYTHONPATH',
  330. local_envar=ENVAR_DCCSI_PYTHONPATH)
  331. #value = os.getenv(ENVAR_DCCSI_PYTHONPATH)
  332. if set_envar:
  333. value = self.set_envar(key, value)
  334. if set_dynamic:
  335. dynakey = f'{self._dyna_prefix}_{key}'
  336. value = self.set_envar(dynakey, value)
  337. return (key, value)
  338. # ---------------------------------------------------------------------
  339. def add_path_to_envar(self,
  340. path: Union[str, Path],
  341. envar: str = 'PATH',
  342. suppress: bool = GSUPPRESS_VERBOSE):
  343. '''! add path to a path-type envar like PATH or PYTHONPATH
  344. @param path: path to add to the envar
  345. @param envar: envar key
  346. @param add_sitedir: (optional) add site code access
  347. @return: return os.environ[envar]'''
  348. # ensure and resolve path object
  349. path = Path(path).resolve()
  350. if not path.exists() and not suppress:
  351. _LOGGER.debug(f'path.exists()={path.exists()}, path: {path.as_posix()}')
  352. _envar = os.getenv(envar)
  353. if _envar:
  354. os.environ[envar] = ''
  355. # (split) into a list
  356. known_pathlist = _envar.split(os.pathsep)
  357. known_pathlist = [i for i in known_pathlist if i] # remove ""
  358. known_pathlist = pathify_list(known_pathlist)
  359. if (path not in known_pathlist):
  360. known_pathlist.insert(0, path)
  361. if not suppress:
  362. _LOGGER.debug(f'envar:{envar}, adding path: {path}')
  363. for path in known_pathlist:
  364. os.environ[envar] = path.as_posix() + os.pathsep + os.environ[envar]
  365. # adding directly to sys.path apparently doesn't work for
  366. # .dll locations like Qt this pattern by extending the ENVAR
  367. # seems to work correctly which why this pattern is used.
  368. else:
  369. os.environ[envar] = path.as_posix()
  370. return os.environ[envar]
  371. # -----------------------------------------------------------------
  372. def add_pathlist_to_envar(self,
  373. path_list: list,
  374. envar: str = 'PATH',
  375. suppress: bool = GSUPPRESS_VERBOSE):
  376. """!
  377. Take in a list of Path objects to add to system ENVAR (like PATH).
  378. This method explicitly adds the paths to the system ENVAR.
  379. @param path_list: a list() of paths
  380. @param envar: add paths to this ENVAR
  381. @ return: os.environ[envar]
  382. """
  383. if len(path_list) == 0:
  384. _LOGGER.warning('No {} paths added, path_list is empty'.format(envar))
  385. return None
  386. else:
  387. for path in path_list:
  388. self.add_path_to_envar(path, envar, suppress)
  389. return os.environ[envar]
  390. # -----------------------------------------------------------------
  391. def get_config_settings(self,
  392. prefix: str = DCCSI_DYNAMIC_PREFIX):
  393. # all ConfigClass objects that export settings, will generate this
  394. # value. When inspecting settings we will always know that local
  395. # settings have been aggregated and include local settings.
  396. self.add_setting('DCCSI_LOCAL_SETTINGS', True)
  397. self.add_pathlist_to_envar(self.sys_path,
  398. f'{prefix}_{ENVAR_DCCSI_SYS_PATH}',
  399. True)
  400. self.add_pathlist_to_envar(self.pythonpath,
  401. f'{prefix}_{ENVAR_DCCSI_PYTHONPATH}',
  402. True)
  403. from dynaconf import settings
  404. self.settings = settings
  405. if self.auto_set:
  406. self.settings.setenv()
  407. return self.settings
  408. # -----------------------------------------------------------------
  409. def export_settings(self,
  410. settings_filepath: Union[str, Path] = None,
  411. prefix: str = DCCSI_DYNAMIC_PREFIX,
  412. set_env: bool = True,
  413. use_dynabox: bool = False,
  414. env: bool = False,
  415. merge: bool = False,
  416. log_settings: bool = False):
  417. '''! exports the settings to a file
  418. @param settings_filepath: The file path for the exported
  419. settings. Default file is: settings.local.json
  420. This file name is chosen as it's also a default settings file
  421. dynaconf will read in when initializing settings.
  422. # basic file writer looks something like
  423. dynaconf.loaders.write(settings_path=Path('settings.local.json'),
  424. settings_data=DynaBox(settings).to_dict(),
  425. env='core',
  426. merge=True)
  427. @param prefix: the prefix str for dynamic settings
  428. @param use_dynabox: use dynaconf.utils.boxing module
  429. https://dynaconf.readthedocs.io/en/docs_223/reference/dynaconf.utils.html
  430. @param env: put settings into an env, e.g. development
  431. https://dynaconf.readthedocs.io/en/docs_223/reference/dynaconf.loaders.html
  432. @param merge: whether existing file should be merged with new data
  433. @param log_settings: outputs settings contents to logging
  434. The default is to write: < dccsi >/settings.local.json
  435. Dynaconf is configured by default to behave in the following
  436. manner,
  437. This is the Dynaconf call to initialize and retreive settings,
  438. make this call from your entrypoint e.g. main.py:
  439. from dynaconf import settings
  440. That will find and execute the local config and aggregate
  441. settings from the following:
  442. config.py (walks to find)
  443. settings.py (in the same folder as config.py)
  444. settings.json (in the same folder as config.py)
  445. settings.local.json (in the same folder as config.py)
  446. We do not commit settings.local.json to source control,
  447. effectively it can be created and used as a local stash of the
  448. settings, with the beneift that a user can overide settings locally.
  449. '''
  450. # It would be an improvement to add malformed json validation
  451. # and pre-check the settings.local.json to report warnings
  452. # this can easily cause a bad error that is deep and hard to debug
  453. if settings_filepath is None:
  454. settings_filepath = self.settings_local_filepath
  455. settings_filepath = Path(settings_filepath).resolve()
  456. self.get_config_settings()
  457. if not use_dynabox: # this writes a prettier file with indents
  458. _settings_box = Box(self.settings.as_dict())
  459. # writes settings box
  460. _settings_box.to_json(filename=settings_filepath.as_posix(),
  461. sort_keys=True,
  462. indent=4)
  463. if log_settings:
  464. _LOGGER.info(f'Pretty print, settings: {settings_filepath.as_posix()}')
  465. _LOGGER.info(str(_settings_box.to_json(sort_keys=True,
  466. indent=4)))
  467. else:
  468. from dynaconf import loaders
  469. from dynaconf.utils.boxing import DynaBox
  470. _settings_box = DynaBox(self.settings).to_dict()
  471. if not env:
  472. # writes to a file, the format is inferred by extension
  473. # can be .yaml, .toml, .ini, .json, .py
  474. loaders.write(settings_filepath.as_posix(), _settings_box, merge)
  475. return _settings_box
  476. elif env:
  477. # The env can also be written, though this isn't yet utilized by config.py
  478. # A suggested improvement is that we should investigate setting up
  479. # each DCC tool as it's own env group?
  480. loaders.write(settings_filepath.as_posix(), _settings_box, merge, env)
  481. return _settings_box
  482. return self.settings
  483. # --- END -----------------------------------------------------------------
  484. ###########################################################################
  485. # Main Code Block, runs this script as main (testing)
  486. # -------------------------------------------------------------------------
  487. if __name__ == '__main__':
  488. """Run in debug perform local tests from IDE or CLI"""
  489. from DccScriptingInterface.azpy.constants import STR_CROSSBAR
  490. _LOGGER.info(STR_CROSSBAR)
  491. _LOGGER.info(f'~ {_MODULENAME}.py ... Running script as __main__')
  492. _LOGGER.info(STR_CROSSBAR)
  493. _foo_test = ConfigClass()
  494. # you can aggregate a list for PATH (self.sys_path) this way
  495. # same with PYTHONPATH
  496. new_list = list()
  497. new_list.append('c:/foo')
  498. new_list.append('c:/fooey')
  499. # lets pass that list to set our insternal sys_path storage setter
  500. _foo_test.sys_path = new_list
  501. # that will also set up the dynamic setting
  502. _LOGGER.info(f'_foo_test.DCCSI_SYS_PATH = {_foo_test.settings.DCCSI_SYS_PATH}')
  503. # or you could add them individually like this
  504. # add some paths to test special tracking
  505. _foo_test.pythonpath.append(Path('c:/looey').as_posix())
  506. # this just returns the internal list that stores them
  507. _LOGGER.info(f'_foo_test.pythonpath = {_foo_test.pythonpath}')
  508. # only pre-existing paths on the raw envar are returned here
  509. _LOGGER.info(f"PYTHONPATH: {os.getenv('PYTHONPATH')}")
  510. # although verbose, you can better set those path types like this
  511. _foo_test.add_setting('KABLOOEY_PATH',
  512. Path('c:/kablooey'),
  513. set_dynamic = True,
  514. check_envar = True,
  515. set_envar = True,
  516. set_pythonpath = True)
  517. # With that call, the path will immediately be set on the envar
  518. _LOGGER.info(f"PYTHONPATH: {os.getenv('PYTHONPATH')}")
  519. # if added more then once, should collapse down to one on get
  520. _foo_test.pythonpath.append('c:/kablooey')
  521. _LOGGER.info(f'{_foo_test.pythonpath}')
  522. settings = _foo_test.get_config_settings()
  523. # add a envar bool
  524. _bool_test = _foo_test.add_setting('FOO_IS', True)
  525. if _bool_test:
  526. _LOGGER.info(f'The boolean is: {_bool_test}')
  527. _LOGGER.info(f'FOO_IS: {_foo_test.settings.FOO_IS}')
  528. # add a envar int
  529. _foo_test.add_setting('foo_level', 10)
  530. # you can pass in lower, but must retreive UPPER from settings
  531. _LOGGER.info(f'foo_level: {_foo_test.settings.FOO_LEVEL}')
  532. # add a envar dict
  533. _foo_test.add_setting('FOO_DICT', {'fooey': 'Is fun!'})
  534. # add an average envar path
  535. _foo_test.add_setting('BAR_PATH', Path('c:/bar'))
  536. _LOGGER.info(f'foo_dict: {_foo_test.settings.FOO_DICT}')
  537. _LOGGER.info(f'settings: {_foo_test.settings}')
  538. _export_filepath = Path(PATH_DCCSIG, '__tmp__', 'settings_export.json').resolve()
  539. _foo_test.export_settings(settings_filepath = _export_filepath,
  540. log_settings = True)