New Upstream Release - python-dynaconf
Ready changes
Summary
Merged new upstream version: 3.1.12 (was: 3.1.11).
Resulting package
Built on 2023-05-19T06:48 (took 8m31s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases python3-dynaconf
Lintian Result
Diff
diff --git a/3.x-release-notes.md b/3.x-release-notes.md
index a19d9e5..0753c63 100644
--- a/3.x-release-notes.md
+++ b/3.x-release-notes.md
@@ -3,13 +3,13 @@
In Dynaconf 3.0.0 we introduced some improvements and with
those improvements it comes some **breaking changes.**
-Some of the changes were discussed on the **1st Dynaconf community meeting** [video is available](https://www.twitch.tv/videos/657033043) and [meeting notes #354](https://github.com/rochacbruno/dynaconf/issues/354).
+Some of the changes were discussed on the **1st Dynaconf community meeting** [video is available](https://www.twitch.tv/videos/657033043) and [meeting notes #354](https://github.com/dynaconf/dynaconf/issues/354).
## Improvements
-- Validators now implements `|` and `&` operators to allow `Validator() &| Validator()` and has more `operations` available such as `len_eq, len_min, len_max, startswith` [#353](https://github.com/rochacbruno/dynaconf/pull/353).
-- First level variables are now allowed to be `lowercase` it is now possible to access `settings.foo` or `settings.FOO` [#357](https://github.com/rochacbruno/dynaconf/pull/357).
+- Validators now implements `|` and `&` operators to allow `Validator() &| Validator()` and has more `operations` available such as `len_eq, len_min, len_max, startswith` [#353](https://github.com/dynaconf/dynaconf/pull/353).
+- First level variables are now allowed to be `lowercase` it is now possible to access `settings.foo` or `settings.FOO` [#357](https://github.com/dynaconf/dynaconf/pull/357).
- All Dependencies are now vendored, so when installing Dynaconf is not needed to install any dependency.
- Dynaconf configuration options are now aliased so when creating an instance of `LazySettings|FlaskDynaconf|DjangoDynaconf` it is now possible to pass instead of `ENVVAR_PREFIX_FOR_DYNACONF` just `envvar_prefix` and this lowercase alias is now accepted.
- Fixed bugs in `merge` and deprecated the `@reset` token.
@@ -39,7 +39,7 @@ settings = Dynaconf(**options)
and then in your program you do `from project.config import settings` instead of `from dynaconf import settings`.
-The `**options` are any of the [dynaconf config options](https://dynaconf.readthedocs.io/en/latest/guides/configuration.html)
+The `**options` are any of the [dynaconf config options](https://www.dynaconf.com/configuration/)
ex:
@@ -66,7 +66,7 @@ key = 'value'
key = 'value'
```
-**Now starting on 3.0.0** the environments are disabled by default, so the same file can be crated as.
+**Now starting on 3.0.0** the environments are disabled by default, so the same file can be created as.
```toml
key = 'value'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e686c8e..7594689 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,576 @@ Changelog
=========
+3.1.11 (2022-09-22)
+-------------------
+- Release version 3.1.11. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Bruno Rocha (2):
+ Release version 3.1.10
+ Release hotfix (no need to run coverage or include tests_functional)
+- Release hotfix (no need to run coverage or include tests_functional)
+ [Bruno Rocha]
+- Release version 3.1.10. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Amadou Crookes (1):
+ envars.md typo fix (#786)
+
+ Bruno Rocha (19):
+ Release version 3.1.9
+ Bump dev version to 3.1.10
+ Update badges
+ demo repo will be replaced by a video tutorial soon
+ Fix CI
+ New data key casing must adapt to existing key casing (#795)
+ Add test and docs about includes (#796)
+ Removed vendor_src folder (#798)
+ Replacing rochacbruno/ with dynaconf/ (#800)
+ Fix codecov (#801)
+ Parse negative numbers from envvar Fix #799 and Fix #585 (#802)
+ Fix get command with Django (#804)
+ Add a functional test runner (#805)
+ Test runner docs and styling (#806)
+ Allow merge_unique on lists when merge_enabled=True (#810)
+ Rebind current env when forced for Pytest Fix #728 (#809)
+ AUTO_CAST can be enabled on instance (#811)
+ Ensure pyminify is on release script
+ Add missing tomllib to monify script
+
+ Gaurav Talreja (1):
+ Fix #807 Use client.auth.approle.login instead of client.auth_approle (#808)
+
+ Jitendra Yejare (1):
+ Fix #768 of kv property depreciation from client object (#769)
+
+ Joren Retel (2):
+ Feature/detect casting comb token from converters (#784)
+ Adding documentation and example to makefile. (#791)
+
+ João Gustavo A. Amorim (1):
+ Add pyupgrade hook (#759)
+
+ Kian-Meng Ang (1):
+ Fix typos (#788)
+
+ Lucas Limeira (1):
+ Using filter_strategy in env_loader to fix #760 (#767)
+
+ Nicholas Nadeau, Ph.D., P.Eng (1):
+ fix: typo (#766)
+
+ Oleksii Baranov (2):
+ Bump codecov action version (#775)
+ Fix cli init command for flask (#705) (#774)
+
+ Pedro de Medeiros (1):
+ documentation fixes (#771)
+
+ The Gitter Badger (1):
+ Add a Gitter chat badge to README.md (#776)
+
+ Théo Melo (1):
+ Fixing a typo on the readme file (#763)
+
+ Vicente Marçal (1):
+ docs(pt-br): Docs Translation to brazilian portugues. (#787)
+
+
+3.1.10 (2022-09-22)
+-------------------
+
+Fix
+~~~
+- Typo (#766) [Bruno Rocha, Nicholas Nadeau, Ph.D., P.Eng]
+
+Other
+~~~~~
+- Release version 3.1.10. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Amadou Crookes (1):
+ envars.md typo fix (#786)
+
+ Bruno Rocha (19):
+ Release version 3.1.9
+ Bump dev version to 3.1.10
+ Update badges
+ demo repo will be replaced by a video tutorial soon
+ Fix CI
+ New data key casing must adapt to existing key casing (#795)
+ Add test and docs about includes (#796)
+ Removed vendor_src folder (#798)
+ Replacing rochacbruno/ with dynaconf/ (#800)
+ Fix codecov (#801)
+ Parse negative numbers from envvar Fix #799 and Fix #585 (#802)
+ Fix get command with Django (#804)
+ Add a functional test runner (#805)
+ Test runner docs and styling (#806)
+ Allow merge_unique on lists when merge_enabled=True (#810)
+ Rebind current env when forced for Pytest Fix #728 (#809)
+ AUTO_CAST can be enabled on instance (#811)
+ Ensure pyminify is on release script
+ Add missing tomllib to monify script
+
+ Gaurav Talreja (1):
+ Fix #807 Use client.auth.approle.login instead of client.auth_approle (#808)
+
+ Jitendra Yejare (1):
+ Fix #768 of kv property depreciation from client object (#769)
+
+ Joren Retel (2):
+ Feature/detect casting comb token from converters (#784)
+ Adding documentation and example to makefile. (#791)
+
+ João Gustavo A. Amorim (1):
+ Add pyupgrade hook (#759)
+
+ Kian-Meng Ang (1):
+ Fix typos (#788)
+
+ Lucas Limeira (1):
+ Using filter_strategy in env_loader to fix #760 (#767)
+
+ Nicholas Nadeau, Ph.D., P.Eng (1):
+ fix: typo (#766)
+
+ Oleksii Baranov (2):
+ Bump codecov action version (#775)
+ Fix cli init command for flask (#705) (#774)
+
+ Pedro de Medeiros (1):
+ documentation fixes (#771)
+
+ The Gitter Badger (1):
+ Add a Gitter chat badge to README.md (#776)
+
+ Théo Melo (1):
+ Fixing a typo on the readme file (#763)
+
+ Vicente Marçal (1):
+ docs(pt-br): Docs Translation to brazilian portugues. (#787)
+- Add missing tomllib to monify script. [Bruno Rocha]
+- Ensure pyminify is on release script. [Bruno Rocha]
+- AUTO_CAST can be enabled on instance (#811) [Bruno Rocha]
+
+ Fix #772
+- Rebind current env when forced for Pytest Fix #728 (#809) [Bruno
+ Rocha]
+- Allow merge_unique on lists when merge_enabled=True (#810) [Bruno
+ Rocha]
+
+ Fix #726
+- Fix #807 Use client.auth.approle.login instead of client.auth_approle
+ (#808) [Gaurav Talreja]
+- Fix typos (#788) [Kian-Meng Ang]
+
+ Found via this command:
+
+ codespell -S "./dynaconf/vendor/*,./docs/pt-br/*,./.mypy_cache/*,*.svg" -L hashi
+- Test runner docs and styling (#806) [Bruno Rocha]
+
+ * Test runner docs and styling
+
+ * No emojis on windows
+- Add a functional test runner (#805) [Bruno Rocha]
+
+ * Add a functional test runner
+
+ * Renamed example/ to tests_functional/
+- Fix get command with Django (#804) [Bruno Rocha]
+
+ Fix #789
+- Parse negative numbers from envvar Fix #799 and Fix #585 (#802) [Bruno
+ Rocha]
+- Fix codecov (#801) [Bruno Rocha]
+
+ * Fix codecov
+
+ * call coverage xml
+- Replacing rochacbruno/ with dynaconf/ (#800) [Bruno Rocha]
+
+ * Replacing rochacbruno/ with dynaconf/
+
+ * xscode doesn't exist anymore
+- Removed vendor_src folder (#798) [Bruno Rocha]
+
+ * Removed vendor_src folder
+
+ Now `vendor` is the source
+ and minification happens during release process.
+
+ * Added tomllib (vendored) as a replacement for toml fix #708
+
+ toml kept as a fallback until 4.0.0 to nor break compatibility
+
+ - toml follows 0.5.0 spec
+ - tomlib follows 1.0.0 spec
+ - toml allows emojis and unicode chars unencoded
+ - tomllib foolows the spec where only encoded chars are allowed
+- Add test and docs about includes (#796) [Bruno Rocha]
+
+ closes #794
+- New data key casing must adapt to existing key casing (#795) [Bruno
+ Rocha]
+
+ Fix #737
+- Docs(pt-br): Docs Translation to brazilian portugues. (#787) [Vicente
+ Marçal]
+- Adding documentation and example to makefile. (#791) [Joren Retel]
+
+ * Adding documentation and example to makefile.
+
+ * Put header one level down in docs.
+- Feature/detect casting comb token from converters (#784) [Joren Retel]
+- Envars.md typo fix (#786) [Amadou Crookes]
+- Fix CI. [Bruno Rocha]
+- Demo repo will be replaced by a video tutorial soon. [Bruno Rocha]
+- Update badges. [Bruno Rocha]
+- Documentation fixes (#771) [Bruno Rocha, Pedro de Medeiros]
+- Add a Gitter chat badge to README.md (#776) [Bruno Rocha, The Gitter
+ Badger]
+- Fix cli init command for flask (#705) (#774) [Bruno Rocha, Oleksii
+ Baranov]
+- Bump codecov action version (#775) [Oleksii Baranov]
+- Fix #768 of kv property depreciation from client object (#769)
+ [Jitendra Yejare]
+- Using filter_strategy in env_loader to fix #760 (#767) [Lucas Limeira]
+- Fixing a typo on the readme file (#763) [Théo Melo]
+- Add pyupgrade hook (#759) [João Gustavo A. Amorim]
+
+ * update hooks and add pyupgrade
+
+ * updates by pyupgrade
+
+ * remove unused typing imports
+
+ * add `from __future__ import annotations` across the codebase
+
+ * add `from __future__ import annotations` in examples
+- Bump dev version to 3.1.10. [Bruno Rocha]
+- Release version 3.1.9. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Bruno Rocha (4):
+ Release version 3.1.8
+ Bye py 3.7
+ Multiple fixes for 3.19 (#756)
+ update docs site (#758)
+
+ João Gustavo A. Amorim (1):
+ Organize pre-commit setup (#757)
+
+ dependabot[bot] (1):
+ Bump django from 2.2.27 to 2.2.28 in /example/django_pytest_pure (#743)
+
+
+3.1.9 (2022-06-06)
+------------------
+- Release version 3.1.9. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Bruno Rocha (4):
+ Release version 3.1.8
+ Bye py 3.7
+ Multiple fixes for 3.19 (#756)
+ update docs site (#758)
+
+ João Gustavo A. Amorim (1):
+ Organize pre-commit setup (#757)
+
+ dependabot[bot] (1):
+ Bump django from 2.2.27 to 2.2.28 in /example/django_pytest_pure (#743)
+- Update docs site (#758) [Bruno Rocha]
+- Organize pre-commit setup (#757) [João Gustavo A. Amorim]
+- Multiple fixes for 3.19 (#756) [Bruno Rocha]
+- Bump django from 2.2.27 to 2.2.28 in /example/django_pytest_pure
+ (#743) [dependabot[bot], dependabot[bot]]
+- Bye py 3.7. [Bruno Rocha]
+- Release version 3.1.8. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Anderson Sousa (1):
+ Document the usage with python -m (#710)
+
+ Andressa Cabistani (2):
+ Add unique label when merging lists to fix issue #653 (#661)
+ Add new validation to fix issue #585 (#667)
+
+ Armin Berres (1):
+ Fix typo in error message
+
+ Bruno Rocha (7):
+ Release version 3.1.7
+ Found this bug that was duplicating the generated envlist (#663)
+ Add support for Python 3.10 (#665)
+ Attempt to fix #555 (#669)
+ Create update_contributors.yml
+ Fixing pre-coomit and docs CI
+ Added `dynaconf get` command to cli (#730)
+
+ Caneco (2):
+ improvement: add brand new logo to the project (#686)
+ improvement: update socialcard to match the python way (#687)
+
+ EdwardCuiPeacock (2):
+ Feature: add @jinja and @format casting (#704)
+ Combo converter doc (#735)
+
+ Eitan Mosenkis (1):
+ Fix FlaskConfig.setdefault (#706)
+
+ Enderson Menezes (Mr. Enderson) (2):
+ Force PYTHONIOENCODING to utf-8 to fix #664 (#672)
+ edit: move discussions to github tab (#682)
+
+ Eugene Triguba (1):
+ Fix custom prefix link in envvar documentation (#680)
+
+ Gibran Herrera (1):
+ Fix Issue 662 Lazy validation (#675)
+
+ Jitendra Yejare (2):
+ Load vault secrets from environment less stores or which are not written by dynaconf (#725)
+ Use default value when settings is blank (#729)
+
+ Pavel Alimpiev (1):
+ Update docs link (#678)
+
+ Ugo Benassayag (1):
+ Added validate_only_current_env to validator (issue #734) (#736)
+
+ Waylon Walker (1):
+ Docs Fix Spelling (#696)
+
+ dependabot[bot] (3):
+ Bump django from 2.1.5 to 2.2.26 in /example/django_pytest_pure (#711)
+ Bump mkdocs from 1.1.2 to 1.2.3 (#715)
+ Bump django from 2.2.26 to 2.2.27 in /example/django_pytest_pure (#717)
+
+ github-actions[bot] (2):
+ [automated] Update Contributors File (#691)
+ [automated] Update Contributors File (#732)
+
+ lowercase00 (1):
+ Makes Django/Flask kwargs case insensitive (#721)
+
+
+3.1.8 (2022-04-15)
+------------------
+- Release version 3.1.8. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Anderson Sousa (1):
+ Document the usage with python -m (#710)
+
+ Andressa Cabistani (2):
+ Add unique label when merging lists to fix issue #653 (#661)
+ Add new validation to fix issue #585 (#667)
+
+ Armin Berres (1):
+ Fix typo in error message
+
+ Bruno Rocha (7):
+ Release version 3.1.7
+ Found this bug that was duplicating the generated envlist (#663)
+ Add support for Python 3.10 (#665)
+ Attempt to fix #555 (#669)
+ Create update_contributors.yml
+ Fixing pre-coomit and docs CI
+ Added `dynaconf get` command to cli (#730)
+
+ Caneco (2):
+ improvement: add brand new logo to the project (#686)
+ improvement: update socialcard to match the python way (#687)
+
+ EdwardCuiPeacock (2):
+ Feature: add @jinja and @format casting (#704)
+ Combo converter doc (#735)
+
+ Eitan Mosenkis (1):
+ Fix FlaskConfig.setdefault (#706)
+
+ Enderson Menezes (Mr. Enderson) (2):
+ Force PYTHONIOENCODING to utf-8 to fix #664 (#672)
+ edit: move discussions to github tab (#682)
+
+ Eugene Triguba (1):
+ Fix custom prefix link in envvar documentation (#680)
+
+ Gibran Herrera (1):
+ Fix Issue 662 Lazy validation (#675)
+
+ Jitendra Yejare (2):
+ Load vault secrets from environment less stores or which are not written by dynaconf (#725)
+ Use default value when settings is blank (#729)
+
+ Pavel Alimpiev (1):
+ Update docs link (#678)
+
+ Ugo Benassayag (1):
+ Added validate_only_current_env to validator (issue #734) (#736)
+
+ Waylon Walker (1):
+ Docs Fix Spelling (#696)
+
+ dependabot[bot] (3):
+ Bump django from 2.1.5 to 2.2.26 in /example/django_pytest_pure (#711)
+ Bump mkdocs from 1.1.2 to 1.2.3 (#715)
+ Bump django from 2.2.26 to 2.2.27 in /example/django_pytest_pure (#717)
+
+ github-actions[bot] (2):
+ [automated] Update Contributors File (#691)
+ [automated] Update Contributors File (#732)
+
+ lowercase00 (1):
+ Makes Django/Flask kwargs case insensitive (#721)
+- Combo converter doc (#735) [EdwardCuiPeacock]
+- Added validate_only_current_env to validator (issue #734) (#736) [Ugo
+ Benassayag, Ugo Benassayag]
+- [automated] Update Contributors File (#732) [github-actions[bot],
+ rochacbruno]
+- Added `dynaconf get` command to cli (#730) [Bruno Rocha]
+- Fixing pre-coomit and docs CI. [Bruno Rocha]
+- Fix typo in error message. [Armin Berres]
+
+ It is, e.g., REDIS_HOST_FOR_DYNACONF - not REDIS_FOR_DYNACONF_HOST.
+- Bump django from 2.2.26 to 2.2.27 in /example/django_pytest_pure
+ (#717) [Bruno Rocha, dependabot[bot], dependabot[bot]]
+- Bump mkdocs from 1.1.2 to 1.2.3 (#715) [Bruno Rocha, dependabot[bot],
+ dependabot[bot]]
+- Fix custom prefix link in envvar documentation (#680) [Andressa
+ Cabistani, Bruno Rocha, Eugene Triguba]
+- Use default value when settings is blank (#729) [Bruno Rocha, Jitendra
+ Yejare]
+- Load vault secrets from environment less stores or which are not
+ written by dynaconf (#725) [Jitendra Yejare]
+- Makes Django/Flask kwargs case insensitive (#721) [lowercase00]
+- Docs Fix Spelling (#696) [Bruno Rocha, Waylon Walker]
+- Bump django from 2.1.5 to 2.2.26 in /example/django_pytest_pure (#711)
+ [Bruno Rocha, dependabot[bot], dependabot[bot]]
+- [automated] Update Contributors File (#691) [github-actions[bot],
+ rochacbruno]
+- Feature: add @jinja and @format casting (#704) [Bruno Rocha,
+ EdwardCuiPeacock]
+- Document the usage with python -m (#710) [Anderson Sousa, Bruno Rocha]
+- Fix FlaskConfig.setdefault (#706) [Eitan Mosenkis]
+- Create update_contributors.yml. [Bruno Rocha]
+- Improvement: update socialcard to match the python way (#687) [Caneco]
+- Improvement: add brand new logo to the project (#686) [Caneco]
+- Edit: move discussions to github tab (#682) [Enderson Menezes (Mr.
+ Enderson)]
+- Update docs link (#678) [Pavel Alimpiev]
+
+ * Replace an old Django-related link with a new one
+
+ * Update docs link
+- Fix Issue 662 Lazy validation (#675) [Gibran Herrera]
+- Force PYTHONIOENCODING to utf-8 to fix #664 (#672) [Enderson Menezes
+ (Mr. Enderson)]
+- Attempt to fix #555 (#669) [Bruno Rocha]
+- Add new validation to fix issue #585 (#667) [Andressa Cabistani,
+ andressa.cabistani]
+- Add support for Python 3.10 (#665) [Bruno Rocha]
+
+ Python 3.10 supported and tested
+- Found this bug that was duplicating the generated envlist (#663)
+ [Bruno Rocha]
+- Add unique label when merging lists to fix issue #653 (#661) [Andressa
+ Cabistani, andressa.cabistani]
+- Release version 3.1.7. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Bruno Rocha (2):
+ Release version 3.1.6
+ Add missing docs and missing python_requires (#659)
+
+
+3.1.7 (2021-09-09)
+------------------
+- Release version 3.1.7. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Bruno Rocha (2):
+ Release version 3.1.6
+ Add missing docs and missing python_requires (#659)
+- Add missing docs and missing python_requires (#659) [Bruno Rocha]
+- Release version 3.1.6. [Bruno Rocha]
+
+ Shortlog of commits since last release:
+
+ Ambient Lighter (1):
+ Fix typo (#647)
+
+ Bruno Rocha (19):
+ Release version 3.1.4
+ demo link (#546)
+ removed release_notes from the docs. (#550)
+ HOTFIX: Add coverage for 2 lines on validators.
+ Fix #595 namedtuples are no more converted to BoxList (#623)
+ Fix black issues (#631)
+ Update FUNDING.yml
+ description and type annotation for validator (#634)
+ Add myoy and pre-commit to CI (#635)
+ Update codaci badge (#636)
+ Remove dependabot (this project has no dependencies)
+ fix #596 django override (#645)
+ fix #491 pytest django Fix #491 pytest and django (#646)
+ Delete requirements.txt
+ Update FUNDING.yml
+ Add support for dynaconf_hooks(post) issue #654 (#655)
+ Move to Github Actions (#656)
+ Bye Azure (#657)
+ Bump dev version
+
+ FrankBattaglia (1):
+ fix dict iterator methods for flask DynaconfConfig (#581)
+
+ Jacob Callahan (1):
+ Add the ability for selective validation (#549)
+
+ Kamil Gałuszka (1):
+ Add support for Python 3.9 and remove Ubuntu 16.04 that is deprecated in Azure Pipelines (#618)
+
+ Konstantin (2):
+ Update configuration.md (#553)
+ Update configuration.md (#554)
+
+ Linus Torvalds (1):
+ Fix a typo in the docs
+
+ Martin Thoma (1):
+ Add type annotations for dynaconf.utils (#450)
+
+ Nicholas Dentandt (1):
+ feat: add filter strategy with PrefixFilter (#625)
+
+ Robert Rosca (1):
+ Add a warning if `--env` is passed to `init` (#629)
+
+ Tanya Tereshchenko (1):
+ Do not search anywhere if the absolute path to a file provided (#570)
+
+ Yusuf Kaka (1):
+ Added an example using FastAPI (#571)
+
+ dependabot-preview[bot] (2):
+ Bump mkdocs-material from 7.0.5 to 7.0.6 (#552)
+ Upgrade to GitHub-native Dependabot (#574)
+
+ puntonim (1):
+ Fix typo (#588)
+
+
3.1.6 (2021-09-09)
------------------
- Release version 3.1.6. [Bruno Rocha]
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e009cca..7cde113 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -14,7 +14,7 @@ This Diagram can help you understand visually what happens on Dynaconf: https://
2. Activate a python3.6+ virtualenv
3. Code
2. Update the `docs/guides/` related to your changes.
-3. Update `example/` (editing or adding a new one related to your changes)
+3. Update `tests_functional/` (editing or adding a new one related to your changes)
4. Ensure tests are passing (see below `make all`)
1. This project uses `pre-commit` and `Black` for code styling and adequacy tests.
5. Commit, Push and make a Pull Request!
@@ -27,7 +27,7 @@ This Diagram can help you understand visually what happens on Dynaconf: https://
git clone git@github.com:{$USER}/dynaconf.git
# Add the upstream remote
-git remote add upstream https://github.com/rochacbruno/dynaconf.git
+git remote add upstream https://github.com/dynaconf/dynaconf.git
# Activate your Python Environment
python3.7 -m venv venv
@@ -47,8 +47,8 @@ git fetch upstream; git rebase upstream/master
# Fix any conflicts if any.
# Update docs/guides/ if needed
-# Edit example/ if needed
-# Create a new app in example/{your_example} and add it to Makefile.
+# Edit tests_functional/ if needed
+# Create a new app in tests_functional/{your_example} and add it to Makefile.
# Then ensure everything is ok
make all
@@ -59,7 +59,7 @@ git commit -am "Changed XPTO to fix #issue_number"
# Push to your own fork
git push -u origin HEAD
-# Open github.com/rochacbruno/dynaconf and send a Pull Request.
+# Open github.com/dynaconf/dynaconf and send a Pull Request.
```
### Run integration tests
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..a6de01e
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,34 @@
+# Contributors
+
+Shout out to our top contributors!
+
+- [rochacbruno](https://api.github.com/users/rochacbruno)
+- [douglas](https://api.github.com/users/douglas)
+- [dependabot-preview[bot]](https://api.github.com/users/dependabot-preview%5Bbot%5D)
+- [jperras](https://api.github.com/users/jperras)
+- [janw](https://api.github.com/users/janw)
+- [hilam](https://api.github.com/users/hilam)
+- [VaultVulp](https://api.github.com/users/VaultVulp)
+- [gpkc](https://api.github.com/users/gpkc)
+- [ilitotor](https://api.github.com/users/ilitotor)
+- [kedark3](https://api.github.com/users/kedark3)
+- [sirex](https://api.github.com/users/sirex)
+- [dependabot[bot]](https://api.github.com/users/dependabot%5Bbot%5D)
+- [dgarcia360](https://api.github.com/users/dgarcia360)
+- [rsnyman](https://api.github.com/users/rsnyman)
+- [andressadotpy](https://api.github.com/users/andressadotpy)
+- [Bernardoow](https://api.github.com/users/Bernardoow)
+- [caneco](https://api.github.com/users/caneco)
+- [dmsimard](https://api.github.com/users/dmsimard)
+- [Sytten](https://api.github.com/users/Sytten)
+- [endersonmenezes](https://api.github.com/users/endersonmenezes)
+- [FrankBattaglia](https://api.github.com/users/FrankBattaglia)
+- [jyejare](https://api.github.com/users/jyejare)
+- [pheanex](https://api.github.com/users/pheanex)
+- [chobeat](https://api.github.com/users/chobeat)
+- [tanalam2411](https://api.github.com/users/tanalam2411)
+- [mspinelli](https://api.github.com/users/mspinelli)
+- [cassiobotaro](https://api.github.com/users/cassiobotaro)
+- [mirekdlugosz](https://api.github.com/users/mirekdlugosz)
+- [adevore](https://api.github.com/users/adevore)
+- [AmbientLighter](https://api.github.com/users/AmbientLighter)
diff --git a/MANIFEST.in b/MANIFEST.in
index 448ac1e..b1638fa 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,4 +3,5 @@ include *.png
include LICENSE
include VERSION
include dynaconf/VERSION
+recursive-include vendor_licenses **
prune dynaconf/vendor_src
diff --git a/PKG-INFO b/PKG-INFO
index 22dcf73..1629de8 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,8 +1,8 @@
Metadata-Version: 2.1
Name: dynaconf
-Version: 3.1.7
+Version: 3.1.12
Summary: The dynamic configurator for your Python Project
-Home-page: https://github.com/rochacbruno/dynaconf
+Home-page: https://github.com/dynaconf/dynaconf
Author: Bruno Rocha
Author-email: rochacbruno@gmail.com
License: MIT
@@ -17,13 +17,14 @@ Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Utilities
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.7
+Requires-Python: >=3.8
Description-Content-Type: text/markdown
Provides-Extra: redis
Provides-Extra: vault
@@ -32,17 +33,26 @@ Provides-Extra: toml
Provides-Extra: ini
Provides-Extra: configobj
Provides-Extra: all
+Provides-Extra: test
License-File: LICENSE
+License-File: vendor_licenses/box-LICENSE.txt
+License-File: vendor_licenses/click-LICENSE.rst
+License-File: vendor_licenses/licenses.sh
+License-File: vendor_licenses/python-dotenv-LICENSE.txt
+License-File: vendor_licenses/ruamel.yaml-LICENSE.txt
+License-File: vendor_licenses/toml-LICENSE.txt
+License-File: vendor_licenses/tomli-LICENSE.txt
+License-File: vendor_licenses/vendor_versions.txt
-[![Dynaconf](docs/img/logo_400.svg?sanitize=true)](http://dynaconf.com)
+<!-- [![Dynaconf](docs/img/logo_400.svg?sanitize=true)](http://dynaconf.com) -->
-> **dynaconf** - Configuration Management for Python.
-
-[![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) [![PyPI](https://img.shields.io/pypi/v/dynaconf.svg)](https://pypi.python.org/pypi/dynaconf) [![PyPI](https://img.shields.io/pypi/pyversions/dynaconf.svg)]() ![PyPI - Downloads](https://img.shields.io/pypi/dm/dynaconf.svg?label=pip%20installs&logo=python) [![CI](https://github.com/rochacbruno/dynaconf/actions/workflows/main.yml/badge.svg)](https://github.com/rochacbruno/dynaconf/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/rochacbruno/dynaconf/branch/master/graph/badge.svg)](https://codecov.io/gh/rochacbruno/dynaconf) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/42d2f11ef0a446808b246c8c69603f6e)](https://www.codacy.com/gh/rochacbruno/dynaconf/dashboard?utm_source=github.com&utm_medium=referral&utm_content=rochacbruno/dynaconf&utm_campaign=Badge_Grade) ![GitHub issues](https://img.shields.io/github/issues/rochacbruno/dynaconf.svg) ![GitHub stars](https://img.shields.io/github/stars/rochacbruno/dynaconf.svg) ![GitHub Release Date](https://img.shields.io/github/release-date/rochacbruno/dynaconf.svg) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/rochacbruno/dynaconf/latest.svg) ![GitHub last commit](https://img.shields.io/github/last-commit/rochacbruno/dynaconf.svg) [![Code Style Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black/) [![Discussion](https://img.shields.io/badge/chat-discussions-blue.svg?logo=googlechat)](https://github.com/rochacbruno/dynaconf/discussions) [![Discussion](https://img.shields.io/badge/demo-learn-yellow.svg?logo=gnubash)](https://github.com/rochacbruno/learndynaconf)
+<p align="center"><img src="/art/header.png?v2" alt="dynaconf. new logo"></p>
+> **dynaconf** - Configuration Management for Python.
+[![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) [![PyPI](https://img.shields.io/pypi/v/dynaconf.svg)](https://pypi.python.org/pypi/dynaconf) [![PyPI](https://img.shields.io/pypi/pyversions/dynaconf.svg)]() ![PyPI - Downloads](https://img.shields.io/pypi/dm/dynaconf.svg?label=pip%20installs&logo=python) [![CI](https://github.com/dynaconf/dynaconf/actions/workflows/main.yml/badge.svg)](https://github.com/dynaconf/dynaconf/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/dynaconf/dynaconf/branch/master/graph/badge.svg)](https://codecov.io/gh/dynaconf/dynaconf) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/3fb2de98464442f99a7663181803b400)](https://www.codacy.com/gh/dynaconf/dynaconf/dashboard?utm_source=github.com&utm_medium=referral&utm_content=dynaconf/dynaconf&utm_campaign=Badge_Grade) ![GitHub stars](https://img.shields.io/github/stars/dynaconf/dynaconf.svg) ![GitHub Release Date](https://img.shields.io/github/release-date/dynaconf/dynaconf.svg) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/dynaconf/dynaconf/latest.svg) ![GitHub last commit](https://img.shields.io/github/last-commit/dynaconf/dynaconf.svg) [![Code Style Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black/)
-[![Foo](https://xscode.com/assets/promo-banner.svg)](https://xscode.com/rochacbruno/dynaconf)
+![GitHub issues](https://img.shields.io/github/issues/dynaconf/dynaconf.svg) [![User Forum](https://img.shields.io/badge/users-forum-blue.svg?logo=googlechat)](https://github.com/dynaconf/dynaconf/discussions) [![Join the chat at https://gitter.im/dynaconf/dev](https://badges.gitter.im/dynaconf/dev.svg)](https://gitter.im/dynaconf/dev?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![ Matrix](https://img.shields.io/badge/dev-room-blue.svg?logo=matrix)](https://matrix.to/#/#dynaconf:matrix.org)
## Features
@@ -57,11 +67,6 @@ License-File: LICENSE
- CLI for common operations such as `init, list, write, validate, export`.
- full docs on https://dynaconf.com
-## Quick start
-
-> **DEMO:** You can see a working demo here: https://github.com/rochacbruno/learndynaconf
-
-
### Install
```bash
@@ -97,7 +102,7 @@ $ dynaconf init -f toml
.
├── config.py # This is from where you import your settings object (required)
├── .secrets.toml # This is to hold sensitive data like passwords and tokens (optional)
-└── settings.toml # This is to hold your application setttings (optional)
+└── settings.toml # This is to hold your application settings (optional)
```
On the file `config.py` Dynaconf init generates the following boilerpate
@@ -168,10 +173,10 @@ There is a lot more you can do, **read the docs:** http://dynaconf.com
## Contribute
-Main discussions happens on [t.me/dynaconf](https://t.me/dynaconf) learn more about how to get involved on [CONTRIBUTING.md guide](CONTRIBUTING.md)
+Main discussions happens on [Discussions Tab](https://github.com/dynaconf/dynaconf/discussions) learn more about how to get involved on [CONTRIBUTING.md guide](CONTRIBUTING.md)
-
-## more
+## More
If you are looking for something similar to Dynaconf to use in your Rust projects: https://github.com/rubik/hydroconf
+And a special thanks to [Caneco](https://twitter.com/caneco) for the logo.
diff --git a/README.md b/README.md
index 6a71d06..24cacd9 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
-[![Dynaconf](docs/img/logo_400.svg?sanitize=true)](http://dynaconf.com)
+<!-- [![Dynaconf](docs/img/logo_400.svg?sanitize=true)](http://dynaconf.com) -->
-> **dynaconf** - Configuration Management for Python.
-
-[![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) [![PyPI](https://img.shields.io/pypi/v/dynaconf.svg)](https://pypi.python.org/pypi/dynaconf) [![PyPI](https://img.shields.io/pypi/pyversions/dynaconf.svg)]() ![PyPI - Downloads](https://img.shields.io/pypi/dm/dynaconf.svg?label=pip%20installs&logo=python) [![CI](https://github.com/rochacbruno/dynaconf/actions/workflows/main.yml/badge.svg)](https://github.com/rochacbruno/dynaconf/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/rochacbruno/dynaconf/branch/master/graph/badge.svg)](https://codecov.io/gh/rochacbruno/dynaconf) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/42d2f11ef0a446808b246c8c69603f6e)](https://www.codacy.com/gh/rochacbruno/dynaconf/dashboard?utm_source=github.com&utm_medium=referral&utm_content=rochacbruno/dynaconf&utm_campaign=Badge_Grade) ![GitHub issues](https://img.shields.io/github/issues/rochacbruno/dynaconf.svg) ![GitHub stars](https://img.shields.io/github/stars/rochacbruno/dynaconf.svg) ![GitHub Release Date](https://img.shields.io/github/release-date/rochacbruno/dynaconf.svg) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/rochacbruno/dynaconf/latest.svg) ![GitHub last commit](https://img.shields.io/github/last-commit/rochacbruno/dynaconf.svg) [![Code Style Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black/) [![Discussion](https://img.shields.io/badge/chat-discussions-blue.svg?logo=googlechat)](https://github.com/rochacbruno/dynaconf/discussions) [![Discussion](https://img.shields.io/badge/demo-learn-yellow.svg?logo=gnubash)](https://github.com/rochacbruno/learndynaconf)
+<p align="center"><img src="/art/header.png?v2" alt="dynaconf. new logo"></p>
+> **dynaconf** - Configuration Management for Python.
+[![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) [![PyPI](https://img.shields.io/pypi/v/dynaconf.svg)](https://pypi.python.org/pypi/dynaconf) [![PyPI](https://img.shields.io/pypi/pyversions/dynaconf.svg)]() ![PyPI - Downloads](https://img.shields.io/pypi/dm/dynaconf.svg?label=pip%20installs&logo=python) [![CI](https://github.com/dynaconf/dynaconf/actions/workflows/main.yml/badge.svg)](https://github.com/dynaconf/dynaconf/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/dynaconf/dynaconf/branch/master/graph/badge.svg)](https://codecov.io/gh/dynaconf/dynaconf) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/3fb2de98464442f99a7663181803b400)](https://www.codacy.com/gh/dynaconf/dynaconf/dashboard?utm_source=github.com&utm_medium=referral&utm_content=dynaconf/dynaconf&utm_campaign=Badge_Grade) ![GitHub stars](https://img.shields.io/github/stars/dynaconf/dynaconf.svg) ![GitHub Release Date](https://img.shields.io/github/release-date/dynaconf/dynaconf.svg) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/dynaconf/dynaconf/latest.svg) ![GitHub last commit](https://img.shields.io/github/last-commit/dynaconf/dynaconf.svg) [![Code Style Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black/)
-[![Foo](https://xscode.com/assets/promo-banner.svg)](https://xscode.com/rochacbruno/dynaconf)
+![GitHub issues](https://img.shields.io/github/issues/dynaconf/dynaconf.svg) [![User Forum](https://img.shields.io/badge/users-forum-blue.svg?logo=googlechat)](https://github.com/dynaconf/dynaconf/discussions) [![Join the chat at https://gitter.im/dynaconf/dev](https://badges.gitter.im/dynaconf/dev.svg)](https://gitter.im/dynaconf/dev?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![ Matrix](https://img.shields.io/badge/dev-room-blue.svg?logo=matrix)](https://matrix.to/#/#dynaconf:matrix.org)
## Features
@@ -21,11 +21,6 @@
- CLI for common operations such as `init, list, write, validate, export`.
- full docs on https://dynaconf.com
-## Quick start
-
-> **DEMO:** You can see a working demo here: https://github.com/rochacbruno/learndynaconf
-
-
### Install
```bash
@@ -61,7 +56,7 @@ $ dynaconf init -f toml
.
├── config.py # This is from where you import your settings object (required)
├── .secrets.toml # This is to hold sensitive data like passwords and tokens (optional)
-└── settings.toml # This is to hold your application setttings (optional)
+└── settings.toml # This is to hold your application settings (optional)
```
On the file `config.py` Dynaconf init generates the following boilerpate
@@ -132,9 +127,10 @@ There is a lot more you can do, **read the docs:** http://dynaconf.com
## Contribute
-Main discussions happens on [t.me/dynaconf](https://t.me/dynaconf) learn more about how to get involved on [CONTRIBUTING.md guide](CONTRIBUTING.md)
-
+Main discussions happens on [Discussions Tab](https://github.com/dynaconf/dynaconf/discussions) learn more about how to get involved on [CONTRIBUTING.md guide](CONTRIBUTING.md)
-## more
+## More
If you are looking for something similar to Dynaconf to use in your Rust projects: https://github.com/rubik/hydroconf
+
+And a special thanks to [Caneco](https://twitter.com/caneco) for the logo.
diff --git a/debian/changelog b/debian/changelog
index 88b6c60..18034db 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+python-dynaconf (3.1.12-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 19 May 2023 06:41:15 -0000
+
python-dynaconf (3.1.7-2) unstable; urgency=medium
[ Debian Janitor ]
diff --git a/dynaconf.egg-info/PKG-INFO b/dynaconf.egg-info/PKG-INFO
index 22dcf73..1629de8 100644
--- a/dynaconf.egg-info/PKG-INFO
+++ b/dynaconf.egg-info/PKG-INFO
@@ -1,8 +1,8 @@
Metadata-Version: 2.1
Name: dynaconf
-Version: 3.1.7
+Version: 3.1.12
Summary: The dynamic configurator for your Python Project
-Home-page: https://github.com/rochacbruno/dynaconf
+Home-page: https://github.com/dynaconf/dynaconf
Author: Bruno Rocha
Author-email: rochacbruno@gmail.com
License: MIT
@@ -17,13 +17,14 @@ Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Utilities
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.7
+Requires-Python: >=3.8
Description-Content-Type: text/markdown
Provides-Extra: redis
Provides-Extra: vault
@@ -32,17 +33,26 @@ Provides-Extra: toml
Provides-Extra: ini
Provides-Extra: configobj
Provides-Extra: all
+Provides-Extra: test
License-File: LICENSE
+License-File: vendor_licenses/box-LICENSE.txt
+License-File: vendor_licenses/click-LICENSE.rst
+License-File: vendor_licenses/licenses.sh
+License-File: vendor_licenses/python-dotenv-LICENSE.txt
+License-File: vendor_licenses/ruamel.yaml-LICENSE.txt
+License-File: vendor_licenses/toml-LICENSE.txt
+License-File: vendor_licenses/tomli-LICENSE.txt
+License-File: vendor_licenses/vendor_versions.txt
-[![Dynaconf](docs/img/logo_400.svg?sanitize=true)](http://dynaconf.com)
+<!-- [![Dynaconf](docs/img/logo_400.svg?sanitize=true)](http://dynaconf.com) -->
-> **dynaconf** - Configuration Management for Python.
-
-[![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) [![PyPI](https://img.shields.io/pypi/v/dynaconf.svg)](https://pypi.python.org/pypi/dynaconf) [![PyPI](https://img.shields.io/pypi/pyversions/dynaconf.svg)]() ![PyPI - Downloads](https://img.shields.io/pypi/dm/dynaconf.svg?label=pip%20installs&logo=python) [![CI](https://github.com/rochacbruno/dynaconf/actions/workflows/main.yml/badge.svg)](https://github.com/rochacbruno/dynaconf/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/rochacbruno/dynaconf/branch/master/graph/badge.svg)](https://codecov.io/gh/rochacbruno/dynaconf) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/42d2f11ef0a446808b246c8c69603f6e)](https://www.codacy.com/gh/rochacbruno/dynaconf/dashboard?utm_source=github.com&utm_medium=referral&utm_content=rochacbruno/dynaconf&utm_campaign=Badge_Grade) ![GitHub issues](https://img.shields.io/github/issues/rochacbruno/dynaconf.svg) ![GitHub stars](https://img.shields.io/github/stars/rochacbruno/dynaconf.svg) ![GitHub Release Date](https://img.shields.io/github/release-date/rochacbruno/dynaconf.svg) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/rochacbruno/dynaconf/latest.svg) ![GitHub last commit](https://img.shields.io/github/last-commit/rochacbruno/dynaconf.svg) [![Code Style Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black/) [![Discussion](https://img.shields.io/badge/chat-discussions-blue.svg?logo=googlechat)](https://github.com/rochacbruno/dynaconf/discussions) [![Discussion](https://img.shields.io/badge/demo-learn-yellow.svg?logo=gnubash)](https://github.com/rochacbruno/learndynaconf)
+<p align="center"><img src="/art/header.png?v2" alt="dynaconf. new logo"></p>
+> **dynaconf** - Configuration Management for Python.
+[![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) [![PyPI](https://img.shields.io/pypi/v/dynaconf.svg)](https://pypi.python.org/pypi/dynaconf) [![PyPI](https://img.shields.io/pypi/pyversions/dynaconf.svg)]() ![PyPI - Downloads](https://img.shields.io/pypi/dm/dynaconf.svg?label=pip%20installs&logo=python) [![CI](https://github.com/dynaconf/dynaconf/actions/workflows/main.yml/badge.svg)](https://github.com/dynaconf/dynaconf/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/dynaconf/dynaconf/branch/master/graph/badge.svg)](https://codecov.io/gh/dynaconf/dynaconf) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/3fb2de98464442f99a7663181803b400)](https://www.codacy.com/gh/dynaconf/dynaconf/dashboard?utm_source=github.com&utm_medium=referral&utm_content=dynaconf/dynaconf&utm_campaign=Badge_Grade) ![GitHub stars](https://img.shields.io/github/stars/dynaconf/dynaconf.svg) ![GitHub Release Date](https://img.shields.io/github/release-date/dynaconf/dynaconf.svg) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/dynaconf/dynaconf/latest.svg) ![GitHub last commit](https://img.shields.io/github/last-commit/dynaconf/dynaconf.svg) [![Code Style Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black/)
-[![Foo](https://xscode.com/assets/promo-banner.svg)](https://xscode.com/rochacbruno/dynaconf)
+![GitHub issues](https://img.shields.io/github/issues/dynaconf/dynaconf.svg) [![User Forum](https://img.shields.io/badge/users-forum-blue.svg?logo=googlechat)](https://github.com/dynaconf/dynaconf/discussions) [![Join the chat at https://gitter.im/dynaconf/dev](https://badges.gitter.im/dynaconf/dev.svg)](https://gitter.im/dynaconf/dev?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![ Matrix](https://img.shields.io/badge/dev-room-blue.svg?logo=matrix)](https://matrix.to/#/#dynaconf:matrix.org)
## Features
@@ -57,11 +67,6 @@ License-File: LICENSE
- CLI for common operations such as `init, list, write, validate, export`.
- full docs on https://dynaconf.com
-## Quick start
-
-> **DEMO:** You can see a working demo here: https://github.com/rochacbruno/learndynaconf
-
-
### Install
```bash
@@ -97,7 +102,7 @@ $ dynaconf init -f toml
.
├── config.py # This is from where you import your settings object (required)
├── .secrets.toml # This is to hold sensitive data like passwords and tokens (optional)
-└── settings.toml # This is to hold your application setttings (optional)
+└── settings.toml # This is to hold your application settings (optional)
```
On the file `config.py` Dynaconf init generates the following boilerpate
@@ -168,10 +173,10 @@ There is a lot more you can do, **read the docs:** http://dynaconf.com
## Contribute
-Main discussions happens on [t.me/dynaconf](https://t.me/dynaconf) learn more about how to get involved on [CONTRIBUTING.md guide](CONTRIBUTING.md)
+Main discussions happens on [Discussions Tab](https://github.com/dynaconf/dynaconf/discussions) learn more about how to get involved on [CONTRIBUTING.md guide](CONTRIBUTING.md)
-
-## more
+## More
If you are looking for something similar to Dynaconf to use in your Rust projects: https://github.com/rubik/hydroconf
+And a special thanks to [Caneco](https://twitter.com/caneco) for the logo.
diff --git a/dynaconf.egg-info/SOURCES.txt b/dynaconf.egg-info/SOURCES.txt
index f400eff..f63aa28 100644
--- a/dynaconf.egg-info/SOURCES.txt
+++ b/dynaconf.egg-info/SOURCES.txt
@@ -1,6 +1,7 @@
3.x-release-notes.md
CHANGELOG.md
CONTRIBUTING.md
+CONTRIBUTORS.md
LICENSE
MANIFEST.in
README.md
@@ -109,4 +110,39 @@ dynaconf/vendor/toml/__init__.py
dynaconf/vendor/toml/decoder.py
dynaconf/vendor/toml/encoder.py
dynaconf/vendor/toml/ordered.py
-dynaconf/vendor/toml/tz.py
\ No newline at end of file
+dynaconf/vendor/toml/tz.py
+dynaconf/vendor/tomllib/__init__.py
+dynaconf/vendor/tomllib/_parser.py
+dynaconf/vendor/tomllib/_re.py
+dynaconf/vendor/tomllib/_types.py
+dynaconf/vendor/tomllib/_writer.py
+tests/test_base.py
+tests/test_basic.py
+tests/test_cli.py
+tests/test_compat.py
+tests/test_django.py
+tests/test_dynabox.py
+tests/test_endtoend.py
+tests/test_env_loader.py
+tests/test_envvar_prefix.py
+tests/test_feature_flag.py
+tests/test_flask.py
+tests/test_ini_loader.py
+tests/test_json_loader.py
+tests/test_nested_loading.py
+tests/test_py_loader.py
+tests/test_redis.py
+tests/test_toml_loader.py
+tests/test_utils.py
+tests/test_validators.py
+tests/test_validators_conditions.py
+tests/test_vault.py
+tests/test_yaml_loader.py
+vendor_licenses/box-LICENSE.txt
+vendor_licenses/click-LICENSE.rst
+vendor_licenses/licenses.sh
+vendor_licenses/python-dotenv-LICENSE.txt
+vendor_licenses/ruamel.yaml-LICENSE.txt
+vendor_licenses/toml-LICENSE.txt
+vendor_licenses/tomli-LICENSE.txt
+vendor_licenses/vendor_versions.txt
\ No newline at end of file
diff --git a/dynaconf.egg-info/entry_points.txt b/dynaconf.egg-info/entry_points.txt
index 2f21b24..44e2a17 100644
--- a/dynaconf.egg-info/entry_points.txt
+++ b/dynaconf.egg-info/entry_points.txt
@@ -1,3 +1,2 @@
[console_scripts]
dynaconf = dynaconf.cli:main
-
diff --git a/dynaconf.egg-info/requires.txt b/dynaconf.egg-info/requires.txt
index d5175cb..f4dfab6 100644
--- a/dynaconf.egg-info/requires.txt
+++ b/dynaconf.egg-info/requires.txt
@@ -1,7 +1,4 @@
-[:python_version < "3.5"]
-typing
-
[all]
redis
ruamel.yaml
@@ -17,6 +14,26 @@ configobj
[redis]
redis
+[test]
+pytest
+pytest-cov
+pytest-xdist
+pytest-mock
+flake8
+pep8-naming
+flake8-debugger
+flake8-print
+flake8-todo
+radon
+flask>=0.12
+django
+python-dotenv
+toml
+codecov
+redis
+hvac
+configobj
+
[toml]
toml
diff --git a/dynaconf/VERSION b/dynaconf/VERSION
index 23887f6..b48ce58 100644
--- a/dynaconf/VERSION
+++ b/dynaconf/VERSION
@@ -1 +1 @@
-3.1.7
+3.1.12
diff --git a/dynaconf/__init__.py b/dynaconf/__init__.py
index a8cd25b..a99ce5f 100644
--- a/dynaconf/__init__.py
+++ b/dynaconf/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from dynaconf.base import LazySettings # noqa
from dynaconf.constants import DEFAULT_SETTINGS_FILES
from dynaconf.contrib import DjangoDynaconf # noqa
diff --git a/dynaconf/base.py b/dynaconf/base.py
index 631f1b2..15f3df4 100644
--- a/dynaconf/base.py
+++ b/dynaconf/base.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import copy
import glob
import importlib
@@ -66,7 +68,7 @@ class LazySettings(LazyObject):
self.__resolve_config_aliases(kwargs)
compat_kwargs(kwargs)
self._kwargs = kwargs
- super(LazySettings, self).__init__()
+ super().__init__()
if wrapped:
if self._django_override:
@@ -87,9 +89,9 @@ class LazySettings(LazyObject):
"environment": "environments",
"ENVIRONMENT": "ENVIRONMENTS",
}
- for mispell, correct in mispells.items():
- if mispell in kwargs:
- kwargs[correct] = kwargs.pop(mispell)
+ for misspell, correct in mispells.items():
+ if misspell in kwargs:
+ kwargs[correct] = kwargs.pop(misspell)
for_dynaconf_keys = {
key
@@ -144,6 +146,15 @@ class LazySettings(LazyObject):
"""
return self.get(*args, **kwargs)
+ @property
+ def _should_load_dotenv(self):
+ """Chicken and egg problem, we must manually check envvar
+ before deciding if we are loading envvars :)"""
+ _environ_load_dotenv = parse_conf_data(
+ os.environ.get("LOAD_DOTENV_FOR_DYNACONF"), tomlfy=True
+ )
+ return self._kwargs.get("load_dotenv", _environ_load_dotenv)
+
def _setup(self):
"""Initial setup, run once."""
@@ -155,7 +166,7 @@ class LazySettings(LazyObject):
DeprecationWarning,
)
- default_settings.reload(self._kwargs.get("load_dotenv"))
+ default_settings.reload(self._should_load_dotenv)
environment_variable = self._kwargs.get(
"ENVVAR_FOR_DYNACONF", default_settings.ENVVAR_FOR_DYNACONF
)
@@ -169,10 +180,10 @@ class LazySettings(LazyObject):
Allows user to reconfigure settings object passing a new settings
module or separated kwargs
- :param settings_module: defines the setttings file
+ :param settings_module: defines the settings file
:param kwargs: override default settings
"""
- default_settings.reload(self._kwargs.get("load_dotenv"))
+ default_settings.reload(self._should_load_dotenv)
environment_var = self._kwargs.get(
"ENVVAR_FOR_DYNACONF", default_settings.ENVVAR_FOR_DYNACONF
)
@@ -198,7 +209,7 @@ class Settings:
def __init__(self, settings_module=None, **kwargs): # pragma: no cover
"""Execute loaders and custom initialization
- :param settings_module: defines the setttings file
+ :param settings_module: defines the settings file
:param kwargs: override default settings
"""
self._fresh = False
@@ -218,6 +229,9 @@ class Settings:
self._not_installed_warnings = []
self._validate_only = kwargs.pop("validate_only", None)
self._validate_exclude = kwargs.pop("validate_exclude", None)
+ self._validate_only_current_env = kwargs.pop(
+ "validate_only_current_env", False
+ )
self.validators = ValidatorList(
self, validators=kwargs.pop("validators", None)
@@ -230,11 +244,20 @@ class Settings:
self.set(key, value)
# execute loaders only after setting defaults got from kwargs
self._defaults = kwargs
- self.execute_loaders()
- self.validators.validate(
- only=self._validate_only, exclude=self._validate_exclude
- )
+ # The following flags are used for when copying of settings is done
+ skip_loaders = kwargs.get("dynaconf_skip_loaders", False)
+ skip_validators = kwargs.get("dynaconf_skip_validators", False)
+
+ if not skip_loaders:
+ self.execute_loaders()
+
+ if not skip_validators:
+ self.validators.validate(
+ only=self._validate_only,
+ exclude=self._validate_exclude,
+ only_current_env=self._validate_only_current_env,
+ )
def __call__(self, *args, **kwargs):
"""Allow direct call of `settings('val')`
@@ -246,7 +269,7 @@ class Settings:
"""Allow `settings.FOO = 'value'` while keeping internal attrs."""
if name in RESERVED_ATTRS:
- super(Settings, self).__setattr__(name, value)
+ super().__setattr__(name, value)
else:
self.set(name, value)
@@ -254,7 +277,7 @@ class Settings:
"""stores reference in `_deleted` for proper error management"""
self._deleted.add(name)
if hasattr(self, name):
- super(Settings, self).__delattr__(name)
+ super().__delattr__(name)
def __contains__(self, item):
"""Respond to `item in settings`"""
@@ -316,18 +339,37 @@ class Settings:
"""Redirects to store object"""
return self.store.values()
- def setdefault(self, item, default):
- """Returns value if exists or set it as the given default"""
+ def setdefault(self, item, default, apply_default_on_none=False):
+ """Returns value if exists or set it as the given default
+
+ apply_default_on_none: if True, default is set when value is None
+ """
value = self.get(item, empty)
- if value is empty and default is not empty:
+
+ # Yaml loader reads empty values as None, would we apply defaults?
+ global_apply_default = (
+ self.get("APPLY_DEFAULT_ON_NONE_FOR_DYNACONF") is not None
+ )
+ apply_default = default is not empty and (
+ value is empty
+ or (
+ value is None
+ and (
+ apply_default_on_none is True
+ or global_apply_default is True
+ )
+ )
+ )
+
+ if apply_default:
self.set(
item,
default,
loader_identifier="setdefault",
tomlfy=True,
- dotted_lookup=True,
)
return default
+
return value
def as_dict(self, env=None, internal=False):
@@ -379,11 +421,11 @@ class Settings:
default=None,
cast=None,
fresh=False,
- dotted_lookup=True,
+ dotted_lookup=empty,
parent=None,
):
"""
- Get a value from settings store, this is the prefered way to access::
+ Get a value from settings store, this is the preferred way to access::
>>> from dynaconf import settings
>>> settings.get('KEY')
@@ -401,6 +443,9 @@ class Settings:
# turn FOO__bar__ZAZ in `FOO.bar.ZAZ`
key = key.replace(nested_sep, ".")
+ if dotted_lookup is empty:
+ dotted_lookup = self._store.get("DOTTED_LOOKUP_FOR_DYNACONF")
+
if "." in key and dotted_lookup:
return self._dotted_get(
dotted_key=key,
@@ -438,7 +483,7 @@ class Settings:
"""Check if key exists
:param key: the name of setting variable
- :param fresh: if key should be taken from source direclty
+ :param fresh: if key should be taken from source directly
:return: Boolean
"""
key = upperfy(key)
@@ -646,9 +691,10 @@ class Settings:
"""Return the current active env"""
if self.ENVIRONMENTS_FOR_DYNACONF is False:
- return "main"
+ return self.MAIN_ENV_FOR_DYNACONF.lower()
if self.FORCE_ENV_FOR_DYNACONF is not None:
+ self.ENV_FOR_DYNACONF = self.FORCE_ENV_FOR_DYNACONF
return self.FORCE_ENV_FOR_DYNACONF
try:
@@ -707,8 +753,8 @@ class Settings:
"""
env = env or self.ENV_FOR_DYNACONF
- if not isinstance(env, str):
- raise AttributeError("env should be a string")
+ if not isinstance(env, str) or "_" in env or " " in env:
+ raise ValueError("env should be a string without _ or spaces")
env = env.upper()
@@ -793,7 +839,7 @@ class Settings:
value,
loader_identifier=None,
tomlfy=False,
- dotted_lookup=True,
+ dotted_lookup=empty,
is_secret="DeprecatedArgument", # noqa
merge=False,
):
@@ -805,6 +851,9 @@ class Settings:
:param tomlfy: Bool define if value is parsed by toml (defaults False)
:param merge: Bool define if existing nested data will be merged.
"""
+ if dotted_lookup is empty:
+ dotted_lookup = self.get("DOTTED_LOOKUP_FOR_DYNACONF")
+
nested_sep = self.get("NESTED_SEPARATOR_FOR_DYNACONF")
if nested_sep and nested_sep in key:
# turn FOO__bar__ZAZ in `FOO.bar.ZAZ`
@@ -826,9 +875,15 @@ class Settings:
if getattr(value, "_dynaconf_reset", False): # pragma: no cover
# just in case someone use a `@reset` in a first level var.
- # NOTE: @reset/Reset is deprecated in v3.0.0
value = value.unwrap()
+ if getattr(value, "_dynaconf_merge_unique", False):
+ # just in case someone use a `@merge_unique` in a first level var
+ if existing:
+ value = object_merge(existing, value.unwrap(), unique=True)
+ else:
+ value = value.unwrap()
+
if getattr(value, "_dynaconf_merge", False):
# just in case someone use a `@merge` in a first level var
if existing:
@@ -842,6 +897,7 @@ class Settings:
value = object_merge(existing, value)
else:
# `dynaconf_merge` may be used within the key structure
+ # Or merge_enabled is set to True
value = self._merge_before_set(existing, value)
if isinstance(value, dict):
@@ -849,7 +905,7 @@ class Settings:
self.store[key] = value
self._deleted.discard(key)
- super(Settings, self).__setattr__(key, value)
+ super().__setattr__(key, value)
# set loader identifiers so cleaners know which keys to clean
if loader_identifier and loader_identifier in self.loaded_by_loaders:
@@ -868,6 +924,7 @@ class Settings:
tomlfy=False,
merge=False,
is_secret="DeprecatedArgument", # noqa
+ dotted_lookup=empty,
**kwargs,
):
"""
@@ -898,6 +955,7 @@ class Settings:
loader_identifier=loader_identifier,
tomlfy=tomlfy,
merge=merge,
+ dotted_lookup=dotted_lookup,
)
def _merge_before_set(self, existing, value):
@@ -952,7 +1010,7 @@ class Settings:
"""Execute all internal and registered loaders
:param env: The environment to load
- :param silent: If loading erros is silenced
+ :param silent: If loading errors is silenced
:param key: if provided load a single key
:param filename: optional custom filename to load
:param loaders: optional list of loader modules
@@ -973,8 +1031,8 @@ class Settings:
loaders = self.loaders
- for loader in loaders:
- loader.load(self, env, silent=silent, key=key)
+ for core_loader in loaders:
+ core_loader.load(self, env, silent=silent, key=key)
self.load_includes(env, silent=silent, key=key)
execute_hooks("post", self, env, silent=silent, key=key)
@@ -1017,18 +1075,16 @@ class Settings:
# continue the loop.
continue
- # python 3.6 does not resolve Pathlib basedirs
- # issue #494
root_dir = str(self._root_path or os.getcwd())
+
+ # Issue #494
if (
isinstance(_filename, Path)
and str(_filename.parent) in root_dir
): # pragma: no cover
filepath = str(_filename)
else:
- filepath = os.path.join(
- self._root_path or os.getcwd(), str(_filename)
- )
+ filepath = os.path.join(root_dir, str(_filename))
paths = [
p
@@ -1038,6 +1094,7 @@ class Settings:
local_paths = [
p for p in sorted(glob.glob(filepath)) if ".local." in p
]
+
# Handle possible *.globs sorted alphanumeric
for path in paths + local_paths:
if path in already_loaded: # pragma: no cover
@@ -1141,7 +1198,15 @@ class Settings:
def dynaconf_clone(self):
"""Clone the current settings object."""
- return copy.deepcopy(self)
+ try:
+ return copy.deepcopy(self)
+ except TypeError:
+ # can't deepcopy settings object because of module object
+ # being set as value in the settings dict
+ new_data = self.to_dict(internal=True)
+ new_data["dynaconf_skip_loaders"] = True
+ new_data["dynaconf_skip_validators"] = True
+ return Settings(**new_data)
@property
def dynaconf(self):
@@ -1152,7 +1217,7 @@ class Settings:
internal methods and attrs.
"""
- class AttrProxy(object):
+ class AttrProxy:
def __init__(self, obj):
self.obj = obj
@@ -1208,11 +1273,13 @@ RESERVED_ATTRS = (
"_not_installed_warnings",
"_store",
"_warn_dynaconf_global_settings",
+ "_should_load_dotenv",
"environ",
"SETTINGS_MODULE",
"filter_strategy",
"validators",
"_validate_only",
"_validate_exclude",
+ "_validate_only_current_env",
]
)
diff --git a/dynaconf/cli.py b/dynaconf/cli.py
index 5bb8316..8b8ab5d 100644
--- a/dynaconf/cli.py
+++ b/dynaconf/cli.py
@@ -1,5 +1,7 @@
+from __future__ import annotations
+
import importlib
-import io
+import json
import os
import pprint
import sys
@@ -18,13 +20,20 @@ from dynaconf.utils import upperfy
from dynaconf.utils.files import read_file
from dynaconf.utils.functional import empty
from dynaconf.utils.parse_conf import parse_conf_data
+from dynaconf.utils.parse_conf import unparse_conf_data
from dynaconf.validator import ValidationError
from dynaconf.validator import Validator
from dynaconf.vendor import click
from dynaconf.vendor import toml
+from dynaconf.vendor import tomllib
+os.environ["PYTHONIOENCODING"] = "utf-8"
-CWD = Path.cwd()
+CWD = None
+try:
+ CWD = Path.cwd()
+except FileNotFoundError:
+ pass
EXTS = ["ini", "toml", "yaml", "json", "py", "env"]
WRITERS = ["ini", "toml", "yaml", "json", "py", "redis", "vault", "env"]
@@ -38,6 +47,8 @@ def set_settings(ctx, instance=None):
settings = None
+ _echo_enabled = ctx.invoked_subcommand not in ["get", None]
+
if instance is not None:
if ctx.invoked_subcommand in ["init"]:
raise click.UsageError(
@@ -48,10 +59,11 @@ def set_settings(ctx, instance=None):
elif "FLASK_APP" in os.environ: # pragma: no cover
with suppress(ImportError, click.UsageError):
from flask.cli import ScriptInfo # noqa
+ from dynaconf import FlaskDynaconf
flask_app = ScriptInfo().load_app()
- settings = flask_app.config
- click.echo(
+ settings = FlaskDynaconf(flask_app, **flask_app.config).settings
+ _echo_enabled and click.echo(
click.style(
"Flask app detected", fg="white", bg="bright_black"
)
@@ -67,7 +79,7 @@ def set_settings(ctx, instance=None):
settings = LazySettings()
if settings is not None:
- click.echo(
+ _echo_enabled and click.echo(
click.style(
"Django app detected", fg="white", bg="bright_black"
)
@@ -94,7 +106,7 @@ def set_settings(ctx, instance=None):
def import_settings(dotted_path):
"""Import settings instance from python dotted path.
- Last item in dotted path must be settings instace.
+ Last item in dotted path must be settings instance.
Example: import_settings('path.to.settings')
"""
@@ -108,6 +120,8 @@ def import_settings(dotted_path):
module = importlib.import_module(module)
except ImportError as e:
raise click.UsageError(e)
+ except FileNotFoundError:
+ return
try:
return getattr(module, name)
except AttributeError as e:
@@ -158,7 +172,7 @@ def show_banner(ctx, param, value):
return
set_settings(ctx)
click.echo(settings.dynaconf_banner)
- click.echo("Learn more at: http://github.com/rochacbruno/dynaconf")
+ click.echo("Learn more at: http://github.com/dynaconf/dynaconf")
ctx.exit()
@@ -256,6 +270,19 @@ def init(ctx, fileformat, path, env, _vars, _secrets, wg, y, django):
"""
click.echo("⚙️ Configuring your Dynaconf environment")
click.echo("-" * 42)
+ if "FLASK_APP" in os.environ: # pragma: no cover
+ click.echo(
+ "⚠️ Flask detected, you can't use `dynaconf init` "
+ "on a flask project, instead go to dynaconf.com/flask/ "
+ "for more information.\n"
+ "Or add the following to your app.py\n"
+ "\n"
+ "from dynaconf import FlaskDynaconf\n"
+ "app = Flask(__name__)\n"
+ "FlaskDynaconf(app)\n"
+ )
+ exit(1)
+
path = Path(path)
if env is not None:
@@ -364,15 +391,14 @@ def init(ctx, fileformat, path, env, _vars, _secrets, wg, y, django):
ignore_line = ".secrets.*"
comment = "\n# Ignore dynaconf secret files\n"
if not gitignore_path.exists():
- with io.open(str(gitignore_path), "w", encoding=ENC) as f:
+ with open(str(gitignore_path), "w", encoding=ENC) as f:
f.writelines([comment, ignore_line, "\n"])
else:
existing = (
- ignore_line
- in io.open(str(gitignore_path), encoding=ENC).read()
+ ignore_line in open(str(gitignore_path), encoding=ENC).read()
)
if not existing: # pragma: no cover
- with io.open(str(gitignore_path), "a+", encoding=ENC) as f:
+ with open(str(gitignore_path), "a+", encoding=ENC) as f:
f.writelines([comment, ignore_line, "\n"])
click.echo(
@@ -401,6 +427,51 @@ def init(ctx, fileformat, path, env, _vars, _secrets, wg, y, django):
)
+@main.command(name="get")
+@click.argument("key", required=True)
+@click.option(
+ "--default",
+ "-d",
+ default=empty,
+ help="Default value if settings doesn't exist",
+)
+@click.option(
+ "--env", "-e", default=None, help="Filters the env to get the values"
+)
+@click.option(
+ "--unparse",
+ "-u",
+ default=False,
+ help="Unparse data by adding markers such as @none, @int etc..",
+ is_flag=True,
+)
+def get(key, default, env, unparse):
+ """Returns the raw value for a settings key.
+
+ If result is a dict, list or tuple it is printes as a valid json string.
+ """
+ if env:
+ env = env.strip()
+ if key:
+ key = key.strip()
+
+ if env:
+ settings.setenv(env)
+
+ if default is not empty:
+ result = settings.get(key, default)
+ else:
+ result = settings[key] # let the keyerror raises
+
+ if unparse:
+ result = unparse_conf_data(result)
+
+ if isinstance(result, (dict, list, tuple)):
+ result = json.dumps(result, sort_keys=True)
+
+ click.echo(result, nl=False)
+
+
@main.command(name="list")
@click.option(
"--env", "-e", default=None, help="Filters the env to get the values"
@@ -652,7 +723,16 @@ def validate(path): # pragma: no cover
click.echo(click.style(f"{path} not found", fg="white", bg="red"))
sys.exit(1)
- validation_data = toml.load(open(str(path)))
+ try: # try tomlib first
+ validation_data = tomllib.load(open(str(path), "rb"))
+ except UnicodeDecodeError: # fallback to legacy toml (TBR in 4.0.0)
+ warnings.warn(
+ "TOML files should have only UTF-8 encoded characters. "
+ "starting on 4.0.0 dynaconf will stop allowing invalid chars.",
+ )
+ validation_data = toml.load(
+ open(str(path), encoding=default_settings.ENCODING_FOR_DYNACONF),
+ )
success = True
for env, name_data in validation_data.items():
diff --git a/dynaconf/constants.py b/dynaconf/constants.py
index 1b9e7d3..6256273 100644
--- a/dynaconf/constants.py
+++ b/dynaconf/constants.py
@@ -1,4 +1,6 @@
# pragma: no cover
+from __future__ import annotations
+
INI_EXTENSIONS = (".ini", ".conf", ".properties")
TOML_EXTENSIONS = (".toml", ".tml")
YAML_EXTENSIONS = (".yaml", ".yml")
@@ -16,7 +18,7 @@ EXTERNAL_LOADERS = {
DJANGO_PATCH = """
# HERE STARTS DYNACONF EXTENSION LOAD (Keep at the very bottom of settings.py)
-# Read more at https://dynaconf.readthedocs.io/en/latest/guides/django.html
+# Read more at https://www.dynaconf.com/django/
import dynaconf # noqa
settings = dynaconf.DjangoDynaconf(__name__) # noqa
# HERE ENDS DYNACONF EXTENSION LOAD (No more code below this line)
diff --git a/dynaconf/contrib/__init__.py b/dynaconf/contrib/__init__.py
index 565ae21..2c0279a 100644
--- a/dynaconf/contrib/__init__.py
+++ b/dynaconf/contrib/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from dynaconf.contrib.django_dynaconf_v2 import DjangoDynaconf # noqa
from dynaconf.contrib.flask_dynaconf import DynaconfConfig # noqa
from dynaconf.contrib.flask_dynaconf import FlaskDynaconf # noqa
diff --git a/dynaconf/contrib/django_dynaconf_v2.py b/dynaconf/contrib/django_dynaconf_v2.py
index bb86454..aac4aab 100644
--- a/dynaconf/contrib/django_dynaconf_v2.py
+++ b/dynaconf/contrib/django_dynaconf_v2.py
@@ -1,9 +1,9 @@
"""Dynaconf django extension
-In the `django_project/settings.py` put at the very botton of the file:
+In the `django_project/settings.py` put at the very bottom of the file:
# HERE STARTS DYNACONF EXTENSION LOAD (Keep at the very bottom of settings.py)
-# Read more at https://dynaconf.readthedocs.io/en/latest/guides/django.html
+# Read more at https://www.dynaconf.com/django/
import dynaconf # noqa
settings = dynaconf.DjangoDynaconf(__name__) # noqa
# HERE ENDS DYNACONF EXTENSION LOAD (No more code below this line)
@@ -20,6 +20,8 @@ On your projects root folder now you can start as::
DJANGO_ALLOWED_HOSTS='["localhost"]' \
python manage.py runserver
"""
+from __future__ import annotations
+
import inspect
import os
import sys
@@ -49,12 +51,15 @@ def load(django_settings_module_name=None, **kwargs): # pragma: no cover
os.environ["DJANGO_SETTINGS_MODULE"]
]
+ settings_module_name = django_settings_module.__name__
settings_file = os.path.abspath(django_settings_module.__file__)
_root_path = os.path.dirname(settings_file)
# 1) Create the lazy settings object reusing settings_module consts
options = {
- k: v for k, v in django_settings_module.__dict__.items() if k.isupper()
+ k.upper(): v
+ for k, v in django_settings_module.__dict__.items()
+ if k.isupper()
}
options.update(kwargs)
options.setdefault(
@@ -95,6 +100,16 @@ def load(django_settings_module_name=None, **kwargs): # pragma: no cover
lazy_settings.update(dj)
+ # Allow dynaconf_hooks to be in the same folder as the django.settings
+ dynaconf.loaders.execute_hooks(
+ "post",
+ lazy_settings,
+ lazy_settings.current_env,
+ modules=[settings_module_name],
+ files=[settings_file],
+ )
+ lazy_settings._loaded_py_modules.insert(0, settings_module_name)
+
# 5) Patch django.conf.settings
class Wrapper:
diff --git a/dynaconf/contrib/flask_dynaconf.py b/dynaconf/contrib/flask_dynaconf.py
index c4e1b77..a305194 100644
--- a/dynaconf/contrib/flask_dynaconf.py
+++ b/dynaconf/contrib/flask_dynaconf.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import warnings
from collections import ChainMap
from contextlib import suppress
@@ -69,7 +71,7 @@ class FlaskDynaconf:
app,
ENV='MYSITE',
SETTINGS_FILE='settings.yml',
- EXTRA_VALUE='You can add aditional config vars here'
+ EXTRA_VALUE='You can add additional config vars here'
)
Take a look at examples/flask in Dynaconf repository
@@ -90,8 +92,7 @@ class FlaskDynaconf:
"To use this extension Flask must be installed "
"install it with: pip install flask"
)
- self.kwargs = kwargs
-
+ self.kwargs = {k.upper(): v for k, v in kwargs.items()}
kwargs.setdefault("ENVVAR_PREFIX", "FLASK")
env_prefix = f"{kwargs['ENVVAR_PREFIX']}_ENV" # FLASK_ENV
kwargs.setdefault("ENV_SWITCHER", env_prefix)
@@ -143,7 +144,7 @@ class DynaconfConfig(Config):
def __init__(self, _settings, _app, *args, **kwargs):
"""perform the initial load"""
- super(DynaconfConfig, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
# Bring Dynaconf instance value to Flask Config
Config.update(self, _settings.store)
@@ -178,6 +179,9 @@ class DynaconfConfig(Config):
def items(self):
return self._chain_map().items()
+ def setdefault(self, key, value=None):
+ return self._chain_map().setdefault(key, value)
+
def __iter__(self):
return self._chain_map().__iter__()
diff --git a/dynaconf/default_settings.py b/dynaconf/default_settings.py
index 66601b0..40c627b 100644
--- a/dynaconf/default_settings.py
+++ b/dynaconf/default_settings.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import importlib
import os
import sys
@@ -84,8 +86,9 @@ if not SETTINGS_FILE_FOR_DYNACONF and mispelled_files is not None:
# # In dynaconf 1.0.0 `NAMESPACE` got renamed to `ENV`
-# If provided environments will be loaded separatelly
+# If provided environments will be loaded separately
ENVIRONMENTS_FOR_DYNACONF = get("ENVIRONMENTS_FOR_DYNACONF", False)
+MAIN_ENV_FOR_DYNACONF = get("MAIN_ENV_FOR_DYNACONF", "MAIN")
# If False dynaconf will allow access to first level settings only in upper
LOWERCASE_READ_FOR_DYNACONF = get("LOWERCASE_READ_FOR_DYNACONF", True)
@@ -122,12 +125,17 @@ IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF = get(
"IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF", False
)
+AUTO_CAST_FOR_DYNACONF = get("AUTO_CAST_FOR_DYNACONF", True)
+
# The default encoding to open settings files
ENCODING_FOR_DYNACONF = get("ENCODING_FOR_DYNACONF", "utf-8")
# Merge objects on load
MERGE_ENABLED_FOR_DYNACONF = get("MERGE_ENABLED_FOR_DYNACONF", False)
+# Lookup keys considering dots as separators
+DOTTED_LOOKUP_FOR_DYNACONF = get("DOTTED_LOOKUP_FOR_DYNACONF", True)
+
# BY default `__` is the separator for nested env vars
# export `DYNACONF__DATABASE__server=server.com`
# export `DYNACONF__DATABASE__PORT=6666`
@@ -164,6 +172,7 @@ default_vault = {
"timeout": get("VAULT_TIMEOUT_FOR_DYNACONF", None),
"proxies": get("VAULT_PROXIES_FOR_DYNACONF", None),
"allow_redirects": get("VAULT_ALLOW_REDIRECTS_FOR_DYNACONF", None),
+ "namespace": get("VAULT_NAMESPACE_FOR_DYNACONF", None),
}
VAULT_FOR_DYNACONF = get("VAULT_FOR_DYNACONF", default_vault)
VAULT_ENABLED_FOR_DYNACONF = get("VAULT_ENABLED_FOR_DYNACONF", False)
@@ -230,6 +239,13 @@ PRELOAD_FOR_DYNACONF = get("PRELOAD_FOR_DYNACONF", [])
# Files to skip if found on search tree
SKIP_FILES_FOR_DYNACONF = get("SKIP_FILES_FOR_DYNACONF", [])
+# YAML reads empty vars as None, should dynaconf apply validator defaults?
+# this is set to None, then evaluated on base.Settings.setdefault
+# possible values are True/False
+APPLY_DEFAULT_ON_NONE_FOR_DYNACONF = get(
+ "APPLY_DEFAULT_ON_NONE_FOR_DYNACONF", None
+)
+
# Backwards compatibility with renamed variables
for old, new in RENAMED_VARS.items():
diff --git a/dynaconf/loaders/__init__.py b/dynaconf/loaders/__init__.py
index d4f50cf..e18cb84 100644
--- a/dynaconf/loaders/__init__.py
+++ b/dynaconf/loaders/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import importlib
import os
@@ -69,7 +71,8 @@ def _run_hook_module(hook, hook_module, obj, key=None):
return
if hook_module and getattr(hook_module, "_error", False):
- raise hook_module._error
+ if not isinstance(hook_module._error, FileNotFoundError):
+ raise hook_module._error
hook_func = getattr(hook_module, hook, None)
if hook_func:
@@ -85,13 +88,16 @@ def _run_hook_module(hook, hook_module, obj, key=None):
obj._loaded_hooks[hook_module.__file__][hook] = hook_dict
-def execute_hooks(hook, obj, env=None, silent=True, key=None):
+def execute_hooks(
+ hook, obj, env=None, silent=True, key=None, modules=None, files=None
+):
"""Execute dynaconf_hooks from module or filepath."""
if hook not in ["post"]:
raise ValueError(f"hook {hook} not supported yet.")
# try to load hooks using python module __name__
- for loaded_module in obj._loaded_py_modules:
+ modules = modules or obj._loaded_py_modules
+ for loaded_module in modules:
hook_module_name = ".".join(
loaded_module.split(".")[:-1] + ["dynaconf_hooks"]
)
@@ -109,7 +115,8 @@ def execute_hooks(hook, obj, env=None, silent=True, key=None):
)
# Try to load from python filename path
- for loaded_file in obj._loaded_files:
+ files = files or obj._loaded_files
+ for loaded_file in files:
hook_file = os.path.join(
os.path.dirname(loaded_file), "dynaconf_hooks.py"
)
@@ -261,7 +268,7 @@ def write(filename, data, env=None):
loader_name = f"{filename.rpartition('.')[-1]}_loader"
loader = globals().get(loader_name)
if not loader:
- raise IOError(f"{loader_name} cannot be found.")
+ raise OSError(f"{loader_name} cannot be found.")
data = DynaBox(data, box_settings={}).to_dict()
if loader is not py_loader and env and env not in data:
diff --git a/dynaconf/loaders/base.py b/dynaconf/loaders/base.py
index 9239920..dec5cb0 100644
--- a/dynaconf/loaders/base.py
+++ b/dynaconf/loaders/base.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
import warnings
@@ -19,7 +21,14 @@ class BaseLoader:
"""
def __init__(
- self, obj, env, identifier, extensions, file_reader, string_reader
+ self,
+ obj,
+ env,
+ identifier,
+ extensions,
+ file_reader,
+ string_reader,
+ opener_params=None,
):
"""Instantiates a loader for different sources"""
self.obj = obj
@@ -28,6 +37,10 @@ class BaseLoader:
self.extensions = extensions
self.file_reader = file_reader
self.string_reader = string_reader
+ self.opener_params = opener_params or {
+ "mode": "r",
+ "encoding": obj.get("ENCODING_FOR_DYNACONF", "utf-8"),
+ }
@staticmethod
def warn_not_installed(obj, identifier): # pragma: no cover
@@ -44,7 +57,7 @@ class BaseLoader:
:param filename: Optional filename to load
:param key: if provided load a single key
- :param silent: if load erros should be silenced
+ :param silent: if load errors should be silenced
"""
filename = filename or self.obj.get(self.identifier.upper())
@@ -75,17 +88,12 @@ class BaseLoader:
for source_file in files:
if source_file.endswith(self.extensions):
try:
- with io.open(
- source_file,
- encoding=self.obj.get(
- "ENCODING_FOR_DYNACONF", "utf-8"
- ),
- ) as open_file:
+ with open(source_file, **self.opener_params) as open_file:
content = self.file_reader(open_file)
self.obj._loaded_files.append(source_file)
if content:
data[source_file] = content
- except IOError as e:
+ except OSError as e:
if ".local." not in source_file:
warnings.warn(
f"{self.identifier}_loader: {source_file} "
@@ -118,6 +126,9 @@ class BaseLoader:
# is there a `dynaconf_merge` on top level of file?
file_merge = file_data.get("dynaconf_merge")
+ # is there a flag disabling dotted lookup on file?
+ file_dotted_lookup = file_data.get("dynaconf_dotted_lookup")
+
for env in build_env_list(self.obj, self.env):
env = env.lower() # lower for better comparison
@@ -131,15 +142,12 @@ class BaseLoader:
if not data:
continue
- if env != self.obj.get("DEFAULT_ENV_FOR_DYNACONF").lower():
- identifier = f"{self.identifier}_{env}"
- else:
- identifier = self.identifier
self._set_data_to_obj(
data,
- identifier,
+ f"{self.identifier}_{env}",
file_merge,
key,
+ file_dotted_lookup=file_dotted_lookup,
)
def _set_data_to_obj(
@@ -148,6 +156,7 @@ class BaseLoader:
identifier,
file_merge=None,
key=False,
+ file_dotted_lookup=None,
):
"""Calls settings.set to add the keys"""
# data 1st level keys should be transformed to upper case.
@@ -160,11 +169,21 @@ class BaseLoader:
# is there a `dynaconf_merge` inside an `[env]`?
file_merge = file_merge or data.pop("DYNACONF_MERGE", False)
+
+ # If not passed or passed as None,
+ # look for inner [env] value, or default settings.
+ if file_dotted_lookup is None:
+ file_dotted_lookup = data.pop(
+ "DYNACONF_DOTTED_LOOKUP",
+ self.obj.get("DOTTED_LOOKUP_FOR_DYNACONF"),
+ )
+
if not key:
self.obj.update(
data,
loader_identifier=identifier,
merge=file_merge,
+ dotted_lookup=file_dotted_lookup,
)
elif key in data:
self.obj.set(
@@ -172,4 +191,5 @@ class BaseLoader:
data.get(key),
loader_identifier=identifier,
merge=file_merge,
+ dotted_lookup=file_dotted_lookup,
)
diff --git a/dynaconf/loaders/env_loader.py b/dynaconf/loaders/env_loader.py
index e7b13bd..779e9a4 100644
--- a/dynaconf/loaders/env_loader.py
+++ b/dynaconf/loaders/env_loader.py
@@ -1,8 +1,20 @@
+from __future__ import annotations
+
from os import environ
+from dynaconf.utils import missing
from dynaconf.utils import upperfy
from dynaconf.utils.parse_conf import parse_conf_data
-from dynaconf.vendor.dotenv import cli as dotenv_cli
+
+DOTENV_IMPORTED = False
+try:
+ from dynaconf.vendor.dotenv import cli as dotenv_cli
+
+ DOTENV_IMPORTED = True
+except ImportError:
+ pass
+except FileNotFoundError:
+ pass
IDENTIFIER = "env"
@@ -54,9 +66,8 @@ def load_from_env(
# Load environment variables in bulk (when matching).
else:
- # Only known variables should be loaded from environment.
+ # Only known variables should be loaded from environment?
ignore_unknown = obj.get("IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF")
- known_keys = set(obj.store)
trim_len = len(env_)
data = {
@@ -69,16 +80,21 @@ def load_from_env(
# Ignore environment variables that haven't been
# pre-defined in settings space.
ignore_unknown
- and key[trim_len:] not in known_keys
+ and obj.get(key[trim_len:], default=missing) is missing
)
}
# Update the settings space based on gathered data from environment.
if data:
+ filter_strategy = obj.get("FILTER_STRATEGY")
+ if filter_strategy:
+ data = filter_strategy(data)
obj.update(data, loader_identifier=identifier)
def write(settings_path, settings_data, **kwargs):
"""Write data to .env file"""
+ if not DOTENV_IMPORTED:
+ return
for key, value in settings_data.items():
quote_mode = (
isinstance(value, str)
diff --git a/dynaconf/loaders/ini_loader.py b/dynaconf/loaders/ini_loader.py
index 7b83042..c3b56fd 100644
--- a/dynaconf/loaders/ini_loader.py
+++ b/dynaconf/loaders/ini_loader.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
from pathlib import Path
@@ -51,7 +53,7 @@ def write(settings_path, settings_data, merge=True):
"""
settings_path = Path(settings_path)
if settings_path.exists() and merge: # pragma: no cover
- with io.open(
+ with open(
str(settings_path), encoding=default_settings.ENCODING_FOR_DYNACONF
) as open_file:
object_merge(ConfigObj(open_file).dict(), settings_data)
diff --git a/dynaconf/loaders/json_loader.py b/dynaconf/loaders/json_loader.py
index bfc81aa..72c1e34 100644
--- a/dynaconf/loaders/json_loader.py
+++ b/dynaconf/loaders/json_loader.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
import json
from pathlib import Path
@@ -58,12 +60,12 @@ def write(settings_path, settings_data, merge=True):
"""
settings_path = Path(settings_path)
if settings_path.exists() and merge: # pragma: no cover
- with io.open(
+ with open(
str(settings_path), encoding=default_settings.ENCODING_FOR_DYNACONF
) as open_file:
object_merge(json.load(open_file), settings_data)
- with io.open(
+ with open(
str(settings_path),
"w",
encoding=default_settings.ENCODING_FOR_DYNACONF,
diff --git a/dynaconf/loaders/py_loader.py b/dynaconf/loaders/py_loader.py
index bc95c94..f296459 100644
--- a/dynaconf/loaders/py_loader.py
+++ b/dynaconf/loaders/py_loader.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import errno
import importlib
import inspect
@@ -108,12 +110,12 @@ def import_from_filename(obj, filename, silent=False): # pragma: no cover
mod._is_error = False
mod._error = None
try:
- with io.open(
+ with open(
_find_file(filename),
encoding=default_settings.ENCODING_FOR_DYNACONF,
) as config_file:
exec(compile(config_file.read(), filename, "exec"), mod.__dict__)
- except IOError as e:
+ except OSError as e:
e.strerror = (
f"py_loader: error loading file " f"({e.strerror} {filename})\n"
)
@@ -136,7 +138,7 @@ def write(settings_path, settings_data, merge=True):
existing = DynaconfDict()
load(existing, str(settings_path))
object_merge(existing, settings_data)
- with io.open(
+ with open(
str(settings_path),
"w",
encoding=default_settings.ENCODING_FOR_DYNACONF,
diff --git a/dynaconf/loaders/redis_loader.py b/dynaconf/loaders/redis_loader.py
index 906a35d..1123cb0 100644
--- a/dynaconf/loaders/redis_loader.py
+++ b/dynaconf/loaders/redis_loader.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from dynaconf.utils import build_env_list
from dynaconf.utils import upperfy
from dynaconf.utils.parse_conf import parse_conf_data
@@ -67,7 +69,7 @@ def write(obj, data=None, **kwargs):
raise RuntimeError(
"Redis is not configured \n"
"export REDIS_ENABLED_FOR_DYNACONF=true\n"
- "and configure the REDIS_FOR_DYNACONF_* variables"
+ "and configure the REDIS_*_FOR_DYNACONF variables"
)
client = StrictRedis(**obj.REDIS_FOR_DYNACONF)
holder = obj.get("ENVVAR_PREFIX_FOR_DYNACONF").upper()
diff --git a/dynaconf/loaders/toml_loader.py b/dynaconf/loaders/toml_loader.py
index 8248957..f4f6e17 100644
--- a/dynaconf/loaders/toml_loader.py
+++ b/dynaconf/loaders/toml_loader.py
@@ -1,11 +1,14 @@
-import io
+from __future__ import annotations
+
+import warnings
from pathlib import Path
from dynaconf import default_settings
from dynaconf.constants import TOML_EXTENSIONS
from dynaconf.loaders.base import BaseLoader
from dynaconf.utils import object_merge
-from dynaconf.vendor import toml
+from dynaconf.vendor import toml # Backwards compatibility with uiri/toml
+from dynaconf.vendor import tomllib # New tomllib stdlib on py3.11
def load(obj, env=None, silent=True, key=None, filename=None):
@@ -20,19 +23,54 @@ def load(obj, env=None, silent=True, key=None, filename=None):
:return: None
"""
- loader = BaseLoader(
- obj=obj,
- env=env,
- identifier="toml",
- extensions=TOML_EXTENSIONS,
- file_reader=toml.load,
- string_reader=toml.loads,
- )
- loader.load(
- filename=filename,
- key=key,
- silent=silent,
- )
+ try:
+ loader = BaseLoader(
+ obj=obj,
+ env=env,
+ identifier="toml",
+ extensions=TOML_EXTENSIONS,
+ file_reader=tomllib.load,
+ string_reader=tomllib.loads,
+ opener_params={"mode": "rb"},
+ )
+ loader.load(
+ filename=filename,
+ key=key,
+ silent=silent,
+ )
+ except UnicodeDecodeError: # pragma: no cover
+ """
+ NOTE: Compat functions exists to keep backwards compatibility with
+ the new tomllib library. The old library was called `toml` and
+ the new one is called `tomllib`.
+
+ The old lib uiri/toml allowed unicode characters and re-added files
+ as string.
+
+ The new tomllib (stdlib) does not allow unicode characters, only
+ utf-8 encoded, and read files as binary.
+
+ NOTE: In dynaconf 4.0.0 we will drop support for the old library
+ removing the compat functions and calling directly the new lib.
+ """
+ loader = BaseLoader(
+ obj=obj,
+ env=env,
+ identifier="toml",
+ extensions=TOML_EXTENSIONS,
+ file_reader=toml.load,
+ string_reader=toml.loads,
+ )
+ loader.load(
+ filename=filename,
+ key=key,
+ silent=silent,
+ )
+
+ warnings.warn(
+ "TOML files should have only UTF-8 encoded characters. "
+ "starting on 4.0.0 dynaconf will stop allowing invalid chars.",
+ )
def write(settings_path, settings_data, merge=True):
@@ -44,17 +82,33 @@ def write(settings_path, settings_data, merge=True):
"""
settings_path = Path(settings_path)
if settings_path.exists() and merge: # pragma: no cover
- with io.open(
- str(settings_path), encoding=default_settings.ENCODING_FOR_DYNACONF
+ try: # tomllib first
+ with open(str(settings_path), "rb") as open_file:
+ object_merge(tomllib.load(open_file), settings_data)
+ except UnicodeDecodeError: # pragma: no cover
+ # uiri/toml fallback (TBR on 4.0.0)
+ with open(
+ str(settings_path),
+ encoding=default_settings.ENCODING_FOR_DYNACONF,
+ ) as open_file:
+ object_merge(toml.load(open_file), settings_data)
+
+ try: # tomllib first
+ with open(str(settings_path), "wb") as open_file:
+ tomllib.dump(encode_nulls(settings_data), open_file)
+ except UnicodeEncodeError: # pragma: no cover
+ # uiri/toml fallback (TBR on 4.0.0)
+ with open(
+ str(settings_path),
+ "w",
+ encoding=default_settings.ENCODING_FOR_DYNACONF,
) as open_file:
- object_merge(toml.load(open_file), settings_data)
-
- with io.open(
- str(settings_path),
- "w",
- encoding=default_settings.ENCODING_FOR_DYNACONF,
- ) as open_file:
- toml.dump(encode_nulls(settings_data), open_file)
+ toml.dump(encode_nulls(settings_data), open_file)
+
+ warnings.warn(
+ "TOML files should have only UTF-8 encoded characters. "
+ "starting on 4.0.0 dynaconf will stop allowing invalid chars.",
+ )
def encode_nulls(data):
diff --git a/dynaconf/loaders/vault_loader.py b/dynaconf/loaders/vault_loader.py
index e5d6841..d816ffc 100644
--- a/dynaconf/loaders/vault_loader.py
+++ b/dynaconf/loaders/vault_loader.py
@@ -1,5 +1,7 @@
# docker run -e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' -p 8200:8200 vault
# pip install hvac
+from __future__ import annotations
+
from dynaconf.utils import build_env_list
from dynaconf.utils.parse_conf import parse_conf_data
@@ -31,7 +33,7 @@ def get_client(obj):
**{k: v for k, v in obj.VAULT_FOR_DYNACONF.items() if v is not None}
)
if obj.VAULT_ROLE_ID_FOR_DYNACONF is not None:
- client.auth_approle(
+ client.auth.approle.login(
role_id=obj.VAULT_ROLE_ID_FOR_DYNACONF,
secret_id=obj.get("VAULT_SECRET_ID_FOR_DYNACONF"),
)
@@ -56,7 +58,7 @@ def get_client(obj):
"Vault authentication error: is VAULT_TOKEN_FOR_DYNACONF or "
"VAULT_ROLE_ID_FOR_DYNACONF defined?"
)
- client.kv.default_kv_version = obj.VAULT_KV_VERSION_FOR_DYNACONF
+ client.secrets.kv.default_kv_version = obj.VAULT_KV_VERSION_FOR_DYNACONF
return client
@@ -71,14 +73,27 @@ def load(obj, env=None, silent=None, key=None):
"""
client = get_client(obj)
try:
- dirs = client.secrets.kv.list_secrets(
- path=obj.VAULT_PATH_FOR_DYNACONF,
- mount_point=obj.VAULT_MOUNT_POINT_FOR_DYNACONF,
- )["data"]["keys"]
+ if obj.VAULT_KV_VERSION_FOR_DYNACONF == 2:
+ dirs = client.secrets.kv.v2.list_secrets(
+ path=obj.VAULT_PATH_FOR_DYNACONF,
+ mount_point=obj.VAULT_MOUNT_POINT_FOR_DYNACONF,
+ )["data"]["keys"]
+ else:
+ dirs = client.secrets.kv.v1.list_secrets(
+ path=obj.VAULT_PATH_FOR_DYNACONF,
+ mount_point=obj.VAULT_MOUNT_POINT_FOR_DYNACONF,
+ )["data"]["keys"]
except InvalidPath:
# The given path is not a directory
dirs = []
- env_list = dirs + build_env_list(obj, env)
+ # First look for secrets into environments less store
+ if not obj.ENVIRONMENTS_FOR_DYNACONF:
+ # By adding '', dynaconf will now read secrets from environments-less
+ # store which are not written by `dynaconf write` to Vault store
+ env_list = [obj.MAIN_ENV_FOR_DYNACONF.lower(), ""]
+ # Finally, look for secret into all the environments
+ else:
+ env_list = dirs + build_env_list(obj, env)
for env in env_list:
path = "/".join([obj.VAULT_PATH_FOR_DYNACONF, env])
try:
@@ -99,7 +114,11 @@ def load(obj, env=None, silent=None, key=None):
# extract the inner data
data = data.get("data", {}).get("data", {})
try:
- if obj.VAULT_KV_VERSION_FOR_DYNACONF == 2 and data:
+ if (
+ obj.VAULT_KV_VERSION_FOR_DYNACONF == 2
+ and obj.ENVIRONMENTS_FOR_DYNACONF
+ and data
+ ):
data = data.get("data", {})
if data and key:
value = parse_conf_data(
diff --git a/dynaconf/loaders/yaml_loader.py b/dynaconf/loaders/yaml_loader.py
index fa63150..37b0b6c 100644
--- a/dynaconf/loaders/yaml_loader.py
+++ b/dynaconf/loaders/yaml_loader.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
from pathlib import Path
from warnings import warn
@@ -65,12 +67,12 @@ def write(settings_path, settings_data, merge=True):
"""
settings_path = Path(settings_path)
if settings_path.exists() and merge: # pragma: no cover
- with io.open(
+ with open(
str(settings_path), encoding=default_settings.ENCODING_FOR_DYNACONF
) as open_file:
object_merge(yaml.safe_load(open_file), settings_data)
- with io.open(
+ with open(
str(settings_path),
"w",
encoding=default_settings.ENCODING_FOR_DYNACONF,
diff --git a/dynaconf/strategies/filtering.py b/dynaconf/strategies/filtering.py
index 12b49a0..ef1f51f 100644
--- a/dynaconf/strategies/filtering.py
+++ b/dynaconf/strategies/filtering.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from dynaconf.utils import upperfy
@@ -5,7 +7,7 @@ class PrefixFilter:
def __init__(self, prefix):
if not isinstance(prefix, str):
raise TypeError("`SETTINGS_FILE_PREFIX` must be str")
- self.prefix = "{}_".format(upperfy(prefix))
+ self.prefix = f"{upperfy(prefix)}_"
def __call__(self, data):
"""Filter incoming data by prefix"""
diff --git a/dynaconf/test_settings.py b/dynaconf/test_settings.py
index 61e7638..3c43ec9 100644
--- a/dynaconf/test_settings.py
+++ b/dynaconf/test_settings.py
@@ -1,4 +1,6 @@
# pragma: no cover
+from __future__ import annotations
+
TESTING = True
LOADERS_FOR_DYNACONF = [
"dynaconf.loaders.env_loader",
diff --git a/dynaconf/utils/__init__.py b/dynaconf/utils/__init__.py
index 30d910f..2d1a8c1 100644
--- a/dynaconf/utils/__init__.py
+++ b/dynaconf/utils/__init__.py
@@ -5,13 +5,8 @@ import warnings
from collections import defaultdict
from json import JSONDecoder
from typing import Any
-from typing import Dict
from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Tuple
from typing import TYPE_CHECKING
-from typing import Union
if TYPE_CHECKING: # pragma: no cover
@@ -34,7 +29,7 @@ if os.name == "nt": # pragma: no cover
def object_merge(
- old: Any, new: Any, unique: bool = False, full_path: List[str] = None
+ old: Any, new: Any, unique: bool = False, full_path: list[str] = None
) -> Any:
"""
Recursively merge two data structures, new is mutated in-place.
@@ -51,32 +46,48 @@ def object_merge(
return new
if isinstance(old, list) and isinstance(new, list):
+
+ # 726: allow local_merge to override global merge on lists
+ if "dynaconf_merge_unique" in new:
+ new.remove("dynaconf_merge_unique")
+ unique = True
+
for item in old[::-1]:
if unique and item in new:
continue
new.insert(0, item)
if isinstance(old, dict) and isinstance(new, dict):
- existing_value = recursive_get(old, full_path) # doesnt handle None
+ existing_value = recursive_get(old, full_path) # doesn't handle None
# Need to make every `None` on `_store` to be an wrapped `LazyNone`
- for key, value in old.items():
+ # data coming from source, in `new` can be mix case: KEY4|key4|Key4
+ # data existing on `old` object has the correct case: key4|KEY4|Key4
+ # So we need to ensure that new keys matches the existing keys
+ for new_key in list(new.keys()):
+ correct_case_key = find_the_correct_casing(new_key, old)
+ if correct_case_key:
+ new[correct_case_key] = new.pop(new_key)
+
+ for old_key, value in old.items():
+ # This is for when the dict exists internally
+ # but the new value on the end of full path is the same
if (
existing_value is not None
- and key.lower() == full_path[-1].lower()
+ and old_key.lower() == full_path[-1].lower()
and existing_value is value
):
# Here Be The Dragons
# This comparison needs to be smarter
continue
- if key not in new:
- new[key] = value
+ if old_key not in new:
+ new[old_key] = value
else:
object_merge(
value,
- new[key],
+ new[old_key],
full_path=full_path[1:] if full_path else None,
)
@@ -86,15 +97,15 @@ def object_merge(
def recursive_get(
- obj: Union[DynaBox, Dict[str, int], Dict[str, Union[str, int]]],
- names: Optional[List[str]],
+ obj: DynaBox | dict[str, int] | dict[str, str | int],
+ names: list[str] | None,
) -> Any:
"""Given a dot accessible object and a list of names `foo.bar.zaz`
- gets recursivelly all names one by one obj.foo.bar.zaz.
+ gets recursively all names one by one obj.foo.bar.zaz.
"""
if not names:
return
- head, tail = names[0], names[1:]
+ head, *tail = names
result = getattr(obj, head, None)
if not tail:
return result
@@ -102,7 +113,7 @@ def recursive_get(
def handle_metavalues(
- old: Union[DynaBox, Dict[str, int], Dict[str, Union[str, int]]], new: Any
+ old: DynaBox | dict[str, int] | dict[str, str | int], new: Any
) -> None:
"""Cleanup of MetaValues on new dict"""
@@ -111,7 +122,6 @@ def handle_metavalues(
# MetaValue instances
if getattr(new[key], "_dynaconf_reset", False): # pragma: no cover
# a Reset on `new` triggers reasign of existing data
- # @reset is deprecated on v3.0.0
new[key] = new[key].unwrap()
elif getattr(new[key], "_dynaconf_del", False):
# a Del on `new` triggers deletion of existing data
@@ -178,7 +188,7 @@ class DynaconfDict(dict):
self._not_installed_warnings = []
self._validate_only = kwargs.pop("validate_only", None)
self._validate_exclude = kwargs.pop("validate_exclude", None)
- super(DynaconfDict, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def set(self, key: str, value: str, *args, **kwargs) -> None:
self[key] = value
@@ -208,7 +218,7 @@ RENAMED_VARS = {
}
-def compat_kwargs(kwargs: Dict[str, Any]) -> None:
+def compat_kwargs(kwargs: dict[str, Any]) -> None:
"""To keep backwards compat change the kwargs to new names"""
warn_deprecations(kwargs)
for old, new in RENAMED_VARS.items():
@@ -230,7 +240,7 @@ class Missing:
"""Respond to boolean duck-typing."""
return False
- def __eq__(self, other: Union[DynaBox, Missing]) -> bool:
+ def __eq__(self, other: DynaBox | Missing) -> bool:
"""Equality check for a singleton."""
return isinstance(other, self.__class__)
@@ -249,7 +259,7 @@ class Missing:
missing = Missing()
-def deduplicate(list_object: List[str]) -> List[str]:
+def deduplicate(list_object: list[str]) -> list[str]:
"""Rebuild `list_object` removing duplicated and keeping order"""
new = []
for item in list_object:
@@ -269,8 +279,8 @@ def warn_deprecations(data: Any) -> None:
def trimmed_split(
- s: str, seps: Union[str, Tuple[str, str]] = (";", ",")
-) -> List[str]:
+ s: str, seps: str | tuple[str, str] = (";", ",")
+) -> list[str]:
"""Given a string s, split is by one of one of the seps."""
for sep in seps:
if sep not in s:
@@ -280,7 +290,7 @@ def trimmed_split(
return [s] # raw un-splitted
-def ensure_a_list(data: Any) -> Union[List[int], List[str]]:
+def ensure_a_list(data: Any) -> list[int] | list[str]:
"""Ensure data is a list or wrap it in a list"""
if not data:
return []
@@ -292,9 +302,7 @@ def ensure_a_list(data: Any) -> Union[List[int], List[str]]:
return [data]
-def build_env_list(
- obj: Union[Settings, LazySettings], env: Optional[str]
-) -> List[str]:
+def build_env_list(obj: Settings | LazySettings, env: str | None) -> list[str]:
"""Build env list for loaders to iterate.
Arguments:
@@ -305,27 +313,27 @@ def build_env_list(
[str] -- A list of string names of the envs to load.
"""
# add the [default] env
- env_list = [obj.get("DEFAULT_ENV_FOR_DYNACONF")]
+ env_list = [(obj.get("DEFAULT_ENV_FOR_DYNACONF") or "default").lower()]
# compatibility with older versions that still uses [dynaconf] as
# [default] env
- global_env = obj.get("ENVVAR_PREFIX_FOR_DYNACONF") or "DYNACONF"
+ global_env = (obj.get("ENVVAR_PREFIX_FOR_DYNACONF") or "dynaconf").lower()
if global_env not in env_list:
env_list.append(global_env)
# add the current env
- if obj.current_env and obj.current_env not in env_list:
- env_list.append(obj.current_env)
+ current_env = obj.current_env
+ if current_env and current_env.lower() not in env_list:
+ env_list.append(current_env.lower())
# add a manually set env
- if env and env not in env_list:
- env_list.append(env)
+ if env and env.lower() not in env_list:
+ env_list.append(env.lower())
# add the [global] env
- env_list.append("GLOBAL")
+ env_list.append("global")
- # loaders are responsible to change to lower/upper cases
- return [env.lower() for env in env_list]
+ return env_list
def upperfy(key: str) -> str:
@@ -355,7 +363,7 @@ def upperfy(key: str) -> str:
return key.upper()
-def multi_replace(text: str, patterns: Dict[str, str]) -> str:
+def multi_replace(text: str, patterns: dict[str, str]) -> str:
"""Replaces multiple pairs in a string
Arguments:
@@ -372,7 +380,7 @@ def multi_replace(text: str, patterns: Dict[str, str]) -> str:
def extract_json_objects(
text: str, decoder: JSONDecoder = JSONDecoder()
-) -> Iterator[Dict[str, Union[int, Dict[Any, Any]]]]:
+) -> Iterator[dict[str, int | dict[Any, Any]]]:
"""Find JSON objects in text, and yield the decoded JSON data
Does not attempt to look for JSON arrays, text, or other JSON types outside
@@ -393,7 +401,7 @@ def extract_json_objects(
def recursively_evaluate_lazy_format(
- value: Any, settings: Union[Settings, LazySettings]
+ value: Any, settings: Settings | LazySettings
) -> Any:
"""Given a value as a data structure, traverse all its members
to find Lazy values and evaluate it.
@@ -431,3 +439,23 @@ def isnamedtupleinstance(value):
if not isinstance(f, tuple):
return False
return all(type(n) == str for n in f)
+
+
+def find_the_correct_casing(key: str, data: dict[str, Any]) -> str | None:
+ """Given a key, find the proper casing in data
+
+ Arguments:
+ key {str} -- A key to be searched in data
+ data {dict} -- A dict to be searched
+
+ Returns:
+ str -- The proper casing of the key in data
+ """
+ if key in data:
+ return key
+ for k in data.keys():
+ if k.lower() == key.lower():
+ return k
+ if k.replace(" ", "_").lower() == key.lower():
+ return k
+ return None
diff --git a/dynaconf/utils/boxing.py b/dynaconf/utils/boxing.py
index 9a30453..ff78f12 100644
--- a/dynaconf/utils/boxing.py
+++ b/dynaconf/utils/boxing.py
@@ -1,8 +1,10 @@
+from __future__ import annotations
+
import inspect
from functools import wraps
+from dynaconf.utils import find_the_correct_casing
from dynaconf.utils import recursively_evaluate_lazy_format
-from dynaconf.utils import upperfy
from dynaconf.utils.functional import empty
from dynaconf.vendor.box import Box
@@ -33,18 +35,18 @@ class DynaBox(Box):
@evaluate_lazy_format
def __getattr__(self, item, *args, **kwargs):
try:
- return super(DynaBox, self).__getattr__(item, *args, **kwargs)
+ return super().__getattr__(item, *args, **kwargs)
except (AttributeError, KeyError):
- n_item = item.lower() if item.isupper() else upperfy(item)
- return super(DynaBox, self).__getattr__(n_item, *args, **kwargs)
+ n_item = find_the_correct_casing(item, self) or item
+ return super().__getattr__(n_item, *args, **kwargs)
@evaluate_lazy_format
def __getitem__(self, item, *args, **kwargs):
try:
- return super(DynaBox, self).__getitem__(item, *args, **kwargs)
+ return super().__getitem__(item, *args, **kwargs)
except (AttributeError, KeyError):
- n_item = item.lower() if item.isupper() else upperfy(item)
- return super(DynaBox, self).__getitem__(n_item, *args, **kwargs)
+ n_item = find_the_correct_casing(item, self) or item
+ return super().__getitem__(n_item, *args, **kwargs)
def __copy__(self):
return self.__class__(
@@ -58,22 +60,11 @@ class DynaBox(Box):
box_settings=self._box_config.get("box_settings"),
)
- def _case_insensitive_get(self, item, default=None):
- """adds a bit of overhead but allows case insensitive get
- See issue: #486
- """
- lower_self = {k.casefold(): v for k, v in self.items()}
- return lower_self.get(item.casefold(), default)
-
@evaluate_lazy_format
def get(self, item, default=None, *args, **kwargs):
- if item not in self: # toggle case
- item = item.lower() if item.isupper() else upperfy(item)
- value = super(DynaBox, self).get(item, empty, *args, **kwargs)
- if value is empty:
- # see Issue: #486
- return self._case_insensitive_get(item, default)
- return value
+ n_item = find_the_correct_casing(item, self) or item
+ value = super().get(n_item, empty, *args, **kwargs)
+ return value if value is not empty else default
def __dir__(self):
keys = list(self.keys())
diff --git a/dynaconf/utils/files.py b/dynaconf/utils/files.py
index 3331db5..ec6fbd8 100644
--- a/dynaconf/utils/files.py
+++ b/dynaconf/utils/files.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import inspect
import io
import os
@@ -10,7 +12,7 @@ def _walk_to_root(path, break_at=None):
Directories starting from the given directory up to the root or break_at
"""
if not os.path.exists(path): # pragma: no cover
- raise IOError("Starting path not found")
+ raise OSError("Starting path not found")
if os.path.isfile(path): # pragma: no cover
path = os.path.dirname(path)
@@ -42,10 +44,13 @@ def find_file(filename=".env", project_root=None, skip_files=None, **kwargs):
- Current working directory
For each path in the `search_tree` it will also look for an
- aditional `./config` folder.
+ additional `./config` folder.
"""
search_tree = []
- work_dir = os.getcwd()
+ try:
+ work_dir = os.getcwd()
+ except FileNotFoundError:
+ return ""
skip_files = skip_files or []
# If filename is an absolute path and exists, just return it
@@ -84,7 +89,7 @@ def find_file(filename=".env", project_root=None, skip_files=None, **kwargs):
def read_file(path, **kwargs):
content = ""
- with io.open(path, **kwargs) as open_file:
+ with open(path, **kwargs) as open_file:
content = open_file.read().strip()
return content
diff --git a/dynaconf/utils/functional.py b/dynaconf/utils/functional.py
index 147d26a..c9a93af 100644
--- a/dynaconf/utils/functional.py
+++ b/dynaconf/utils/functional.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import copy
import operator
diff --git a/dynaconf/utils/parse_conf.py b/dynaconf/utils/parse_conf.py
index c42b07a..ac3262d 100644
--- a/dynaconf/utils/parse_conf.py
+++ b/dynaconf/utils/parse_conf.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import os
import re
@@ -9,7 +11,9 @@ from dynaconf.utils import isnamedtupleinstance
from dynaconf.utils import multi_replace
from dynaconf.utils import recursively_evaluate_lazy_format
from dynaconf.utils.boxing import DynaBox
+from dynaconf.utils.functional import empty
from dynaconf.vendor import toml
+from dynaconf.vendor import tomllib
try:
from jinja2 import Environment
@@ -80,6 +84,9 @@ class Merge(MetaValue):
_dynaconf_merge = True
def __init__(self, value, box_settings, unique=False):
+ if unique:
+ self._dynaconf_merge_unique = True
+
self.box_settings = box_settings
self.value = parse_conf_data(
@@ -162,19 +169,26 @@ class Lazy:
_dynaconf_lazy_format = True
- def __init__(self, value, formatter=Formatters.python_formatter):
+ def __init__(
+ self, value=empty, formatter=Formatters.python_formatter, casting=None
+ ):
self.value = value
self.formatter = formatter
+ self.casting = casting
@property
def context(self):
"""Builds a context for formatting."""
return {"env": os.environ, "this": self.settings}
- def __call__(self, settings):
+ def __call__(self, settings, validator_object=None):
"""LazyValue triggers format lazily."""
self.settings = settings
- return self.formatter(self.value, **self.context)
+ self.context["_validator_object"] = validator_object
+ result = self.formatter(self.value, **self.context)
+ if self.casting is not None:
+ result = self.casting(result)
+ return result
def __str__(self):
"""Gives string representation for the object."""
@@ -188,6 +202,11 @@ class Lazy:
"""Encodes this object values to be serializable to json"""
return f"@{self.formatter} {self.value}"
+ def set_casting(self, casting):
+ """Set the casting and return the instance."""
+ self.casting = casting
+ return self
+
def try_to_encode(value, callback=str):
"""Tries to encode a value by verifying existence of `_dynaconf_encode`"""
@@ -210,11 +229,25 @@ def evaluate_lazy_format(f):
converters = {
- "@str": str,
- "@int": int,
- "@float": float,
- "@bool": lambda value: str(value).lower() in true_values,
- "@json": json.loads,
+ "@str": lambda value: value.set_casting(str)
+ if isinstance(value, Lazy)
+ else str(value),
+ "@int": lambda value: value.set_casting(int)
+ if isinstance(value, Lazy)
+ else int(value),
+ "@float": lambda value: value.set_casting(float)
+ if isinstance(value, Lazy)
+ else float(value),
+ "@bool": lambda value: value.set_casting(
+ lambda x: str(x).lower() in true_values
+ )
+ if isinstance(value, Lazy)
+ else str(value).lower() in true_values,
+ "@json": lambda value: value.set_casting(
+ lambda x: json.loads(x.replace("'", '"'))
+ )
+ if isinstance(value, Lazy)
+ else json.loads(value),
"@format": lambda value: Lazy(value),
"@jinja": lambda value: Lazy(value, formatter=Formatters.jinja_formatter),
# Meta Values to trigger pre assignment actions
@@ -230,6 +263,7 @@ converters = {
"@comment": lambda value: None,
"@null": lambda value: None,
"@none": lambda value: None,
+ "@empty": lambda value: empty,
}
@@ -244,10 +278,22 @@ def get_converter(converter_key, value, box_settings):
def parse_with_toml(data):
"""Uses TOML syntax to parse data"""
- try:
- return toml.loads(f"key={data}")["key"]
- except (toml.TomlDecodeError, KeyError):
- return data
+ try: # try tomllib first
+ try:
+ return tomllib.loads(f"key={data}")["key"]
+ except (tomllib.TOMLDecodeError, KeyError):
+ return data
+ except UnicodeDecodeError: # pragma: no cover
+ # fallback to toml (TBR in 4.0.0)
+ try:
+ return toml.loads(f"key={data}")["key"]
+ except (toml.TomlDecodeError, KeyError):
+ return data
+ warnings.warn(
+ "TOML files should have only UTF-8 encoded characters. "
+ "starting on 4.0.0 dynaconf will stop allowing invalid chars.",
+ DeprecationWarning,
+ )
def _parse_conf_data(data, tomlfy=False, box_settings=None):
@@ -265,8 +311,12 @@ def _parse_conf_data(data, tomlfy=False, box_settings=None):
# not enforced to not break backwards compatibility with custom loaders
box_settings = box_settings or {}
- cast_toggler = os.environ.get("AUTO_CAST_FOR_DYNACONF", "true").lower()
- castenabled = cast_toggler not in false_values
+ castenabled = box_settings.get("AUTO_CAST_FOR_DYNACONF", empty)
+ if castenabled is empty:
+ castenabled = (
+ os.environ.get("AUTO_CAST_FOR_DYNACONF", "true").lower()
+ not in false_values
+ )
if (
castenabled
@@ -274,10 +324,23 @@ def _parse_conf_data(data, tomlfy=False, box_settings=None):
and isinstance(data, str)
and data.startswith(tuple(converters.keys()))
):
- parts = data.partition(" ")
- converter_key = parts[0]
- value = parts[-1]
- value = get_converter(converter_key, value, box_settings)
+ # Check combination token is used
+ comb_token = re.match(
+ f"^({'|'.join(converters.keys())}) @(jinja|format)",
+ data,
+ )
+ if comb_token:
+ tokens = comb_token.group(0)
+ converter_key_list = tokens.split(" ")
+ value = data.replace(tokens, "").strip()
+ else:
+ parts = data.partition(" ")
+ converter_key_list = [parts[0]]
+ value = parts[-1]
+
+ # Parse the converters iteratively
+ for converter_key in converter_key_list[::-1]:
+ value = get_converter(converter_key, value, box_settings)
else:
value = parse_with_toml(data) if tomlfy else data
@@ -289,7 +352,7 @@ def _parse_conf_data(data, tomlfy=False, box_settings=None):
def parse_conf_data(data, tomlfy=False, box_settings=None):
- # fix for https://github.com/rochacbruno/dynaconf/issues/595
+ # fix for https://github.com/dynaconf/dynaconf/issues/595
if isnamedtupleinstance(data):
return data
@@ -297,7 +360,6 @@ def parse_conf_data(data, tomlfy=False, box_settings=None):
box_settings = box_settings or {}
if isinstance(data, (tuple, list)):
-
# recursively parse each sequence item
return [
parse_conf_data(item, tomlfy=tomlfy, box_settings=box_settings)
diff --git a/dynaconf/validator.py b/dynaconf/validator.py
index 225f5be..b85269f 100644
--- a/dynaconf/validator.py
+++ b/dynaconf/validator.py
@@ -5,11 +5,7 @@ from itertools import chain
from types import MappingProxyType
from typing import Any
from typing import Callable
-from typing import Dict
-from typing import List
-from typing import Optional
from typing import Sequence
-from typing import Union
from dynaconf import validator_conditions
from dynaconf.utils import ensure_a_list
@@ -27,7 +23,12 @@ EQUALITY_ATTRS = (
class ValidationError(Exception):
- pass
+ """Raised when a validation fails"""
+
+ def __init__(self, message: str, *args, **kwargs):
+ self.details = kwargs.pop("details", [])
+ super().__init__(message, *args, **kwargs)
+ self.message = message
class Validator:
@@ -100,15 +101,16 @@ class Validator:
def __init__(
self,
*names: str,
- must_exist: Optional[bool] = None,
- required: Optional[bool] = None, # alias for `must_exist`
- condition: Optional[Callable[[Any], bool]] = None,
- when: Optional[Validator] = None,
- env: Optional[Union[str, Sequence[str]]] = None,
- messages: Optional[Dict[str, str]] = None,
- cast: Optional[Callable[[Any], Any]] = None,
- default: Optional[Union[Any, Callable[[Any, Validator], Any]]] = empty,
- description: Optional[str] = None,
+ must_exist: bool | None = None,
+ required: bool | None = None, # alias for `must_exist`
+ condition: Callable[[Any], bool] | None = None,
+ when: Validator | None = None,
+ env: str | Sequence[str] | None = None,
+ messages: dict[str, str] | None = None,
+ cast: Callable[[Any], Any] | None = None,
+ default: Any | Callable[[Any, Validator], Any] | None = empty,
+ description: str | None = None,
+ apply_default_on_none: bool | None = False,
**operations: Any,
) -> None:
# Copy immutable MappingProxyType as a mutable dict
@@ -130,7 +132,11 @@ class Validator:
self.operations = operations
self.default = default
self.description = description
- self.envs: Optional[Sequence[str]] = None
+ self.envs: Sequence[str] | None = None
+ self.apply_default_on_none = apply_default_on_none
+
+ # See #585
+ self.is_type_of = operations.get("is_type_of")
if isinstance(env, str):
self.envs = [env]
@@ -162,8 +168,9 @@ class Validator:
def validate(
self,
settings: Any,
- only: Optional[Union[str, Sequence]] = None,
- exclude: Optional[Union[str, Sequence]] = None,
+ only: str | Sequence | None = None,
+ exclude: str | Sequence | None = None,
+ only_current_env: bool = False,
) -> None:
"""Raise ValidationError if invalid"""
# If only or exclude are not set, this value always passes startswith
@@ -189,6 +196,15 @@ class Validator:
# if when is invalid, return canceling validation flow
return
+ if only_current_env:
+ if settings.current_env.upper() in map(
+ lambda s: s.upper(), self.envs
+ ):
+ self._validate_items(
+ settings, settings.current_env, only=only, exclude=exclude
+ )
+ return
+
# If only using current_env, skip using_env decoration (reload)
if (
len(self.envs) == 1
@@ -207,9 +223,9 @@ class Validator:
def _validate_items(
self,
settings: Any,
- env: Optional[str] = None,
- only: Optional[Union[str, Sequence]] = None,
- exclude: Optional[Union[str, Sequence]] = None,
+ env: str | None = None,
+ only: str | Sequence | None = None,
+ exclude: str | Sequence | None = None,
) -> None:
env = env or settings.current_env
for name in self.names:
@@ -230,49 +246,73 @@ class Validator:
else:
default_value = empty
- value = self.cast(settings.setdefault(name, default_value))
+ # THIS IS A FIX FOR #585 in contrast with #799
+ # toml considers signed strings "+-1" as integers
+ # however existing users are passing strings
+ # to default on validator (see #585)
+ # The solution we added on #667 introduced a new problem
+ # This fix here makes it to work for both cases.
+ if (
+ isinstance(default_value, str)
+ and default_value.startswith(("+", "-"))
+ and self.is_type_of is str
+ ):
+ # avoid TOML from parsing "+-1" as integer
+ default_value = f"'{default_value}'"
+
+ value = settings.setdefault(
+ name,
+ default_value,
+ apply_default_on_none=self.apply_default_on_none,
+ )
# is name required but not exists?
if self.must_exist is True and value is empty:
- raise ValidationError(
- self.messages["must_exist_true"].format(name=name, env=env)
+ _message = self.messages["must_exist_true"].format(
+ name=name, env=env
)
+ raise ValidationError(_message, details=[(self, _message)])
if self.must_exist is False and value is not empty:
- raise ValidationError(
- self.messages["must_exist_false"].format(
- name=name, env=env
- )
+ _message = self.messages["must_exist_false"].format(
+ name=name, env=env
)
+ raise ValidationError(_message, details=[(self, _message)])
if self.must_exist in (False, None) and value is empty:
continue
+ if self.cast:
+ # value or default value already set
+ # by settings.setdefault above
+ # however we need to cast it
+ # so we call .set again
+ value = self.cast(settings.get(name))
+ settings.set(name, value)
+
# is there a callable condition?
if self.condition is not None:
if not self.condition(value):
- raise ValidationError(
- self.messages["condition"].format(
- name=name,
- function=self.condition.__name__,
- value=value,
- env=env,
- )
+ _message = self.messages["condition"].format(
+ name=name,
+ function=self.condition.__name__,
+ value=value,
+ env=env,
)
+ raise ValidationError(_message, details=[(self, _message)])
# operations
for op_name, op_value in self.operations.items():
op_function = getattr(validator_conditions, op_name)
if not op_function(value, op_value):
- raise ValidationError(
- self.messages["operations"].format(
- name=name,
- operation=op_function.__name__,
- op_value=op_value,
- value=value,
- env=env,
- )
+ _message = self.messages["operations"].format(
+ name=name,
+ operation=op_function.__name__,
+ op_value=op_value,
+ value=value,
+ env=env,
)
+ raise ValidationError(_message, details=[(self, _message)])
class CombinedValidator(Validator):
@@ -296,8 +336,9 @@ class CombinedValidator(Validator):
def validate(
self,
settings: Any,
- only: Optional[Union[str, Sequence]] = None,
- exclude: Optional[Union[str, Sequence]] = None,
+ only: str | Sequence | None = None,
+ exclude: str | Sequence | None = None,
+ only_current_env: bool = False,
) -> None: # pragma: no cover
raise NotImplementedError(
"subclasses OrValidator or AndValidator implements this method"
@@ -310,28 +351,33 @@ class OrValidator(CombinedValidator):
def validate(
self,
settings: Any,
- only: Optional[Union[str, Sequence]] = None,
- exclude: Optional[Union[str, Sequence]] = None,
+ only: str | Sequence | None = None,
+ exclude: str | Sequence | None = None,
+ only_current_env: bool = False,
) -> None:
"""Ensure at least one of the validators are valid"""
errors = []
for validator in self.validators:
try:
- validator.validate(settings, only=only, exclude=exclude)
+ validator.validate(
+ settings,
+ only=only,
+ exclude=exclude,
+ only_current_env=only_current_env,
+ )
except ValidationError as e:
errors.append(e)
continue
else:
return
- raise ValidationError(
- self.messages["combined"].format(
- errors=" or ".join(
- str(e).replace("combined validators failed ", "")
- for e in errors
- )
+ _message = self.messages["combined"].format(
+ errors=" or ".join(
+ str(e).replace("combined validators failed ", "")
+ for e in errors
)
)
+ raise ValidationError(_message, details=[(self, _message)])
class AndValidator(CombinedValidator):
@@ -340,34 +386,39 @@ class AndValidator(CombinedValidator):
def validate(
self,
settings: Any,
- only: Optional[Union[str, Sequence]] = None,
- exclude: Optional[Union[str, Sequence]] = None,
+ only: str | Sequence | None = None,
+ exclude: str | Sequence | None = None,
+ only_current_env: bool = False,
) -> None:
"""Ensure both the validators are valid"""
errors = []
for validator in self.validators:
try:
- validator.validate(settings, only=only, exclude=exclude)
+ validator.validate(
+ settings,
+ only=only,
+ exclude=exclude,
+ only_current_env=only_current_env,
+ )
except ValidationError as e:
errors.append(e)
continue
if errors:
- raise ValidationError(
- self.messages["combined"].format(
- errors=" and ".join(
- str(e).replace("combined validators failed ", "")
- for e in errors
- )
+ _message = self.messages["combined"].format(
+ errors=" and ".join(
+ str(e).replace("combined validators failed ", "")
+ for e in errors
)
)
+ raise ValidationError(_message, details=[(self, _message)])
class ValidatorList(list):
def __init__(
self,
settings: Any,
- validators: Optional[Sequence[Validator]] = None,
+ validators: Sequence[Validator] | None = None,
*args: Validator,
**kwargs: Any,
) -> None:
@@ -375,11 +426,11 @@ class ValidatorList(list):
args = list(args) + list(validators) # type: ignore
self._only = kwargs.pop("validate_only", None)
self._exclude = kwargs.pop("validate_exclude", None)
- super(ValidatorList, self).__init__(args, **kwargs) # type: ignore
+ super().__init__(args, **kwargs) # type: ignore
self.settings = settings
def register(self, *args: Validator, **kwargs: Validator):
- validators: List[Validator] = list(
+ validators: list[Validator] = list(
chain.from_iterable(kwargs.values()) # type: ignore
)
validators.extend(args)
@@ -387,12 +438,10 @@ class ValidatorList(list):
if validator and validator not in self:
self.append(validator)
- def descriptions(
- self, flat: bool = False
- ) -> Dict[str, Union[str, List[str]]]:
+ def descriptions(self, flat: bool = False) -> dict[str, str | list[str]]:
if flat:
- descriptions: Dict[str, Union[str, List[str]]] = {}
+ descriptions: dict[str, str | list[str]] = {}
else:
descriptions = defaultdict(list)
@@ -410,8 +459,40 @@ class ValidatorList(list):
def validate(
self,
- only: Optional[Union[str, Sequence]] = None,
- exclude: Optional[Union[str, Sequence]] = None,
+ only: str | Sequence | None = None,
+ exclude: str | Sequence | None = None,
+ only_current_env: bool = False,
+ ) -> None:
+ for validator in self:
+ validator.validate(
+ self.settings,
+ only=only,
+ exclude=exclude,
+ only_current_env=only_current_env,
+ )
+
+ def validate_all(
+ self,
+ only: str | Sequence | None = None,
+ exclude: str | Sequence | None = None,
+ only_current_env: bool = False,
) -> None:
+ errors = []
+ details = []
for validator in self:
- validator.validate(self.settings, only=only, exclude=exclude)
+ try:
+ validator.validate(
+ self.settings,
+ only=only,
+ exclude=exclude,
+ only_current_env=only_current_env,
+ )
+ except ValidationError as e:
+ errors.append(e)
+ details.append((validator, str(e)))
+ continue
+
+ if errors:
+ raise ValidationError(
+ "; ".join(str(e) for e in errors), details=details
+ )
diff --git a/dynaconf/validator_conditions.py b/dynaconf/validator_conditions.py
index cb1c3f5..96d1510 100644
--- a/dynaconf/validator_conditions.py
+++ b/dynaconf/validator_conditions.py
@@ -2,6 +2,7 @@
"""
Implement basic assertions to be used in assertion action
"""
+from __future__ import annotations
def eq(value, other):
@@ -75,10 +76,15 @@ def len_min(value, other):
def len_max(value, other):
- """Maximum lenght"""
+ """Maximum length"""
return len(value) <= other
def startswith(value, term):
"""returns value.startswith(term) result"""
return value.startswith(term)
+
+
+def endswith(value, term):
+ """returns value.endswith(term) result"""
+ return value.endswith(term)
diff --git a/dynaconf/vendor/box/__init__.py b/dynaconf/vendor/box/__init__.py
index fb02ed8..6ff6531 100644
--- a/dynaconf/vendor/box/__init__.py
+++ b/dynaconf/vendor/box/__init__.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
__author__='Chris Griffith'
__version__='4.2.3'
from .box import Box
diff --git a/dynaconf/vendor/box/box.py b/dynaconf/vendor/box/box.py
index 5e14910..baf9ca3 100644
--- a/dynaconf/vendor/box/box.py
+++ b/dynaconf/vendor/box/box.py
@@ -1,20 +1,18 @@
-_Z='keys'
-_Y='box_settings'
-_X='default_box_attr'
-_W='Box is frozen'
-_V='modify_tuples_box'
-_U='box_safe_prefix'
-_T='default_box_none_transform'
-_S='__created'
-_R='box_dots'
-_Q='box_duplicates'
-_P='ignore'
-_O='.'
-_N='strict'
-_M='box_recast'
-_L='box_intact_types'
-_K='default_box'
-_J='_'
+#!/usr/bin/env python
+_W='box_settings'
+_V='default_box_attr'
+_U='Box is frozen'
+_T='modify_tuples_box'
+_S='box_safe_prefix'
+_R='default_box_none_transform'
+_Q='__created'
+_P='box_dots'
+_O='box_duplicates'
+_N='ignore'
+_M='strict'
+_L='box_recast'
+_K='box_intact_types'
+_J='default_box'
_I='utf-8'
_H='_box_config'
_G=True
@@ -37,7 +35,7 @@ _first_cap_re=re.compile('(.)([A-Z][a-z]+)')
_all_cap_re=re.compile('([a-z0-9])([A-Z])')
_list_pos_re=re.compile('\\[(\\d+)\\]')
NO_DEFAULT=object()
-def _camel_killer(attr):D='\\1_\\2';A=attr;A=str(A);B=_first_cap_re.sub(D,A);C=_all_cap_re.sub(D,B);return re.sub(' *_+',_J,C.lower())
+def _camel_killer(attr):B='\\1_\\2';A=attr;A=str(A);C=_first_cap_re.sub(B,A);D=_all_cap_re.sub(B,C);return re.sub(' *_+','_',D.lower())
def _recursive_tuples(iterable,box_class,recreate_tuples=_B,**E):
D=recreate_tuples;C=box_class;B=[]
for A in iterable:
@@ -49,21 +47,21 @@ def _parse_box_dots(item):
A=item
for (B,C) in enumerate(A):
if C=='[':return A[:B],A[B:]
- elif C==_O:return A[:B],A[B+1:]
+ elif C=='.':return A[:B],A[B+1:]
raise BoxError('Could not split box dots properly')
-def _get_box_config():return{_S:_B,_C:{}}
+def _get_box_config():return{_Q:_B,_C:{}}
class Box(dict):
- _protected_keys=['to_dict','to_json','to_yaml','from_yaml','from_json','from_toml','to_toml','merge_update']+[A for A in dir({})if not A.startswith(_J)]
- def __new__(A,*D,box_settings=_A,default_box=_B,default_box_attr=NO_DEFAULT,default_box_none_transform=_G,frozen_box=_B,camel_killer_box=_B,conversion_box=_G,modify_tuples_box=_B,box_safe_prefix='x',box_duplicates=_P,box_intact_types=(),box_recast=_A,box_dots=_B,**E):C=default_box_attr;B=super(Box,A).__new__(A,*D,**E);B._box_config=_get_box_config();B._box_config.update({_K:default_box,_X:A.__class__ if C is NO_DEFAULT else C,_T:default_box_none_transform,_E:conversion_box,_U:box_safe_prefix,_D:frozen_box,_F:camel_killer_box,_V:modify_tuples_box,_Q:box_duplicates,_L:tuple(box_intact_types),_M:box_recast,_R:box_dots,_Y:box_settings or{}});return B
- def __init__(A,*B,box_settings=_A,default_box=_B,default_box_attr=NO_DEFAULT,default_box_none_transform=_G,frozen_box=_B,camel_killer_box=_B,conversion_box=_G,modify_tuples_box=_B,box_safe_prefix='x',box_duplicates=_P,box_intact_types=(),box_recast=_A,box_dots=_B,**F):
- E=default_box_attr;super().__init__();A._box_config=_get_box_config();A._box_config.update({_K:default_box,_X:A.__class__ if E is NO_DEFAULT else E,_T:default_box_none_transform,_E:conversion_box,_U:box_safe_prefix,_D:frozen_box,_F:camel_killer_box,_V:modify_tuples_box,_Q:box_duplicates,_L:tuple(box_intact_types),_M:box_recast,_R:box_dots,_Y:box_settings or{}})
- if not A._box_config[_E]and A._box_config[_Q]!=_P:raise BoxError('box_duplicates are only for conversion_boxes')
+ _protected_keys=['to_dict','to_json','to_yaml','from_yaml','from_json','from_toml','to_toml','merge_update']+[A for A in dir({})if not A.startswith('_')]
+ def __new__(A,*D,box_settings=_A,default_box=_B,default_box_attr=NO_DEFAULT,default_box_none_transform=_G,frozen_box=_B,camel_killer_box=_B,conversion_box=_G,modify_tuples_box=_B,box_safe_prefix='x',box_duplicates=_N,box_intact_types=(),box_recast=_A,box_dots=_B,**E):C=default_box_attr;B=super(Box,A).__new__(A,*(D),**E);B._box_config=_get_box_config();B._box_config.update({_J:default_box,_V:A.__class__ if C is NO_DEFAULT else C,_R:default_box_none_transform,_E:conversion_box,_S:box_safe_prefix,_D:frozen_box,_F:camel_killer_box,_T:modify_tuples_box,_O:box_duplicates,_K:tuple(box_intact_types),_L:box_recast,_P:box_dots,_W:box_settings or{}});return B
+ def __init__(A,*B,box_settings=_A,default_box=_B,default_box_attr=NO_DEFAULT,default_box_none_transform=_G,frozen_box=_B,camel_killer_box=_B,conversion_box=_G,modify_tuples_box=_B,box_safe_prefix='x',box_duplicates=_N,box_intact_types=(),box_recast=_A,box_dots=_B,**F):
+ E=default_box_attr;super().__init__();A._box_config=_get_box_config();A._box_config.update({_J:default_box,_V:A.__class__ if E is NO_DEFAULT else E,_R:default_box_none_transform,_E:conversion_box,_S:box_safe_prefix,_D:frozen_box,_F:camel_killer_box,_T:modify_tuples_box,_O:box_duplicates,_K:tuple(box_intact_types),_L:box_recast,_P:box_dots,_W:box_settings or{}})
+ if not A._box_config[_E]and A._box_config[_O]!=_N:raise BoxError('box_duplicates are only for conversion_boxes')
if len(B)==1:
if isinstance(B[0],str):raise BoxValueError('Cannot extrapolate Box from string')
if isinstance(B[0],Mapping):
for (D,C) in B[0].items():
if C is B[0]:C=A
- if C is _A and A._box_config[_K]and A._box_config[_T]:continue
+ if C is _A and A._box_config[_J]and A._box_config[_R]:continue
A.__setitem__(D,C)
elif isinstance(B[0],Iterable):
for (D,C) in B[0]:A.__setitem__(D,C)
@@ -72,7 +70,7 @@ class Box(dict):
for (D,C) in F.items():
if B and isinstance(B[0],Mapping)and C is B[0]:C=A
A.__setitem__(D,C)
- A._box_config[_S]=_G
+ A._box_config[_Q]=_G
def __add__(C,other):
A=other;B=C.copy()
if not isinstance(A,dict):raise BoxTypeError(f"Box can only merge two boxes or a box and a dictionary.")
@@ -84,7 +82,7 @@ class Box(dict):
return B
raise BoxTypeError('unhashable type: "Box"')
def __dir__(B):
- D=string.ascii_letters+string.digits+_J;C=set(super().__dir__())
+ D=string.ascii_letters+string.digits+'_';C=set(super().__dir__())
for A in B.keys():
A=str(A)
if' 'not in A and A[0]not in string.digits and A not in kwlist:
@@ -101,9 +99,9 @@ class Box(dict):
C=key;A=default
if C not in B:
if A is NO_DEFAULT:
- if B._box_config[_K]and B._box_config[_T]:return B.__get_default(C)
+ if B._box_config[_J]and B._box_config[_R]:return B.__get_default(C)
else:return _A
- if isinstance(A,dict)and not isinstance(A,Box):return Box(A,box_settings=B._box_config.get(_Y))
+ if isinstance(A,dict)and not isinstance(A,Box):return Box(A,box_settings=B._box_config.get(_W))
if isinstance(A,list)and not isinstance(A,box.BoxList):return box.BoxList(A)
return A
return B[C]
@@ -118,7 +116,7 @@ class Box(dict):
def values(A):return[A[B]for B in A.keys()]
def items(A):return[(B,A[B])for B in A.keys()]
def __get_default(B,item):
- A=B._box_config[_X]
+ A=B._box_config[_V]
if A in(B.__class__,dict):C=B.__class__(**B.__box_config())
elif isinstance(A,dict):C=B.__class__(**B.__box_config(),**A)
elif isinstance(A,list):C=box.BoxList(**B.__box_config())
@@ -133,34 +131,34 @@ class Box(dict):
return A
def __recast(A,item,value):
C=value;B=item
- if A._box_config[_M]and B in A._box_config[_M]:
- try:return A._box_config[_M][B](C)
- except ValueError:raise BoxValueError(f"Cannot convert {C} to {A._box_config[_M][B]}") from _A
+ if A._box_config[_L]and B in A._box_config[_L]:
+ try:return A._box_config[_L][B](C)
+ except ValueError:raise BoxValueError(f"Cannot convert {C} to {A._box_config[_L][B]}") from _A
return C
def __convert_and_store(B,item,value):
C=item;A=value
if B._box_config[_E]:D=B._safe_attr(C);B._box_config[_C][D]=C
if isinstance(A,(int,float,str,bytes,bytearray,bool,complex,set,frozenset)):return super().__setitem__(C,A)
- if B._box_config[_L]and isinstance(A,B._box_config[_L]):return super().__setitem__(C,A)
+ if B._box_config[_K]and isinstance(A,B._box_config[_K]):return super().__setitem__(C,A)
if isinstance(A,dict)and not isinstance(A,Box):A=B.__class__(A,**B.__box_config())
elif isinstance(A,list)and not isinstance(A,box.BoxList):
- if B._box_config[_D]:A=_recursive_tuples(A,B.__class__,recreate_tuples=B._box_config[_V],**B.__box_config())
+ if B._box_config[_D]:A=_recursive_tuples(A,B.__class__,recreate_tuples=B._box_config[_T],**B.__box_config())
else:A=box.BoxList(A,box_class=B.__class__,**B.__box_config())
- elif B._box_config[_V]and isinstance(A,tuple):A=_recursive_tuples(A,B.__class__,recreate_tuples=_G,**B.__box_config())
+ elif B._box_config[_T]and isinstance(A,tuple):A=_recursive_tuples(A,B.__class__,recreate_tuples=_G,**B.__box_config())
super().__setitem__(C,A)
def __getitem__(B,item,_ignore_default=_B):
A=item
try:return super().__getitem__(A)
except KeyError as E:
if A==_H:raise BoxKeyError('_box_config should only exist as an attribute and is never defaulted') from _A
- if B._box_config[_R]and isinstance(A,str)and(_O in A or'['in A):
+ if B._box_config[_P]and isinstance(A,str)and('.'in A or'['in A):
C,F=_parse_box_dots(A)
if C in B.keys():
if hasattr(B[C],'__getitem__'):return B[C][F]
if B._box_config[_F]and isinstance(A,str):
D=_camel_killer(A)
if D in B.keys():return super().__getitem__(D)
- if B._box_config[_K]and not _ignore_default:return B.__get_default(A)
+ if B._box_config[_J]and not _ignore_default:return B.__get_default(A)
raise BoxKeyError(str(E)) from _A
def __getattr__(A,item):
B=item
@@ -173,24 +171,24 @@ class Box(dict):
if A._box_config[_E]:
D=A._safe_attr(B)
if D in A._box_config[_C]:return A.__getitem__(A._box_config[_C][D])
- if A._box_config[_K]:return A.__get_default(B)
+ if A._box_config[_J]:return A.__get_default(B)
raise BoxKeyError(str(E)) from _A
return C
def __setitem__(A,key,value):
C=value;B=key
- if B!=_H and A._box_config[_S]and A._box_config[_D]:raise BoxError(_W)
- if A._box_config[_R]and isinstance(B,str)and _O in B:
+ if B!=_H and A._box_config[_Q]and A._box_config[_D]:raise BoxError(_U)
+ if A._box_config[_P]and isinstance(B,str)and'.'in B:
D,E=_parse_box_dots(B)
if D in A.keys():
if hasattr(A[D],'__setitem__'):return A[D].__setitem__(E,C)
C=A.__recast(B,C)
if B not in A.keys()and A._box_config[_F]:
if A._box_config[_F]and isinstance(B,str):B=_camel_killer(B)
- if A._box_config[_E]and A._box_config[_Q]!=_P:A._conversion_checks(B)
+ if A._box_config[_E]and A._box_config[_O]!=_N:A._conversion_checks(B)
A.__convert_and_store(B,C)
def __setattr__(A,key,value):
C=value;B=key
- if B!=_H and A._box_config[_D]and A._box_config[_S]:raise BoxError(_W)
+ if B!=_H and A._box_config[_D]and A._box_config[_Q]:raise BoxError(_U)
if B in A._protected_keys:raise BoxKeyError(f'Key name "{B}" is protected')
if B==_H:return object.__setattr__(A,B,C)
C=A.__recast(B,C);D=A._safe_attr(B)
@@ -198,9 +196,9 @@ class Box(dict):
A.__setitem__(B,C)
def __delitem__(A,key):
B=key
- if A._box_config[_D]:raise BoxError(_W)
- if B not in A.keys()and A._box_config[_R]and isinstance(B,str)and _O in B:
- C,E=B.split(_O,1)
+ if A._box_config[_D]:raise BoxError(_U)
+ if B not in A.keys()and A._box_config[_P]and isinstance(B,str)and'.'in B:
+ C,E=B.split('.',1)
if C in A.keys()and isinstance(A[C],dict):return A[C].__delitem__(E)
if B not in A.keys()and A._box_config[_F]:
if A._box_config[_F]and isinstance(B,str):
@@ -209,7 +207,7 @@ class Box(dict):
super().__delitem__(B)
def __delattr__(A,item):
B=item
- if A._box_config[_D]:raise BoxError(_W)
+ if A._box_config[_D]:raise BoxError(_U)
if B==_H:raise BoxError('"_box_config" is protected')
if B in A._protected_keys:raise BoxKeyError(f'Key name "{B}" is protected')
try:A.__delitem__(B)
@@ -249,7 +247,7 @@ class Box(dict):
def update(C,__m=_A,**D):
B=__m
if B:
- if hasattr(B,_Z):
+ if hasattr(B,'keys'):
for A in B:C.__convert_and_store(A,B[A])
else:
for (A,E) in B:C.__convert_and_store(A,E)
@@ -257,7 +255,7 @@ class Box(dict):
def merge_update(A,__m=_A,**E):
C=__m
def D(k,v):
- B=A._box_config[_L]and isinstance(v,A._box_config[_L])
+ B=A._box_config[_K]and isinstance(v,A._box_config[_K])
if isinstance(v,dict)and not B:
v=A.__class__(v,**A.__box_config())
if k in A and isinstance(A[k],dict):
@@ -267,7 +265,7 @@ class Box(dict):
if isinstance(v,list)and not B:v=box.BoxList(v,**A.__box_config())
A.__setitem__(k,v)
if C:
- if hasattr(C,_Z):
+ if hasattr(C,'keys'):
for B in C:D(B,C[B])
else:
for (B,F) in C:D(B,F)
@@ -279,48 +277,48 @@ class Box(dict):
if isinstance(A,list):A=box.BoxList(A,box_class=B.__class__,**B.__box_config())
B[C]=A;return A
def _safe_attr(C,attr):
- B=attr;G=string.ascii_letters+string.digits+_J
- if isinstance(B,tuple):B=_J.join([str(A)for A in B])
- B=B.decode(_I,_P)if isinstance(B,bytes)else str(B)
+ B=attr;G=string.ascii_letters+string.digits+'_'
+ if isinstance(B,tuple):B='_'.join([str(A)for A in B])
+ B=B.decode(_I,_N)if isinstance(B,bytes)else str(B)
if C.__box_config()[_F]:B=_camel_killer(B)
A=[];D=0
for (E,F) in enumerate(B):
if F in G:D=E;A.append(F)
elif not A:continue
- elif D==E-1:A.append(_J)
+ elif D==E-1:A.append('_')
A=''.join(A)[:D+1]
try:int(A[0])
except (ValueError,IndexError):pass
- else:A=f"{C.__box_config()[_U]}{A}"
- if A in kwlist:A=f"{C.__box_config()[_U]}{A}"
+ else:A=f"{C.__box_config()[_S]}{A}"
+ if A in kwlist:A=f"{C.__box_config()[_S]}{A}"
return A
def _conversion_checks(A,item):
B=A._safe_attr(item)
if B in A._box_config[_C]:
C=[f"{item}({B})",f"{A._box_config[_C][B]}({B})"]
- if A._box_config[_Q].startswith('warn'):warnings.warn(f"Duplicate conversion attributes exist: {C}",BoxWarning)
+ if A._box_config[_O].startswith('warn'):warnings.warn(f"Duplicate conversion attributes exist: {C}",BoxWarning)
else:raise BoxError(f"Duplicate conversion attributes exist: {C}")
- def to_json(A,filename=_A,encoding=_I,errors=_N,**B):return _to_json(A.to_dict(),filename=filename,encoding=encoding,errors=errors,**B)
+ def to_json(A,filename=_A,encoding=_I,errors=_M,**B):return _to_json(A.to_dict(),filename=filename,encoding=encoding,errors=errors,**B)
@classmethod
- def from_json(E,json_string=_A,filename=_A,encoding=_I,errors=_N,**A):
+ def from_json(E,json_string=_A,filename=_A,encoding=_I,errors=_M,**A):
D={}
for B in A.copy():
if B in BOX_PARAMETERS:D[B]=A.pop(B)
C=_from_json(json_string,filename=filename,encoding=encoding,errors=errors,**A)
if not isinstance(C,dict):raise BoxError(f"json data not returned as a dictionary, but rather a {type(C).__name__}")
return E(C,**D)
- def to_yaml(A,filename=_A,default_flow_style=_B,encoding=_I,errors=_N,**B):return _to_yaml(A.to_dict(),filename=filename,default_flow_style=default_flow_style,encoding=encoding,errors=errors,**B)
+ def to_yaml(A,filename=_A,default_flow_style=_B,encoding=_I,errors=_M,**B):return _to_yaml(A.to_dict(),filename=filename,default_flow_style=default_flow_style,encoding=encoding,errors=errors,**B)
@classmethod
- def from_yaml(E,yaml_string=_A,filename=_A,encoding=_I,errors=_N,**A):
+ def from_yaml(E,yaml_string=_A,filename=_A,encoding=_I,errors=_M,**A):
D={}
for B in A.copy():
if B in BOX_PARAMETERS:D[B]=A.pop(B)
C=_from_yaml(yaml_string=yaml_string,filename=filename,encoding=encoding,errors=errors,**A)
if not isinstance(C,dict):raise BoxError(f"yaml data not returned as a dictionary but rather a {type(C).__name__}")
return E(C,**D)
- def to_toml(A,filename=_A,encoding=_I,errors=_N):return _to_toml(A.to_dict(),filename=filename,encoding=encoding,errors=errors)
+ def to_toml(A,filename=_A,encoding=_I,errors=_M):return _to_toml(A.to_dict(),filename=filename,encoding=encoding,errors=errors)
@classmethod
- def from_toml(D,toml_string=_A,filename=_A,encoding=_I,errors=_N,**B):
+ def from_toml(D,toml_string=_A,filename=_A,encoding=_I,errors=_M,**B):
C={}
for A in B.copy():
if A in BOX_PARAMETERS:C[A]=B.pop(A)
diff --git a/dynaconf/vendor/box/box_list.py b/dynaconf/vendor/box/box_list.py
index 9c03a67..50584c9 100644
--- a/dynaconf/vendor/box/box_list.py
+++ b/dynaconf/vendor/box/box_list.py
@@ -1,4 +1,4 @@
-_H='toml'
+#!/usr/bin/env python
_G='box_dots'
_F='BoxList is frozen'
_E='frozen_box'
@@ -44,11 +44,11 @@ class BoxList(list):
return super(BoxList,B).__getitem__(E).__setitem__(A[len(D.group()):].lstrip('.'),C)
super(BoxList,B).__setitem__(A,C)
def _is_intact_type(A,obj):
- C='box_intact_types'
+ B='box_intact_types'
try:
- if A.box_options.get(C)and isinstance(obj,A.box_options[C]):return True
- except AttributeError as B:
- if'box_options'in A.__dict__:raise BoxKeyError(B)
+ if A.box_options.get(B)and isinstance(obj,A.box_options[B]):return True
+ except AttributeError as C:
+ if'box_options'in A.__dict__:raise BoxKeyError(C)
return _D
def append(A,p_object):
B=p_object
@@ -109,9 +109,9 @@ class BoxList(list):
C=_from_yaml(yaml_string=yaml_string,filename=filename,encoding=encoding,errors=errors,**A)
if not isinstance(C,list):raise BoxError(f"yaml data not returned as a list but rather a {type(C).__name__}")
return E(C,**D)
- def to_toml(A,filename=_A,key_name=_H,encoding=_B,errors=_C):return _to_toml({key_name:A.to_list()},filename=filename,encoding=encoding,errors=errors)
+ def to_toml(A,filename=_A,key_name='toml',encoding=_B,errors=_C):return _to_toml({key_name:A.to_list()},filename=filename,encoding=encoding,errors=errors)
@classmethod
- def from_toml(F,toml_string=_A,filename=_A,key_name=_H,encoding=_B,errors=_C,**C):
+ def from_toml(F,toml_string=_A,filename=_A,key_name='toml',encoding=_B,errors=_C,**C):
A=key_name;D={}
for B in list(C.keys()):
if B in BOX_PARAMETERS:D[B]=C.pop(B)
diff --git a/dynaconf/vendor/box/config_box.py b/dynaconf/vendor/box/config_box.py
index 1194963..d9934e5 100644
--- a/dynaconf/vendor/box/config_box.py
+++ b/dynaconf/vendor/box/config_box.py
@@ -1,27 +1,24 @@
-_H='getint'
-_G='getfloat'
-_F='getboolean'
-_E='list'
-_D='float'
-_C='int'
-_B='bool'
+#!/usr/bin/env python
+_D='getint'
+_C='getfloat'
+_B='getboolean'
_A=None
from dynaconf.vendor.box.box import Box
class ConfigBox(Box):
- _protected_keys=dir(Box)+[_B,_C,_D,_E,_F,_G,_H]
+ _protected_keys=dir(Box)+['bool','int','float','list',_B,_C,_D]
def __getattr__(A,item):
try:return super().__getattr__(item)
except AttributeError:return super().__getattr__(item.lower())
- def __dir__(A):return super().__dir__()+[_B,_C,_D,_E,_F,_G,_H]
- def bool(C,item,default=_A):
- E=False;B=default;A=item
- try:A=C.__getattr__(A)
- except AttributeError as D:
+ def __dir__(A):return super().__dir__()+['bool','int','float','list',_B,_C,_D]
+ def bool(D,item,default=_A):
+ C=False;B=default;A=item
+ try:A=D.__getattr__(A)
+ except AttributeError as E:
if B is not _A:return B
- raise D
+ raise E
if isinstance(A,(bool,int)):return bool(A)
- if isinstance(A,str)and A.lower()in('n','no','false','f','0'):return E
- return True if A else E
+ if isinstance(A,str)and A.lower()in('n','no','false','f','0'):return C
+ return True if A else C
def int(C,item,default=_A):
B=default;A=item
try:A=C.__getattr__(A)
diff --git a/dynaconf/vendor/box/converters.py b/dynaconf/vendor/box/converters.py
index 93cdcfb..5b420a5 100644
--- a/dynaconf/vendor/box/converters.py
+++ b/dynaconf/vendor/box/converters.py
@@ -1,5 +1,4 @@
-_G='r'
-_F='w'
+#!/usr/bin/env python
_E=False
_D=True
_C='strict'
@@ -9,7 +8,7 @@ import csv,json,sys,warnings
from pathlib import Path
import dynaconf.vendor.ruamel.yaml as yaml
from dynaconf.vendor.box.exceptions import BoxError,BoxWarning
-from dynaconf.vendor import toml
+from dynaconf.vendor import tomllib as toml
BOX_PARAMETERS='default_box','default_box_attr','conversion_box','frozen_box','camel_killer_box','box_safe_prefix','box_duplicates','ordered_box','default_box_none_transform','box_dots','modify_tuples_box','box_intact_types','box_recast'
def _exists(filename,create=_E):
A=filename;B=Path(A)
@@ -23,13 +22,13 @@ def _to_json(obj,filename=_A,encoding=_B,errors=_C,**C):
A=filename;B=json.dumps(obj,ensure_ascii=_E,**C)
if A:
_exists(A,create=_D)
- with open(A,_F,encoding=encoding,errors=errors)as D:D.write(B if sys.version_info>=(3,0)else B.decode(_B))
+ with open(A,'w',encoding=encoding,errors=errors)as D:D.write(B if sys.version_info>=(3,0)else B.decode(_B))
else:return B
def _from_json(json_string=_A,filename=_A,encoding=_B,errors=_C,multiline=_E,**B):
D=json_string;A=filename
if A:
_exists(A)
- with open(A,_G,encoding=encoding,errors=errors)as E:
+ with open(A,'r',encoding=encoding,errors=errors)as E:
if multiline:C=[json.loads(A.strip(),**B)for A in E if A.strip()and not A.strip().startswith('#')]
else:C=json.load(E,**B)
elif D:C=json.loads(D,**B)
@@ -39,14 +38,14 @@ def _to_yaml(obj,filename=_A,default_flow_style=_E,encoding=_B,errors=_C,**C):
B=default_flow_style;A=filename
if A:
_exists(A,create=_D)
- with open(A,_F,encoding=encoding,errors=errors)as D:yaml.dump(obj,stream=D,default_flow_style=B,**C)
+ with open(A,'w',encoding=encoding,errors=errors)as D:yaml.dump(obj,stream=D,default_flow_style=B,**C)
else:return yaml.dump(obj,default_flow_style=B,**C)
def _from_yaml(yaml_string=_A,filename=_A,encoding=_B,errors=_C,**A):
- F='Loader';C=yaml_string;B=filename
- if F not in A:A[F]=yaml.SafeLoader
+ E='Loader';C=yaml_string;B=filename
+ if E not in A:A[E]=yaml.SafeLoader
if B:
_exists(B)
- with open(B,_G,encoding=encoding,errors=errors)as E:D=yaml.load(E,**A)
+ with open(B,'r',encoding=encoding,errors=errors)as F:D=yaml.load(F,**A)
elif C:D=yaml.load(C,**A)
else:raise BoxError('from_yaml requires a string or filename')
return D
@@ -54,13 +53,13 @@ def _to_toml(obj,filename=_A,encoding=_B,errors=_C):
A=filename
if A:
_exists(A,create=_D)
- with open(A,_F,encoding=encoding,errors=errors)as B:toml.dump(obj,B)
+ with open(A,'w',encoding=encoding,errors=errors)as B:toml.dump(obj,B)
else:return toml.dumps(obj)
def _from_toml(toml_string=_A,filename=_A,encoding=_B,errors=_C):
B=toml_string;A=filename
if A:
_exists(A)
- with open(A,_G,encoding=encoding,errors=errors)as D:C=toml.load(D)
+ with open(A,'r',encoding=encoding,errors=errors)as D:C=toml.load(D)
elif B:C=toml.loads(B)
else:raise BoxError('from_toml requires a string or filename')
return C
@@ -70,9 +69,9 @@ def _to_csv(box_list,filename,encoding=_B,errors=_C):
if list(E.keys())!=C:raise BoxError('BoxList must contain the same dictionary structure for every item to convert to csv')
if B:
_exists(B,create=_D)
- with open(B,_F,encoding=encoding,errors=errors,newline='')as F:
+ with open(B,'w',encoding=encoding,errors=errors,newline='')as F:
D=csv.DictWriter(F,fieldnames=C);D.writeheader()
for G in A:D.writerow(G)
def _from_csv(filename,encoding=_B,errors=_C):
A=filename;_exists(A)
- with open(A,_G,encoding=encoding,errors=errors,newline='')as B:C=csv.DictReader(B);return[A for A in C]
\ No newline at end of file
+ with open(A,'r',encoding=encoding,errors=errors,newline='')as B:C=csv.DictReader(B);return[A for A in C]
\ No newline at end of file
diff --git a/dynaconf/vendor/box/exceptions.py b/dynaconf/vendor/box/exceptions.py
index 66199e7..8133455 100644
--- a/dynaconf/vendor/box/exceptions.py
+++ b/dynaconf/vendor/box/exceptions.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
class BoxError(Exception):0
class BoxKeyError(BoxError,KeyError,AttributeError):0
class BoxTypeError(BoxError,TypeError):0
diff --git a/dynaconf/vendor/box/from_file.py b/dynaconf/vendor/box/from_file.py
index daa1137..5e0f484 100644
--- a/dynaconf/vendor/box/from_file.py
+++ b/dynaconf/vendor/box/from_file.py
@@ -1,7 +1,8 @@
+#!/usr/bin/env python
from json import JSONDecodeError
from pathlib import Path
from typing import Union
-from dynaconf.vendor.toml import TomlDecodeError
+from dynaconf.vendor.tomllib import TOMLDecodeError
from dynaconf.vendor.ruamel.yaml import YAMLError
from .exceptions import BoxError
from .box import Box
@@ -17,7 +18,7 @@ def _to_yaml(data):
except BoxError:return BoxList.from_yaml(data)
def _to_toml(data):
try:return Box.from_toml(data)
- except TomlDecodeError:raise BoxError('File is not TOML as expected')
+ except TOMLDecodeError:raise BoxError('File is not TOML as expected')
def box_from_file(file,file_type=None,encoding='utf-8',errors='strict'):
C=file_type;A=file
if not isinstance(A,Path):A=Path(A)
diff --git a/dynaconf/vendor/box/shorthand_box.py b/dynaconf/vendor/box/shorthand_box.py
index b6da19c..2347405 100644
--- a/dynaconf/vendor/box/shorthand_box.py
+++ b/dynaconf/vendor/box/shorthand_box.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
from dynaconf.vendor.box.box import Box
class SBox(Box):
_protected_keys=dir({})+['to_dict','to_json','to_yaml','json','yaml','from_yaml','from_json','dict','toml','from_toml','to_toml']
diff --git a/dynaconf/vendor/click/_bashcomplete.py b/dynaconf/vendor/click/_bashcomplete.py
index e27049d..7f7c2e8 100644
--- a/dynaconf/vendor/click/_bashcomplete.py
+++ b/dynaconf/vendor/click/_bashcomplete.py
@@ -1,9 +1,7 @@
-_I='COMP_CWORD'
-_H='COMP_WORDS'
-_G='fish'
-_F='zsh'
-_E='bash'
-_D='_'
+_G='COMP_CWORD'
+_F='COMP_WORDS'
+_E='zsh'
+_D='bash'
_C=False
_B=None
_A=True
@@ -19,9 +17,9 @@ WORDBREAK='='
COMPLETION_SCRIPT_BASH='\n%(complete_func)s() {\n local IFS=$\'\n\'\n COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\\n COMP_CWORD=$COMP_CWORD \\\n %(autocomplete_var)s=complete $1 ) )\n return 0\n}\n\n%(complete_func)setup() {\n local COMPLETION_OPTIONS=""\n local BASH_VERSION_ARR=(${BASH_VERSION//./ })\n # Only BASH version 4.4 and later have the nosort option.\n if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] && [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then\n COMPLETION_OPTIONS="-o nosort"\n fi\n\n complete $COMPLETION_OPTIONS -F %(complete_func)s %(script_names)s\n}\n\n%(complete_func)setup\n'
COMPLETION_SCRIPT_ZSH='\n#compdef %(script_names)s\n\n%(complete_func)s() {\n local -a completions\n local -a completions_with_descriptions\n local -a response\n (( ! $+commands[%(script_names)s] )) && return 1\n\n response=("${(@f)$( env COMP_WORDS="${words[*]}" \\\n COMP_CWORD=$((CURRENT-1)) \\\n %(autocomplete_var)s="complete_zsh" \\\n %(script_names)s )}")\n\n for key descr in ${(kv)response}; do\n if [[ "$descr" == "_" ]]; then\n completions+=("$key")\n else\n completions_with_descriptions+=("$key":"$descr")\n fi\n done\n\n if [ -n "$completions_with_descriptions" ]; then\n _describe -V unsorted completions_with_descriptions -U\n fi\n\n if [ -n "$completions" ]; then\n compadd -U -V unsorted -a completions\n fi\n compstate[insert]="automenu"\n}\n\ncompdef %(complete_func)s %(script_names)s\n'
COMPLETION_SCRIPT_FISH='complete --no-files --command %(script_names)s --arguments "(env %(autocomplete_var)s=complete_fish COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) %(script_names)s)"'
-_completion_scripts={_E:COMPLETION_SCRIPT_BASH,_F:COMPLETION_SCRIPT_ZSH,_G:COMPLETION_SCRIPT_FISH}
+_completion_scripts={_D:COMPLETION_SCRIPT_BASH,_E:COMPLETION_SCRIPT_ZSH,'fish':COMPLETION_SCRIPT_FISH}
_invalid_ident_char_re=re.compile('[^a-zA-Z0-9_]')
-def get_completion_script(prog_name,complete_var,shell):A=prog_name;B=_invalid_ident_char_re.sub('',A.replace('-',_D));C=_completion_scripts.get(shell,COMPLETION_SCRIPT_BASH);return (C%{'complete_func':f"_{B}_completion",'script_names':A,'autocomplete_var':complete_var}).strip()+';'
+def get_completion_script(prog_name,complete_var,shell):A=prog_name;B=_invalid_ident_char_re.sub('',A.replace('-','_'));C=_completion_scripts.get(shell,COMPLETION_SCRIPT_BASH);return (C%{'complete_func':f"_{B}_completion",'script_names':A,'autocomplete_var':complete_var}).strip()+';'
def resolve_ctx(cli,prog_name,args):
B=args;A=cli.make_context(prog_name,B,resilient_parsing=_A);B=A.protected_args+A.args
while B:
@@ -90,25 +88,25 @@ def get_choices(cli,prog_name,args,incomplete):
if is_incomplete_argument(C.params,A):return get_user_autocompletions(C,D,B,A)
add_subcommand_completions(C,B,E);return sorted(E)
def do_complete(cli,prog_name,include_descriptions):
- B=split_arg_string(os.environ[_H]);C=int(os.environ[_I]);E=B[1:C]
+ B=split_arg_string(os.environ[_F]);C=int(os.environ[_G]);E=B[1:C]
try:D=B[C]
except IndexError:D=''
for A in get_choices(cli,prog_name,E,D):
echo(A[0])
- if include_descriptions:echo(A[1]if A[1]else _D)
+ if include_descriptions:echo(A[1]if A[1]else'_')
return _A
def do_complete_fish(cli,prog_name):
- B=split_arg_string(os.environ[_H]);C=os.environ[_I];D=B[1:]
+ B=split_arg_string(os.environ[_F]);C=os.environ[_G];D=B[1:]
for A in get_choices(cli,prog_name,D,C):
if A[1]:echo(f"{A[0]}\t{A[1]}")
else:echo(A[0])
return _A
def bashcomplete(cli,prog_name,complete_var,complete_instr):
C=complete_instr;B=prog_name
- if _D in C:D,A=C.split(_D,1)
- else:D=C;A=_E
+ if'_'in C:D,A=C.split('_',1)
+ else:D=C;A=_D
if D=='source':echo(get_completion_script(B,complete_var,A));return _A
elif D=='complete':
- if A==_G:return do_complete_fish(cli,B)
- elif A in{_E,_F}:return do_complete(cli,B,A==_F)
+ if A=='fish':return do_complete_fish(cli,B)
+ elif A in{_D,_E}:return do_complete(cli,B,A==_E)
return _C
\ No newline at end of file
diff --git a/dynaconf/vendor/click/_compat.py b/dynaconf/vendor/click/_compat.py
index f1eb2b4..e293268 100644
--- a/dynaconf/vendor/click/_compat.py
+++ b/dynaconf/vendor/click/_compat.py
@@ -1,9 +1,6 @@
-_L='stderr'
-_K='stdout'
-_J='stdin'
-_I='buffer'
-_H='ascii'
-_G='win'
+_I='stderr'
+_H='stdout'
+_G='buffer'
_F='utf-8'
_E='encoding'
_D='replace'
@@ -13,9 +10,9 @@ _A=None
import codecs,io,os,re,sys
from weakref import WeakKeyDictionary
CYGWIN=sys.platform.startswith('cygwin')
-MSYS2=sys.platform.startswith(_G)and'GCC'in sys.version
+MSYS2=sys.platform.startswith('win')and'GCC'in sys.version
APP_ENGINE='APPENGINE_RUNTIME'in os.environ and'Development/'in os.environ.get('SERVER_SOFTWARE','')
-WIN=sys.platform.startswith(_G)and not APP_ENGINE and not MSYS2
+WIN=sys.platform.startswith('win')and not APP_ENGINE and not MSYS2
DEFAULT_COLUMNS=80
auto_wrap_for_ansi=_A
colorama=_A
@@ -28,7 +25,7 @@ def _make_text_stream(stream,encoding,errors,force_readable=_B,force_writable=_B
if B is _A:B=_D
return _NonClosingTextIOWrapper(C,A,B,line_buffering=_C,force_readable=force_readable,force_writable=force_writable)
def is_ascii_encoding(encoding):
- try:return codecs.lookup(encoding).name==_H
+ try:return codecs.lookup(encoding).name=='ascii'
except LookupError:return _B
def get_best_encoding(stream):
A=getattr(stream,_E,_A)or sys.getdefaultencoding()
@@ -84,14 +81,14 @@ def _is_binary_writer(stream,default=_B):
def _find_binary_reader(stream):
A=stream
if _is_binary_reader(A,_B):return A
- B=getattr(A,_I,_A)
+ B=getattr(A,_G,_A)
if B is not _A and _is_binary_reader(B,_C):return B
def _find_binary_writer(stream):
A=stream
if _is_binary_writer(A,_B):return A
- B=getattr(A,_I,_A)
+ B=getattr(A,_G,_A)
if B is not _A and _is_binary_writer(B,_C):return B
-def _stream_is_misconfigured(stream):return is_ascii_encoding(getattr(stream,_E,_A)or _H)
+def _stream_is_misconfigured(stream):return is_ascii_encoding(getattr(stream,_E,_A)or'ascii')
def _is_compat_stream_attr(stream,attr,value):A=value;B=getattr(stream,attr,_A);return B==A or A is _A and B is not _A
def _is_compatible_text_stream(stream,encoding,errors):A=stream;return _is_compat_stream_attr(A,_E,encoding)and _is_compat_stream_attr(A,'errors',errors)
def _force_correct_text_stream(text_stream,encoding,errors,is_binary,find_binary,force_readable=_B,force_writable=_B):
@@ -146,17 +143,17 @@ def _wrap_io_open(file,mode,encoding,errors):
if'b'in A:return open(file,A)
return open(file,A,encoding=encoding,errors=errors)
def open_stream(filename,mode='r',encoding=_A,errors='strict',atomic=_B):
- P='x';O='a';N='w';E=errors;D=encoding;B=filename;A=mode;G='b'in A
+ E=errors;D=encoding;B=filename;A=mode;G='b'in A
if B=='-':
- if any((B in A for B in[N,O,P])):
+ if any((B in A for B in['w','a','x'])):
if G:return get_binary_stdout(),_B
return get_text_stdout(encoding=D,errors=E),_B
if G:return get_binary_stdin(),_B
return get_text_stdin(encoding=D,errors=E),_B
if not atomic:return _wrap_io_open(B,A,D,E),_C
- if O in A:raise ValueError("Appending to an existing file is not supported, because that would involve an expensive `copy`-operation to a temporary file. Open the file in normal `w`-mode and copy explicitly if that's what you're after.")
- if P in A:raise ValueError('Use the `overwrite`-parameter instead.')
- if N not in A:raise ValueError('Atomic writes only make sense with `w`-mode.')
+ if'a'in A:raise ValueError("Appending to an existing file is not supported, because that would involve an expensive `copy`-operation to a temporary file. Open the file in normal `w`-mode and copy explicitly if that's what you're after.")
+ if'x'in A:raise ValueError('Use the `overwrite`-parameter instead.')
+ if'w'not in A:raise ValueError('Atomic writes only make sense with `w`-mode.')
import errno as I,random as K
try:C=os.stat(B).st_mode
except OSError:C=_A
@@ -236,5 +233,5 @@ def _make_cached_stream_func(src_func,wrapper_func):
_default_text_stdin=_make_cached_stream_func(lambda:sys.stdin,get_text_stdin)
_default_text_stdout=_make_cached_stream_func(lambda:sys.stdout,get_text_stdout)
_default_text_stderr=_make_cached_stream_func(lambda:sys.stderr,get_text_stderr)
-binary_streams={_J:get_binary_stdin,_K:get_binary_stdout,_L:get_binary_stderr}
-text_streams={_J:get_text_stdin,_K:get_text_stdout,_L:get_text_stderr}
\ No newline at end of file
+binary_streams={'stdin':get_binary_stdin,_H:get_binary_stdout,_I:get_binary_stderr}
+text_streams={'stdin':get_text_stdin,_H:get_text_stdout,_I:get_text_stderr}
\ No newline at end of file
diff --git a/dynaconf/vendor/click/_termui_impl.py b/dynaconf/vendor/click/_termui_impl.py
index b18a9f2..b082d2a 100644
--- a/dynaconf/vendor/click/_termui_impl.py
+++ b/dynaconf/vendor/click/_termui_impl.py
@@ -1,7 +1,5 @@
-_H='replace'
-_G='less'
-_F='You need to use progress bars in a with block.'
-_E=' '
+_F='replace'
+_E='You need to use progress bars in a with block.'
_D='\n'
_C=False
_B=True
@@ -23,7 +21,7 @@ def _length_hint(obj):
if A is NotImplemented or not isinstance(A,int)or A<0:return _A
return A
class ProgressBar:
- def __init__(A,iterable,length=_A,fill_char='#',empty_char=_E,bar_template='%(bar)s',info_sep=' ',show_eta=_B,show_percent=_A,show_pos=_C,item_show_func=_A,label=_A,file=_A,color=_A,width=30):
+ def __init__(A,iterable,length=_A,fill_char='#',empty_char=' ',bar_template='%(bar)s',info_sep=' ',show_eta=_B,show_percent=_A,show_pos=_C,item_show_func=_A,label=_A,file=_A,color=_A,width=30):
E=width;D=file;C=iterable;B=length;A.fill_char=fill_char;A.empty_char=empty_char;A.bar_template=bar_template;A.info_sep=info_sep;A.show_eta=show_eta;A.show_percent=show_percent;A.show_pos=show_pos;A.item_show_func=item_show_func;A.label=label or''
if D is _A:D=_default_text_stdout()
A.file=D;A.color=color;A.width=E;A.autowidth=E==0
@@ -35,7 +33,7 @@ class ProgressBar:
def __enter__(A):A.entered=_B;A.render_progress();return A
def __exit__(A,exc_type,exc_value,tb):A.render_finish()
def __iter__(A):
- if not A.entered:raise RuntimeError(_F)
+ if not A.entered:raise RuntimeError(_E)
A.render_progress();return A.generator()
def __next__(A):return next(iter(A))
def is_fast(A):return time.time()-A.start<=A.short_limit
@@ -92,13 +90,13 @@ class ProgressBar:
B=[]
if A.autowidth:
H=A.width;A.width=0;I=term_len(A.format_progress_line());D=max(0,G()[0]-I)
- if D<H:B.append(BEFORE_BAR);B.append(_E*A.max_width);A.max_width=D
+ if D<H:B.append(BEFORE_BAR);B.append(' '*A.max_width);A.max_width=D
A.width=D
F=A.width
if A.max_width is not _A:F=A.max_width
B.append(BEFORE_BAR);C=A.format_progress_line();E=term_len(C)
if A.max_width is _A or A.max_width<E:A.max_width=E
- B.append(C);B.append(_E*(F-E));C=''.join(B)
+ B.append(C);B.append(' '*(F-E));C=''.join(B)
if C!=A._last_line and not A.is_fast():A._last_line=C;echo(C,file=A.file,color=A.color,nl=_C);A.file.flush()
def make_step(A,n_steps):
A.pos+=n_steps
@@ -114,13 +112,13 @@ class ProgressBar:
A.render_progress()
def finish(A):A.eta_known=0;A.current_item=_A;A.finished=_B
def generator(A):
- if not A.entered:raise RuntimeError(_F)
+ if not A.entered:raise RuntimeError(_E)
if A.is_hidden:yield from A.iter
else:
for B in A.iter:A.current_item=B;yield B;A.update(1)
A.finish();A.render_progress()
def pager(generator,color=_A):
- H='system';B=color;A=generator;C=_default_text_stdout()
+ F='system';B=color;A=generator;C=_default_text_stdout()
if not isatty(sys.stdin)or not isatty(C):return _nullpager(C,A,B)
D=(os.environ.get('PAGER',_A)or'').strip()
if D:
@@ -128,23 +126,23 @@ def pager(generator,color=_A):
return _pipepager(A,D,B)
if os.environ.get('TERM')in('dumb','emacs'):return _nullpager(C,A,B)
if WIN or sys.platform.startswith('os2'):return _tempfilepager(A,'more <',B)
- if hasattr(os,H)and os.system('(less) 2>/dev/null')==0:return _pipepager(A,_G,B)
- import tempfile as F;G,E=F.mkstemp();os.close(G)
+ if hasattr(os,F)and os.system('(less) 2>/dev/null')==0:return _pipepager(A,'less',B)
+ import tempfile as G;H,E=G.mkstemp();os.close(H)
try:
- if hasattr(os,H)and os.system(f'more "{E}"')==0:return _pipepager(A,'more',B)
+ if hasattr(os,F)and os.system(f'more "{E}"')==0:return _pipepager(A,'more',B)
return _nullpager(C,A,B)
finally:os.unlink(E)
def _pipepager(generator,cmd,color):
- I='LESS';A=color;import subprocess as E;F=dict(os.environ);G=cmd.rsplit('/',1)[-1].split()
- if A is _A and G[0]==_G:
- C=f"{os.environ.get(I,'')}{_E.join(G[1:])}"
- if not C:F[I]='-R';A=_B
+ H='LESS';A=color;import subprocess as E;F=dict(os.environ);G=cmd.rsplit('/',1)[-1].split()
+ if A is _A and G[0]=='less':
+ C=f"{os.environ.get(H,'')}{' '.join(G[1:])}"
+ if not C:F[H]='-R';A=_B
elif'r'in C or'R'in C:A=_B
- B=E.Popen(cmd,shell=_B,stdin=E.PIPE,env=F);H=get_best_encoding(B.stdin)
+ B=E.Popen(cmd,shell=_B,stdin=E.PIPE,env=F);I=get_best_encoding(B.stdin)
try:
for D in generator:
if not A:D=strip_ansi(D)
- B.stdin.write(D.encode(H,_H))
+ B.stdin.write(D.encode(I,_F))
except (OSError,KeyboardInterrupt):pass
else:B.stdin.close()
while _B:
@@ -182,21 +180,21 @@ class Editor:
if F!=0:raise ClickException(f"{B}: Editing failed!")
except OSError as G:raise ClickException(f"{B}: Editing failed: {G}")
def edit(D,text):
- L='\r\n';K='utf-8-sig';A=text;import tempfile as H;A=A or'';E=type(A)in[bytes,bytearray]
+ I='\r\n';H='utf-8-sig';A=text;import tempfile as J;A=A or'';E=type(A)in[bytes,bytearray]
if not E and A and not A.endswith(_D):A+=_D
- I,B=H.mkstemp(prefix='editor-',suffix=D.extension)
+ K,B=J.mkstemp(prefix='editor-',suffix=D.extension)
try:
if not E:
- if WIN:F=K;A=A.replace(_D,L)
+ if WIN:F=H;A=A.replace(_D,I)
else:F='utf-8'
A=A.encode(F)
- C=os.fdopen(I,'wb');C.write(A);C.close();J=os.path.getmtime(B);D.edit_file(B)
- if D.require_save and os.path.getmtime(B)==J:return _A
+ C=os.fdopen(K,'wb');C.write(A);C.close();L=os.path.getmtime(B);D.edit_file(B)
+ if D.require_save and os.path.getmtime(B)==L:return _A
C=open(B,'rb')
try:G=C.read()
finally:C.close()
if E:return G
- else:return G.decode(K).replace(L,_D)
+ else:return G.decode(H).replace(I,_D)
finally:os.unlink(B)
def open_url(url,wait=_C,locate=_C):
F='"';D=locate;C=wait;A=url;import subprocess as G
@@ -257,6 +255,6 @@ else:
except termios.error:pass
def getchar(echo):
with raw_terminal()as B:
- A=os.read(B,32);A=A.decode(get_best_encoding(sys.stdin),_H)
+ A=os.read(B,32);A=A.decode(get_best_encoding(sys.stdin),_F)
if echo and isatty(sys.stdout):sys.stdout.write(A)
_translate_ch_to_exc(A);return A
\ No newline at end of file
diff --git a/dynaconf/vendor/click/_unicodefun.py b/dynaconf/vendor/click/_unicodefun.py
index 792053f..639e5af 100644
--- a/dynaconf/vendor/click/_unicodefun.py
+++ b/dynaconf/vendor/click/_unicodefun.py
@@ -1,28 +1,28 @@
import codecs,os
def _verify_python_env():
- M='.utf8';L='.utf-8';J=None;I='ascii'
- try:import locale as A;G=codecs.lookup(A.getpreferredencoding()).name
- except Exception:G=I
- if G!=I:return
+ L='.utf8';K='.utf-8';H=None;G='ascii'
+ try:import locale as A;I=codecs.lookup(A.getpreferredencoding()).name
+ except Exception:I=G
+ if I!=G:return
B=''
if os.name=='posix':
import subprocess as D
try:C=D.Popen(['locale','-a'],stdout=D.PIPE,stderr=D.PIPE).communicate()[0]
except OSError:C=b''
- E=set();H=False
- if isinstance(C,bytes):C=C.decode(I,'replace')
- for K in C.splitlines():
- A=K.strip()
- if A.lower().endswith((L,M)):
+ E=set();J=False
+ if isinstance(C,bytes):C=C.decode(G,'replace')
+ for M in C.splitlines():
+ A=M.strip()
+ if A.lower().endswith((K,L)):
E.add(A)
- if A.lower()in('c.utf8','c.utf-8'):H=True
+ if A.lower()in('c.utf8','c.utf-8'):J=True
B+='\n\n'
if not E:B+='Additional information: on this system no suitable UTF-8 locales were discovered. This most likely requires resolving by reconfiguring the locale system.'
- elif H:B+='This system supports the C.UTF-8 locale which is recommended. You might be able to resolve your issue by exporting the following environment variables:\n\n export LC_ALL=C.UTF-8\n export LANG=C.UTF-8'
+ elif J:B+='This system supports the C.UTF-8 locale which is recommended. You might be able to resolve your issue by exporting the following environment variables:\n\n export LC_ALL=C.UTF-8\n export LANG=C.UTF-8'
else:B+=f"This system lists some UTF-8 supporting locales that you can pick from. The following suitable locales were discovered: {', '.join(sorted(E))}"
- F=J
+ F=H
for A in (os.environ.get('LC_ALL'),os.environ.get('LANG')):
- if A and A.lower().endswith((L,M)):F=A
- if A is not J:break
- if F is not J:B+=f"\n\nClick discovered that you exported a UTF-8 locale but the locale system could not pick up from it because it does not exist. The exported locale is {F!r} but it is not supported"
+ if A and A.lower().endswith((K,L)):F=A
+ if A is not H:break
+ if F is not H:B+=f"\n\nClick discovered that you exported a UTF-8 locale but the locale system could not pick up from it because it does not exist. The exported locale is {F!r} but it is not supported"
raise RuntimeError(f"Click will abort further execution because Python was configured to use ASCII as encoding for the environment. Consult https://click.palletsprojects.com/unicode-support/ for mitigation steps.{B}")
\ No newline at end of file
diff --git a/dynaconf/vendor/click/core.py b/dynaconf/vendor/click/core.py
index fe475eb..6a3fee9 100644
--- a/dynaconf/vendor/click/core.py
+++ b/dynaconf/vendor/click/core.py
@@ -1,9 +1,5 @@
-_I='default'
-_H=' / '
-_G='...'
-_F='nargs'
-_E='-'
-_D='_'
+_E='default'
+_D='nargs'
_C=True
_B=False
_A=None
@@ -44,7 +40,7 @@ def _maybe_show_deprecated_notice(cmd):
def fast_exit(code):sys.stdout.flush();sys.stderr.flush();os._exit(code)
def _bashcomplete(cmd,prog_name,complete_var=_A):
B=prog_name;A=complete_var
- if A is _A:A=f"_{B}_COMPLETE".replace(_E,_D).upper()
+ if A is _A:A=f"_{B}_COMPLETE".replace('-','_').upper()
C=os.environ.get(A)
if not C:return
from ._bashcomplete import bashcomplete as D
@@ -105,7 +101,7 @@ class Context:
if C is _A:
if B is not _A and B.auto_envvar_prefix is not _A and A.info_name is not _A:C=f"{B.auto_envvar_prefix}_{A.info_name.upper()}"
else:C=C.upper()
- if C is not _A:C=C.replace(_E,_D)
+ if C is not _A:C=C.replace('-','_')
A.auto_envvar_prefix=C
if N is _A and B is not _A:N=B.color
A.color=N;A.show_default=show_default;A._close_callbacks=[];A._depth=0;A._source_by_paramname={}
@@ -167,7 +163,7 @@ class Context:
if D.name not in E and D.expose_value:E[D.name]=D.get_default(G)
B=B[2:]
with augment_usage_errors(F):
- with G:return A(*B,**E)
+ with G:return A(*(B),**E)
def forward(*E,**A):
B,D=E[:2]
if not isinstance(D,Command):raise TypeError('Callback is not a command.')
@@ -218,7 +214,7 @@ class BaseCommand:
except Abort:
if not D:raise
echo('Aborted!',file=sys.stderr);sys.exit(1)
- def __call__(A,*B,**C):return A.main(*B,**C)
+ def __call__(A,*B,**C):return A.main(*(B),**C)
class Command(BaseCommand):
def __init__(A,name,context_settings=_A,callback=_A,params=_A,help=_A,epilog=_A,short_help=_A,options_metavar='[OPTIONS]',add_help_option=_C,no_args_is_help=_B,hidden=_B,deprecated=_B):
B='\x0c';BaseCommand.__init__(A,name,context_settings);A.callback=callback;A.params=params or[]
@@ -305,7 +301,7 @@ class MultiCommand(Command):
def B(f):
B=A.result_callback
if B is _A or replace:A.result_callback=f;return f
- def C(__value,*A,**C):return f(B(__value,*A,**C),*A,**C)
+ def C(__value,*A,**C):return f(B(__value,*(A),**C),*(A),**C)
A.result_callback=D=update_wrapper(C,f);return D
return B
def format_commands(F,ctx,formatter):
@@ -367,11 +363,11 @@ class Group(MultiCommand):
_check_multicommand(C,A,B,register=_C);C.commands[A]=B
def command(B,*C,**D):
from .decorators import command as E
- def A(f):A=E(*C,**D)(f);B.add_command(A);return A
+ def A(f):A=E(*(C),**D)(f);B.add_command(A);return A
return A
def group(B,*C,**D):
from .decorators import group
- def A(f):A=group(*C,**D)(f);B.add_command(A);return A
+ def A(f):A=group(*(C),**D)(f);B.add_command(A);return A
return A
def get_command(A,ctx,cmd_name):return A.commands.get(cmd_name)
def list_commands(A,ctx):return sorted(A.commands)
@@ -404,7 +400,7 @@ class Parameter:
if A.metavar is not _A:return A.metavar
B=A.type.get_metavar(A)
if B is _A:B=A.type.name.upper()
- if A.nargs!=1:B+=_G
+ if A.nargs!=1:B+='...'
return B
def get_default(A,ctx):
if callable(A.default):B=A.default()
@@ -472,12 +468,12 @@ class Parameter:
return C,args
def get_help_record(A,ctx):0
def get_usage_pieces(A,ctx):return[]
- def get_error_hint(A,ctx):B=A.opts or[A.human_readable_name];return _H.join((repr(A)for A in B))
+ def get_error_hint(A,ctx):B=A.opts or[A.human_readable_name];return ' / '.join((repr(A)for A in B))
class Option(Parameter):
param_type_name='option'
def __init__(A,param_decls=_A,show_default=_B,prompt=_B,confirmation_prompt=_B,hide_input=_B,is_flag=_A,flag_value=_A,multiple=_B,count=_B,allow_from_autoenv=_C,type=_A,help=_A,hidden=_B,show_choices=_C,show_envvar=_B,**G):
- F=count;D=prompt;C=flag_value;B=is_flag;H=G.get(_I,_missing)is _missing;Parameter.__init__(A,param_decls,type=type,**G)
- if D is _C:E=A.name.replace(_D,' ').capitalize()
+ F=count;D=prompt;C=flag_value;B=is_flag;H=G.get(_E,_missing)is _missing;Parameter.__init__(A,param_decls,type=type,**G)
+ if D is _C:E=A.name.replace('_',' ').capitalize()
elif D is _B:E=_A
else:E=D
A.prompt=E;A.confirmation_prompt=confirmation_prompt;A.hide_input=hide_input;A.hidden=hidden
@@ -502,14 +498,14 @@ class Option(Parameter):
if A.count:
if A.multiple:raise TypeError('Options cannot be multiple and count at the same time.')
elif A.is_flag:raise TypeError('Options cannot be count and flags at the same time.')
- def _parse_decls(J,decls,expose_value):
- I='/';C=[];F=[];A=_A;D=[]
+ def _parse_decls(I,decls,expose_value):
+ C=[];F=[];A=_A;D=[]
for B in decls:
if B.isidentifier():
if A is not _A:raise TypeError('Name defined twice')
A=B
else:
- H=';'if B[:1]==I else I
+ H=';'if B[:1]=='/'else'/'
if H in B:
E,G=B.split(H,1);E=E.rstrip()
if E:D.append(split_opt(E));C.append(E)
@@ -517,7 +513,7 @@ class Option(Parameter):
if G:F.append(G.lstrip())
else:D.append(split_opt(B));C.append(B)
if A is _A and D:
- D.sort(key=lambda x:-len(x[0]));A=D[0][1].replace(_E,_D).lower()
+ D.sort(key=lambda x:-len(x[0]));A=D[0][1].replace('-','_').lower()
if not A.isidentifier():A=_A
if A is _A:
if not expose_value:return _A,C,F
@@ -525,17 +521,17 @@ class Option(Parameter):
if not C and not F:raise TypeError(f"No options defined but a name was passed ({A}). Did you mean to declare an argument instead of an option?")
return A,C,F
def add_to_parser(A,parser,ctx):
- C=parser;B={'dest':A.name,_F:A.nargs,'obj':A}
+ C=parser;B={'dest':A.name,_D:A.nargs,'obj':A}
if A.multiple:D='append'
elif A.count:D='count'
else:D='store'
if A.is_flag:
- B.pop(_F,_A);E=f"{D}_const"
+ B.pop(_D,_A);E=f"{D}_const"
if A.is_bool_flag and A.secondary_opts:C.add_option(A.opts,action=E,const=_C,**B);C.add_option(A.secondary_opts,action=E,const=_B,**B)
else:C.add_option(A.opts,action=E,const=A.flag_value,**B)
else:B['action']=D;C.add_option(A.opts,**B)
def get_help_record(A,ctx):
- K=', ';E=ctx
+ E=ctx
if A.hidden:return
F=[]
def G(opts):
@@ -550,16 +546,16 @@ class Option(Parameter):
B=A.envvar
if B is _A:
if A.allow_from_autoenv and E.auto_envvar_prefix is not _A:B=f"{E.auto_envvar_prefix}_{A.name.upper()}"
- if B is not _A:J=K.join((str(A)for A in B))if isinstance(B,(list,tuple))else B;C.append(f"env var: {J}")
+ if B is not _A:J=', '.join((str(A)for A in B))if isinstance(B,(list,tuple))else B;C.append(f"env var: {J}")
if A.default is not _A and(A.show_default or E.show_default):
if isinstance(A.show_default,str):D=f"({A.show_default})"
- elif isinstance(A.default,(list,tuple)):D=K.join((str(B)for B in A.default))
+ elif isinstance(A.default,(list,tuple)):D=', '.join((str(B)for B in A.default))
elif inspect.isfunction(A.default):D='(dynamic)'
else:D=A.default
C.append(f"default: {D}")
if A.required:C.append('required')
if C:I=';'.join(C);help=f"{help} [{I}]"if help else f"[{I}]"
- return ('; 'if F else _H).join(H),help
+ return ('; 'if F else' / ').join(H),help
def get_default(A,ctx):
if A.is_flag and not A.is_bool_flag:
for B in ctx.command.params:
@@ -591,8 +587,8 @@ class Argument(Parameter):
def __init__(B,param_decls,required=_A,**C):
A=required
if A is _A:
- if C.get(_I)is not _A:A=_B
- else:A=C.get(_F,1)>0
+ if C.get(_E)is not _A:A=_B
+ else:A=C.get(_D,1)>0
Parameter.__init__(B,param_decls,required=A,**C)
if B.default is not _A and B.nargs<0:raise TypeError('nargs=-1 in combination with a default value is not supported.')
@property
@@ -605,14 +601,14 @@ class Argument(Parameter):
B=A.type.get_metavar(A)
if not B:B=A.name.upper()
if not A.required:B=f"[{B}]"
- if A.nargs!=1:B+=_G
+ if A.nargs!=1:B+='...'
return B
def _parse_decls(D,decls,expose_value):
A=decls
if not A:
if not expose_value:return _A,[],[]
raise TypeError('Could not determine name for argument')
- if len(A)==1:B=C=A[0];B=B.replace(_E,_D).lower()
+ if len(A)==1:B=C=A[0];B=B.replace('-','_').lower()
else:raise TypeError(f"Arguments take exactly one parameter declaration, got {len(A)}.")
return B,[C],[]
def get_usage_pieces(A,ctx):return[A.make_metavar()]
diff --git a/dynaconf/vendor/click/decorators.py b/dynaconf/vendor/click/decorators.py
index 888b3e0..9bc02b8 100644
--- a/dynaconf/vendor/click/decorators.py
+++ b/dynaconf/vendor/click/decorators.py
@@ -18,11 +18,11 @@ from .globals import get_current_context
from .utils import echo
def pass_context(f):
'Marks a callback as wanting to receive the current context\n object as first argument.\n '
- def A(*A,**B):return f(get_current_context(),*A,**B)
+ def A(*A,**B):return f(get_current_context(),*(A),**B)
return update_wrapper(A,f)
def pass_obj(f):
'Similar to :func:`pass_context`, but only pass the object on the\n context onwards (:attr:`Context.obj`). This is useful if that object\n represents the state of a nested system.\n '
- def A(*A,**B):return f(get_current_context().obj,*A,**B)
+ def A(*A,**B):return f(get_current_context().obj,*(A),**B)
return update_wrapper(A,f)
def make_pass_decorator(object_type,ensure=_D):
"Given an object type this creates a decorator that will work\n similar to :func:`pass_obj` but instead of passing the object of the\n current context, it will find the innermost context of type\n :func:`object_type`.\n\n This generates a decorator that works roughly like this::\n\n from functools import update_wrapper\n\n def decorator(f):\n @pass_context\n def new_func(ctx, *args, **kwargs):\n obj = ctx.find_object(object_type)\n return ctx.invoke(f, obj, *args, **kwargs)\n return update_wrapper(new_func, f)\n return decorator\n\n :param object_type: the type of the object to pass.\n :param ensure: if set to `True`, a new object will be created and\n remembered on the context if it's not there yet.\n ";A=object_type
@@ -32,7 +32,7 @@ def make_pass_decorator(object_type,ensure=_D):
if ensure:C=B.ensure_object(A)
else:C=B.find_object(A)
if C is _A:raise RuntimeError(f"Managed to invoke callback without a context object of type {A.__name__!r} existing.")
- return B.invoke(f,C,*D,**E)
+ return B.invoke(f,C,*(D),**E)
return update_wrapper(B,f)
return B
def _make_command(f,name,attrs,cls):
@@ -74,11 +74,11 @@ def confirmation_option(*B,**A):
def C(f):
def C(ctx,param,value):
if not value:ctx.abort()
- A.setdefault(_F,_C);A.setdefault(_G,C);A.setdefault(_H,_D);A.setdefault(_I,'Do you want to continue?');A.setdefault(_B,'Confirm the action without prompting.');return option(*B or('--yes',),**A)(f)
+ A.setdefault(_F,_C);A.setdefault(_G,C);A.setdefault(_H,_D);A.setdefault(_I,'Do you want to continue?');A.setdefault(_B,'Confirm the action without prompting.');return option(*(B or('--yes',)),**A)(f)
return C
def password_option(*B,**A):
"Shortcut for password prompts.\n\n This is equivalent to decorating a function with :func:`option` with\n the following parameters::\n\n @click.command()\n @click.option('--password', prompt=True, confirmation_prompt=True,\n hide_input=True)\n def changeadmin(password):\n pass\n "
- def C(f):A.setdefault(_I,_C);A.setdefault('confirmation_prompt',_C);A.setdefault('hide_input',_C);return option(*B or('--password',),**A)(f)
+ def C(f):A.setdefault(_I,_C);A.setdefault('confirmation_prompt',_C);A.setdefault('hide_input',_C);return option(*(B or('--password',)),**A)(f)
return C
def version_option(version=_A,*B,**A):
"Adds a ``--version`` option which immediately ends the program\n printing out the version number. This is implemented as an eager\n option that prints the version and exits the program in the callback.\n\n :param version: the version number to show. If not provided Click\n attempts an auto discovery via setuptools.\n :param prog_name: the name of the program (defaults to autodetection)\n :param message: custom message to show instead of the default\n (``'%(prog)s, version %(version)s'``)\n :param others: everything else is forwarded to :func:`option`.\n ";D=version
@@ -103,7 +103,7 @@ def version_option(version=_A,*B,**A):
if K.module_name==E:B=F.version;break
if B is _A:raise RuntimeError('Could not determine version')
echo(H%{'prog':C,'version':B},color=A.color);A.exit()
- A.setdefault(_F,_C);A.setdefault(_H,_D);A.setdefault(_J,_C);A.setdefault(_B,'Show the version and exit.');A[_G]=C;return option(*B or('--version',),**A)(f)
+ A.setdefault(_F,_C);A.setdefault(_H,_D);A.setdefault(_J,_C);A.setdefault(_B,'Show the version and exit.');A[_G]=C;return option(*(B or('--version',)),**A)(f)
return C
def help_option(*B,**A):
'Adds a ``--help`` option which immediately ends the program\n printing out the help page. This is usually unnecessary to add as\n this is added by default to all commands unless suppressed.\n\n Like :func:`version_option`, this is implemented as eager option that\n prints in the callback and exits.\n\n All arguments are forwarded to :func:`option`.\n '
@@ -111,5 +111,5 @@ def help_option(*B,**A):
def C(ctx,param,value):
A=ctx
if value and not A.resilient_parsing:echo(A.get_help(),color=A.color);A.exit()
- A.setdefault(_F,_C);A.setdefault(_H,_D);A.setdefault(_B,'Show this message and exit.');A.setdefault(_J,_C);A[_G]=C;return option(*B or('--help',),**A)(f)
+ A.setdefault(_F,_C);A.setdefault(_H,_D);A.setdefault(_B,'Show this message and exit.');A.setdefault(_J,_C);A[_G]=C;return option(*(B or('--help',)),**A)(f)
return C
\ No newline at end of file
diff --git a/dynaconf/vendor/click/globals.py b/dynaconf/vendor/click/globals.py
index e0b71c5..efcfd35 100644
--- a/dynaconf/vendor/click/globals.py
+++ b/dynaconf/vendor/click/globals.py
@@ -1,4 +1,3 @@
-_A=None
from threading import local
_local=local()
def get_current_context(silent=False):
@@ -7,8 +6,8 @@ def get_current_context(silent=False):
if not silent:raise RuntimeError('There is no active click context.')
def push_context(ctx):_local.__dict__.setdefault('stack',[]).append(ctx)
def pop_context():_local.stack.pop()
-def resolve_color_default(color=_A):
+def resolve_color_default(color=None):
A=color
- if A is not _A:return A
+ if A is not None:return A
B=get_current_context(silent=True)
- if B is not _A:return B.color
\ No newline at end of file
+ if B is not None:return B.color
\ No newline at end of file
diff --git a/dynaconf/vendor/click/parser.py b/dynaconf/vendor/click/parser.py
index 769c403..24d2c6c 100644
--- a/dynaconf/vendor/click/parser.py
+++ b/dynaconf/vendor/click/parser.py
@@ -1,4 +1,3 @@
-_D=False
_C='append'
_B='store'
_A=None
@@ -84,7 +83,7 @@ class ParsingState:
def __init__(A,rargs):A.opts={};A.largs=[];A.rargs=rargs;A.order=[]
class OptionParser:
def __init__(A,ctx=_A):
- B=ctx;A.ctx=B;A.allow_interspersed_args=True;A.ignore_unknown_options=_D
+ B=ctx;A.ctx=B;A.allow_interspersed_args=True;A.ignore_unknown_options=False
if B is not _A:A.allow_interspersed_args=B.allow_interspersed_args;A.ignore_unknown_options=B.ignore_unknown_options
A._short_opt={};A._long_opt={};A._opt_prefixes={'-','--'};A._args=[]
def add_option(B,opts,dest,action=_A,nargs=1,const=_A,obj=_A):
@@ -129,7 +128,7 @@ class OptionParser:
else:G=_A
F.process(G,B)
def _match_short_opt(B,arg,state):
- D=arg;A=state;J=_D;F=1;K=D[0];G=[]
+ D=arg;A=state;J=False;F=1;K=D[0];G=[]
for L in D[1:]:
H=normalize_opt(f"{K}{L}",B.ctx);E=B._short_opt.get(H);F+=1
if not E:
@@ -146,8 +145,8 @@ class OptionParser:
if J:break
if B.ignore_unknown_options and G:A.largs.append(f"{K}{''.join(G)}")
def _process_opts(B,arg,state):
- G='=';C=state;A=arg;D=_A
- if G in A:E,D=A.split(G,1)
+ C=state;A=arg;D=_A
+ if'='in A:E,D=A.split('=',1)
else:E=A
F=normalize_opt(E,B.ctx)
try:B._match_long_opt(F,D,C)
diff --git a/dynaconf/vendor/click/testing.py b/dynaconf/vendor/click/testing.py
index f78bc5f..7abd9d3 100644
--- a/dynaconf/vendor/click/testing.py
+++ b/dynaconf/vendor/click/testing.py
@@ -1,6 +1,5 @@
-_E='replace'
-_D=False
-_C='\n'
+_D='replace'
+_C=False
_B='\r\n'
_A=None
import contextlib,io,os,shlex,shutil,sys,tempfile
@@ -28,22 +27,22 @@ class Result:
@property
def output(self):return self.stdout
@property
- def stdout(self):return self.stdout_bytes.decode(self.runner.charset,_E).replace(_B,_C)
+ def stdout(self):return self.stdout_bytes.decode(self.runner.charset,_D).replace(_B,'\n')
@property
def stderr(self):
A=self
if A.stderr_bytes is _A:raise ValueError('stderr not separately captured')
- return A.stderr_bytes.decode(A.runner.charset,_E).replace(_B,_C)
+ return A.stderr_bytes.decode(A.runner.charset,_D).replace(_B,'\n')
def __repr__(A):B=repr(A.exception)if A.exception else'okay';return f"<{type(A).__name__} {B}>"
class CliRunner:
- def __init__(A,charset='utf-8',env=_A,echo_stdin=_D,mix_stderr=True):A.charset=charset;A.env=env or{};A.echo_stdin=echo_stdin;A.mix_stderr=mix_stderr
+ def __init__(A,charset='utf-8',env=_A,echo_stdin=_C,mix_stderr=True):A.charset=charset;A.env=env or{};A.echo_stdin=echo_stdin;A.mix_stderr=mix_stderr
def get_default_prog_name(A,cli):return cli.name or'root'
def make_env(C,overrides=_A):
A=overrides;B=dict(C.env)
if A:B.update(A)
return B
@contextlib.contextmanager
- def isolation(self,input=_A,env=_A,color=_D):
+ def isolation(self,input=_A,env=_A,color=_C):
D=env;A=self;input=make_input_stream(input,A.charset);H=sys.stdin;I=sys.stdout;J=sys.stderr;K=formatting.FORCED_WIDTH;formatting.FORCED_WIDTH=80;D=A.make_env(D);E=io.BytesIO()
if A.echo_stdin:input=EchoingStdin(input,E)
input=io.TextIOWrapper(input,encoding=A.charset);sys.stdout=io.TextIOWrapper(E,encoding=A.charset)
@@ -77,7 +76,7 @@ class CliRunner:
except Exception:pass
else:os.environ[B]=C
sys.stdout=I;sys.stderr=J;sys.stdin=H;termui.visible_prompt_func=Q;termui.hidden_prompt_func=R;termui._getchar=S;utils.should_strip_ansi=T;formatting.FORCED_WIDTH=K
- def invoke(B,cli,args=_A,input=_A,env=_A,catch_exceptions=True,color=_D,**G):
+ def invoke(B,cli,args=_A,input=_A,env=_A,catch_exceptions=True,color=_C,**G):
C=args;E=_A
with B.isolation(input=input,env=env,color=color)as H:
F=_A;A=0
@@ -89,7 +88,7 @@ class CliRunner:
E=sys.exc_info();A=D.code
if A is _A:A=0
if A!=0:F=D
- if not isinstance(A,int):sys.stdout.write(str(A));sys.stdout.write(_C);A=1
+ if not isinstance(A,int):sys.stdout.write(str(A));sys.stdout.write('\n');A=1
except Exception as D:
if not catch_exceptions:raise
F=D;A=1;E=sys.exc_info()
diff --git a/dynaconf/vendor/click/types.py b/dynaconf/vendor/click/types.py
index 30ee5fa..d0824f0 100644
--- a/dynaconf/vendor/click/types.py
+++ b/dynaconf/vendor/click/types.py
@@ -1,4 +1,3 @@
-_F='text'
_E='replace'
_D='utf-8'
_C=True
@@ -38,11 +37,11 @@ class FuncParamType(ParamType):
except UnicodeError:A=A.decode(_D,_E)
B.fail(A,param,ctx)
class UnprocessedParamType(ParamType):
- name=_F
+ name='text'
def convert(A,value,param,ctx):return value
def __repr__(A):return'UNPROCESSED'
class StringParamType(ParamType):
- name=_F
+ name='text'
def convert(D,value,param,ctx):
A=value
if isinstance(A,bytes):
diff --git a/dynaconf/vendor/click/utils.py b/dynaconf/vendor/click/utils.py
index e0cf442..772066c 100644
--- a/dynaconf/vendor/click/utils.py
+++ b/dynaconf/vendor/click/utils.py
@@ -9,7 +9,7 @@ echo_native_types=str,bytes,bytearray
def _posixify(name):return '-'.join(name.split()).lower()
def safecall(func):
def A(*A,**B):
- try:return func(*A,**B)
+ try:return func(*(A),**B)
except Exception:pass
return A
def make_str(value):
diff --git a/dynaconf/vendor/dotenv/__init__.py b/dynaconf/vendor/dotenv/__init__.py
index 25aa760..597ee6e 100644
--- a/dynaconf/vendor/dotenv/__init__.py
+++ b/dynaconf/vendor/dotenv/__init__.py
@@ -4,7 +4,7 @@ from .main import load_dotenv,get_key,set_key,unset_key,find_dotenv,dotenv_value
if IS_TYPE_CHECKING:from typing import Any,Optional
def load_ipython_extension(ipython):from .ipython import load_ipython_extension as A;A(ipython)
def get_cli_string(path=_A,action=_A,key=_A,value=_A,quote=_A):
- E=' ';D=quote;C=action;B=value;A=['dotenv']
+ D=quote;C=action;B=value;A=['dotenv']
if D:A.append('-q %s'%D)
if path:A.append('-f %s'%path)
if C:
@@ -12,7 +12,7 @@ def get_cli_string(path=_A,action=_A,key=_A,value=_A,quote=_A):
if key:
A.append(key)
if B:
- if E in B:A.append('"%s"'%B)
+ if' 'in B:A.append('"%s"'%B)
else:A.append(B)
- return E.join(A).strip()
+ return ' '.join(A).strip()
__all__=['get_cli_string','load_dotenv','dotenv_values','get_key','set_key','unset_key','find_dotenv','load_ipython_extension']
\ No newline at end of file
diff --git a/dynaconf/vendor/dotenv/compat.py b/dynaconf/vendor/dotenv/compat.py
index 09aad2f..a901be1 100644
--- a/dynaconf/vendor/dotenv/compat.py
+++ b/dynaconf/vendor/dotenv/compat.py
@@ -1,4 +1,3 @@
-_A='utf-8'
import sys
PY2=sys.version_info[0]==2
if PY2:from StringIO import StringIO
@@ -10,9 +9,9 @@ def is_type_checking():
IS_TYPE_CHECKING=is_type_checking()
if IS_TYPE_CHECKING:from typing import Text
def to_env(text):
- if PY2:return text.encode(sys.getfilesystemencoding()or _A)
+ if PY2:return text.encode(sys.getfilesystemencoding()or'utf-8')
else:return text
def to_text(string):
A=string
- if PY2:return A.decode(_A)
+ if PY2:return A.decode('utf-8')
else:return A
\ No newline at end of file
diff --git a/dynaconf/vendor/dotenv/ipython.py b/dynaconf/vendor/dotenv/ipython.py
index 47b92bc..7db908f 100644
--- a/dynaconf/vendor/dotenv/ipython.py
+++ b/dynaconf/vendor/dotenv/ipython.py
@@ -11,8 +11,8 @@ class IPythonDotEnv(Magics):
@argument('dotenv_path',nargs='?',type=str,default='.env',help='Search in increasingly higher folders for the `dotenv_path`')
@line_magic
def dotenv(self,line):
- C=True;A=parse_argstring(self.dotenv,line);B=A.dotenv_path
- try:B=find_dotenv(B,C,C)
+ A=parse_argstring(self.dotenv,line);B=A.dotenv_path
+ try:B=find_dotenv(B,True,True)
except IOError:print('cannot find .env file');return
load_dotenv(B,verbose=A.verbose,override=A.override)
def load_ipython_extension(ipython):ipython.register_magics(IPythonDotEnv)
\ No newline at end of file
diff --git a/dynaconf/vendor/dotenv/main.py b/dynaconf/vendor/dotenv/main.py
index 343e298..eee673a 100644
--- a/dynaconf/vendor/dotenv/main.py
+++ b/dynaconf/vendor/dotenv/main.py
@@ -1,5 +1,4 @@
from __future__ import absolute_import,print_function,unicode_literals
-_E='.env'
_D='always'
_C=True
_B=False
@@ -30,7 +29,7 @@ class DotEnv:
elif os.path.isfile(A.dotenv_path):
with io.open(A.dotenv_path,encoding=A.encoding)as B:yield B
else:
- if A.verbose:logger.info('Python-dotenv could not find configuration file %s.',A.dotenv_path or _E)
+ if A.verbose:logger.info('Python-dotenv could not find configuration file %s.',A.dotenv_path or'.env')
yield StringIO('')
def dict(A):
if A._dict:return A._dict
@@ -60,10 +59,10 @@ def rewrite(path):
raise
else:shutil.move(A.name,path)
def set_key(dotenv_path,key_to_set,value_to_set,quote_mode=_D):
- K='"';E=quote_mode;C=dotenv_path;B=key_to_set;A=value_to_set;A=A.strip("'").strip(K)
+ E=quote_mode;C=dotenv_path;B=key_to_set;A=value_to_set;A=A.strip("'").strip('"')
if not os.path.exists(C):logger.warning("Can't write to %s - it doesn't exist.",C);return _A,B,A
if' 'in A:E=_D
- if E==_D:F='"{}"'.format(A.replace(K,'\\"'))
+ if E==_D:F='"{}"'.format(A.replace('"','\\"'))
else:F=A
G='{}={}\n'.format(B,F)
with rewrite(C)as(J,D):
@@ -95,18 +94,18 @@ def _walk_to_root(path):
if os.path.isfile(A):A=os.path.dirname(A)
C=_A;B=os.path.abspath(A)
while C!=B:yield B;D=os.path.abspath(os.path.join(B,os.path.pardir));C,B=B,D
-def find_dotenv(filename=_E,raise_error_if_not_found=_B,usecwd=_B):
- H='.py'
- def E():B='__file__';A=__import__('__main__',_A,_A,fromlist=[B]);return not hasattr(A,B)
- if usecwd or E()or getattr(sys,'frozen',_B):B=os.getcwd()
+def find_dotenv(filename='.env',raise_error_if_not_found=_B,usecwd=_B):
+ E='.py'
+ def F():A='__file__';B=__import__('__main__',_A,_A,fromlist=[A]);return not hasattr(B,A)
+ if usecwd or F()or getattr(sys,'frozen',_B):B=os.getcwd()
else:
A=sys._getframe()
- if PY2 and not __file__.endswith(H):C=__file__.rsplit('.',1)[0]+H
+ if PY2 and not __file__.endswith(E):C=__file__.rsplit('.',1)[0]+E
else:C=__file__
while A.f_code.co_filename==C:assert A.f_back is not _A;A=A.f_back
- F=A.f_code.co_filename;B=os.path.dirname(os.path.abspath(F))
- for G in _walk_to_root(B):
- D=os.path.join(G,filename)
+ G=A.f_code.co_filename;B=os.path.dirname(os.path.abspath(G))
+ for H in _walk_to_root(B):
+ D=os.path.join(H,filename)
if os.path.isfile(D):return D
if raise_error_if_not_found:raise IOError('File not found')
return''
diff --git a/dynaconf/vendor/dotenv/parser.py b/dynaconf/vendor/dotenv/parser.py
index 65f4f31..b752784 100644
--- a/dynaconf/vendor/dotenv/parser.py
+++ b/dynaconf/vendor/dotenv/parser.py
@@ -1,9 +1,5 @@
-_I='error'
-_H='original'
-_G='value'
-_F='key'
-_E='Binding'
-_D='line'
+_E='original'
+_D='Binding'
_C='string'
_B='Original'
_A=None
@@ -26,8 +22,8 @@ _end_of_line=make_regex('[^\\S\\r\\n]*(?:\\r\\n|\\n|\\r|$)')
_rest_of_line=make_regex('[^\\r\\n]*(?:\\r|\\n|\\r\\n)?')
_double_quote_escapes=make_regex('\\\\[\\\\\'\\"abfnrtv]')
_single_quote_escapes=make_regex("\\\\[\\\\']")
-try:import typing;Original=typing.NamedTuple(_B,[(_C,typing.Text),(_D,int)]);Binding=typing.NamedTuple(_E,[(_F,typing.Optional[typing.Text]),(_G,typing.Optional[typing.Text]),(_H,Original),(_I,bool)])
-except ImportError:from collections import namedtuple;Original=namedtuple(_B,[_C,_D]);Binding=namedtuple(_E,[_F,_G,_H,_I])
+try:import typing;Original=typing.NamedTuple(_B,[(_C,typing.Text),('line',int)]);Binding=typing.NamedTuple(_D,[('key',typing.Optional[typing.Text]),('value',typing.Optional[typing.Text]),(_E,Original),('error',bool)])
+except ImportError:from collections import namedtuple;Original=namedtuple(_B,[_C,'line']);Binding=namedtuple(_D,['key','value',_E,'error'])
class Position:
def __init__(A,chars,line):A.chars=chars;A.line=line
@classmethod
@@ -71,14 +67,14 @@ def parse_value(reader):
elif B in('','\n','\r'):return''
else:return parse_unquoted_value(A)
def parse_binding(reader):
- D=False;A=reader;A.set_mark()
+ C=False;A=reader;A.set_mark()
try:
A.read_regex(_multiline_whitespace)
- if not A.has_next():return Binding(key=_A,value=_A,original=A.get_marked(),error=D)
- A.read_regex(_export);C=parse_key(A);A.read_regex(_whitespace)
+ if not A.has_next():return Binding(key=_A,value=_A,original=A.get_marked(),error=C)
+ A.read_regex(_export);D=parse_key(A);A.read_regex(_whitespace)
if A.peek(1)=='=':A.read_regex(_equal_sign);B=parse_value(A)
else:B=_A
- A.read_regex(_comment);A.read_regex(_end_of_line);return Binding(key=C,value=B,original=A.get_marked(),error=D)
+ A.read_regex(_comment);A.read_regex(_end_of_line);return Binding(key=D,value=B,original=A.get_marked(),error=C)
except Error:A.read_regex(_rest_of_line);return Binding(key=_A,value=_A,original=A.get_marked(),error=True)
def parse_stream(stream):
A=Reader(stream)
diff --git a/dynaconf/vendor/ruamel/yaml/__init__.py b/dynaconf/vendor/ruamel/yaml/__init__.py
index ac49423..9a272b8 100644
--- a/dynaconf/vendor/ruamel/yaml/__init__.py
+++ b/dynaconf/vendor/ruamel/yaml/__init__.py
@@ -1,10 +1,8 @@
from __future__ import print_function,absolute_import,division,unicode_literals
-_B='yaml'
-_A=False
-if _A:from typing import Dict,Any
-_package_data=dict(full_package_name='ruamel.yaml',version_info=(0,16,10),__version__='0.16.10',author='Anthon van der Neut',author_email='a.van.der.neut@ruamel.eu',description='ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order',entry_points=None,since=2014,extras_require={':platform_python_implementation=="CPython" and python_version<="2.7"':['ruamel.ordereddict'],':platform_python_implementation=="CPython" and python_version<"3.9"':['ruamel.yaml.clib>=0.1.2'],'jinja2':['ruamel.yaml.jinja2>=0.2'],'docs':['ryd']},classifiers=['Programming Language :: Python :: 2.7','Programming Language :: Python :: 3.5','Programming Language :: Python :: 3.6','Programming Language :: Python :: 3.7','Programming Language :: Python :: 3.8','Programming Language :: Python :: Implementation :: CPython','Programming Language :: Python :: Implementation :: PyPy','Programming Language :: Python :: Implementation :: Jython','Topic :: Software Development :: Libraries :: Python Modules','Topic :: Text Processing :: Markup','Typing :: Typed'],keywords='yaml 1.2 parser round-trip preserve quotes order config',read_the_docs=_B,supported=[(2,7),(3,5)],tox=dict(env='*',deps='ruamel.std.pathlib',fl8excl='_test/lib'),universal=True,rtfd=_B)
+if False:from typing import Dict,Any
+_package_data=dict(full_package_name='ruamel.yaml',version_info=(0,16,10),__version__='0.16.10',author='Anthon van der Neut',author_email='a.van.der.neut@ruamel.eu',description='ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order',entry_points=None,since=2014,extras_require={':platform_python_implementation=="CPython" and python_version<="2.7"':['ruamel.ordereddict'],':platform_python_implementation=="CPython" and python_version<"3.9"':['ruamel.yaml.clib>=0.1.2'],'jinja2':['ruamel.yaml.jinja2>=0.2'],'docs':['ryd']},classifiers=['Programming Language :: Python :: 2.7','Programming Language :: Python :: 3.5','Programming Language :: Python :: 3.6','Programming Language :: Python :: 3.7','Programming Language :: Python :: 3.8','Programming Language :: Python :: Implementation :: CPython','Programming Language :: Python :: Implementation :: PyPy','Programming Language :: Python :: Implementation :: Jython','Topic :: Software Development :: Libraries :: Python Modules','Topic :: Text Processing :: Markup','Typing :: Typed'],keywords='yaml 1.2 parser round-trip preserve quotes order config',read_the_docs='yaml',supported=[(2,7),(3,5)],tox=dict(env='*',deps='ruamel.std.pathlib',fl8excl='_test/lib'),universal=True,rtfd='yaml')
version_info=_package_data['version_info']
__version__=_package_data['__version__']
try:from .cyaml import *;__with_libyaml__=True
-except (ImportError,ValueError):__with_libyaml__=_A
+except (ImportError,ValueError):__with_libyaml__=False
from dynaconf.vendor.ruamel.yaml.main import *
\ No newline at end of file
diff --git a/dynaconf/vendor/ruamel/yaml/anchor.py b/dynaconf/vendor/ruamel/yaml/anchor.py
index 8327508..cb2d673 100644
--- a/dynaconf/vendor/ruamel/yaml/anchor.py
+++ b/dynaconf/vendor/ruamel/yaml/anchor.py
@@ -1,7 +1,6 @@
-_A=False
-if _A:from typing import Any,Dict,Optional,List,Union,Optional,Iterator
+if False:from typing import Any,Dict,Optional,List,Union,Optional,Iterator
anchor_attrib='_yaml_anchor'
class Anchor:
__slots__='value','always_dump';attrib=anchor_attrib
- def __init__(A):A.value=None;A.always_dump=_A
+ def __init__(A):A.value=None;A.always_dump=False
def __repr__(A):B=', (always dump)'if A.always_dump else'';return 'Anchor({!r}{})'.format(A.value,B)
\ No newline at end of file
diff --git a/dynaconf/vendor/ruamel/yaml/comments.py b/dynaconf/vendor/ruamel/yaml/comments.py
index da872f8..1f80924 100644
--- a/dynaconf/vendor/ruamel/yaml/comments.py
+++ b/dynaconf/vendor/ruamel/yaml/comments.py
@@ -1,7 +1,5 @@
from __future__ import absolute_import,print_function
-_G='_od'
-_F='CommentedMap'
-_E='# '
+_E='CommentedMap'
_D=True
_C='\n'
_B=False
@@ -14,7 +12,7 @@ from .anchor import Anchor
if PY2:from collections import MutableSet,Sized,Set,Mapping
else:from collections.abc import MutableSet,Sized,Set,Mapping
if _B:from typing import Any,Dict,Optional,List,Union,Optional,Iterator
-__all__=['CommentedSeq','CommentedKeySeq',_F,'CommentedOrderedMap','CommentedSet','comment_attrib','merge_attrib']
+__all__=['CommentedSeq','CommentedKeySeq',_E,'CommentedOrderedMap','CommentedSet','comment_attrib','merge_attrib']
comment_attrib='_yaml_comment'
format_attrib='_yaml_format'
line_col_attrib='_yaml_line_col'
@@ -96,10 +94,10 @@ class CommentedBase:
A=comment;from .error import CommentMark as C;from .tokens import CommentToken as D;E=B._yaml_get_pre_comment()
if A[-1]==_C:A=A[:-1]
F=C(indent)
- for G in A.split(_C):E.append(D(_E+G+_C,F,_A))
+ for G in A.split(_C):E.append(D('# '+G+_C,F,_A))
def yaml_set_comment_before_after_key(J,key,before=_A,indent=0,after=_A,after_indent=_A):
H=indent;E=after_indent;B=after;A=before;from dynaconf.vendor.ruamel.yaml.error import CommentMark as I;from dynaconf.vendor.ruamel.yaml.tokens import CommentToken as K
- def F(s,mark):return K((_E if s else'')+s+_C,mark,_A)
+ def F(s,mark):return K(('# 'if s else'')+s+_C,mark,_A)
if E is _A:E=H+2
if A and len(A)>1 and A[-1]==_C:A=A[:-1]
if B and B[-1]==_C:B=B[:-1]
@@ -117,13 +115,13 @@ class CommentedBase:
if not hasattr(A,Format.attrib):setattr(A,Format.attrib,Format())
return getattr(A,Format.attrib)
def yaml_add_eol_comment(C,comment,key=NoComment,column=_A):
- H='#';B=column;A=comment;from .tokens import CommentToken as D;from .error import CommentMark as E
+ B=column;A=comment;from .tokens import CommentToken as D;from .error import CommentMark as E
if B is _A:
try:B=C._yaml_get_column(key)
except AttributeError:B=0
- if A[0]!=H:A=_E+A
+ if A[0]!='#':A='# '+A
if B is _A:
- if A[0]==H:A=' '+A;B=0
+ if A[0]=='#':A=' '+A;B=0
F=E(B);G=[D(A,F,_A),_A];C._yaml_add_eol_comment(G,key=key)
@property
def lc(self):
@@ -158,7 +156,7 @@ class CommentedBase:
def _yaml_get_column(A,key):raise NotImplementedError
class CommentedSeq(MutableSliceableSequence,list,CommentedBase):
__slots__=Comment.attrib,'_lst'
- def __init__(A,*B,**C):list.__init__(A,*B,**C)
+ def __init__(A,*B,**C):list.__init__(A,*(B),**C)
def __getsingleitem__(A,idx):return list.__getitem__(A,idx)
def __setsingleitem__(B,idx,value):
C=idx;A=value
@@ -269,7 +267,7 @@ class CommentedMapValuesView(CommentedMapView):
for B in A._mapping._keys():yield A._mapping[B]
class CommentedMap(ordereddict,CommentedBase):
__slots__=Comment.attrib,'_ok','_ref'
- def __init__(A,*B,**C):A._ok=set();A._ref=[];ordereddict.__init__(A,*B,**C)
+ def __init__(A,*B,**C):A._ok=set();A._ref=[];ordereddict.__init__(A,*(B),**C)
def _yaml_add_comment(A,comment,key=NoComment,value=NoComment):
C=value;B=comment
if key is not NoComment:A.yaml_key_comment_extend(key,B);return
@@ -342,7 +340,7 @@ class CommentedMap(ordereddict,CommentedBase):
def get(A,key,default=_A):
try:return A.__getitem__(key)
except:return default
- def __repr__(A):return ordereddict.__repr__(A).replace(_F,'ordereddict')
+ def __repr__(A):return ordereddict.__repr__(A).replace(_E,'ordereddict')
def non_merged_items(A):
for B in ordereddict.__iter__(A):
if B in A._ok:yield(B,ordereddict.__getitem__(A,B))
@@ -409,10 +407,10 @@ class CommentedMap(ordereddict,CommentedBase):
@classmethod
def raise_immutable(cls,*A,**B):raise TypeError('{} objects are immutable'.format(cls.__name__))
class CommentedKeyMap(CommentedBase,Mapping):
- __slots__=Comment.attrib,_G
+ __slots__=Comment.attrib,'_od'
def __init__(A,*B,**C):
- if hasattr(A,_G):raise_immutable(A)
- try:A._od=ordereddict(*B,**C)
+ if hasattr(A,'_od'):raise_immutable(A)
+ try:A._od=ordereddict(*(B),**C)
except TypeError:
if PY2:A._od=ordereddict(B[0].items())
else:raise
@@ -474,12 +472,12 @@ class TaggedScalar(CommentedBase):
if tag is not _A:A.yaml_set_tag(tag)
def __str__(A):return A.value
def dump_comments(d,name='',sep='.',out=sys.stdout):
- G='ca';E='{}\n';D=out;C=sep;A=name
- if isinstance(d,dict)and hasattr(d,G):
+ E='{}\n';D=out;C=sep;A=name
+ if isinstance(d,dict)and hasattr(d,'ca'):
if A:sys.stdout.write(E.format(A))
D.write(E.format(d.ca))
for B in d:dump_comments(d[B],name=A+C+B if A else B,sep=C,out=D)
- elif isinstance(d,list)and hasattr(d,G):
+ elif isinstance(d,list)and hasattr(d,'ca'):
if A:sys.stdout.write(E.format(A))
D.write(E.format(d.ca))
for (F,B) in enumerate(d):dump_comments(B,name=A+C+str(F)if A else str(F),sep=C,out=D)
\ No newline at end of file
diff --git a/dynaconf/vendor/ruamel/yaml/compat.py b/dynaconf/vendor/ruamel/yaml/compat.py
index 0512ad7..c8033f7 100644
--- a/dynaconf/vendor/ruamel/yaml/compat.py
+++ b/dynaconf/vendor/ruamel/yaml/compat.py
@@ -66,7 +66,7 @@ class Nprint:
def __init__(A,file_name=_A):A._max_print=_A;A._count=_A;A._file_name=file_name
def __call__(A,*E,**F):
if not bool(_debug):return
- B=sys.stdout if A._file_name is _A else open(A._file_name,'a');C=print;D=F.copy();D['file']=B;C(*E,**D);B.flush()
+ B=sys.stdout if A._file_name is _A else open(A._file_name,'a');C=print;D=F.copy();D['file']=B;C(*(E),**D);B.flush()
if A._max_print is not _A:
if A._count is _A:A._count=A._max_print
A._count-=1
@@ -107,7 +107,7 @@ class MutableSliceableSequence(MutableSequence):
D=A.indices(len(C));E=(D[1]-D[0]-1)//D[2]+1
if E<len(B):raise TypeError('too many elements in value {} < {}'.format(E,len(B)))
elif E>len(B):raise TypeError('not enough elements in value {} > {}'.format(E,len(B)))
- for (G,H) in enumerate(range(*D)):C[H]=B[G]
+ for (G,H) in enumerate(range(*(D))):C[H]=B[G]
def __delitem__(A,index):
B=index
if not isinstance(B,slice):return A.__delsingleitem__(B)
diff --git a/dynaconf/vendor/ruamel/yaml/composer.py b/dynaconf/vendor/ruamel/yaml/composer.py
index 9a0f8f0..37a24d2 100644
--- a/dynaconf/vendor/ruamel/yaml/composer.py
+++ b/dynaconf/vendor/ruamel/yaml/composer.py
@@ -1,5 +1,4 @@
from __future__ import absolute_import,print_function
-_B='typ'
_A=None
import warnings
from .error import MarkedYAMLError,ReusedAnchorWarning
@@ -17,12 +16,12 @@ class Composer:
@property
def parser(self):
A=self
- if hasattr(A.loader,_B):A.loader.parser
+ if hasattr(A.loader,'typ'):A.loader.parser
return A.loader._parser
@property
def resolver(self):
A=self
- if hasattr(A.loader,_B):A.loader.resolver
+ if hasattr(A.loader,'typ'):A.loader.resolver
return A.loader._resolver
def check_node(A):
if A.parser.check_event(StreamStartEvent):A.parser.get_event()
diff --git a/dynaconf/vendor/ruamel/yaml/constructor.py b/dynaconf/vendor/ruamel/yaml/constructor.py
index 2400e6b..9897b80 100644
--- a/dynaconf/vendor/ruamel/yaml/constructor.py
+++ b/dynaconf/vendor/ruamel/yaml/constructor.py
@@ -1,61 +1,53 @@
from __future__ import print_function,absolute_import,division
-_AD='expected the empty value, but found %r'
-_AC='cannot find module %r (%s)'
-_AB='expected non-empty name appended to the tag'
-_AA='tag:yaml.org,2002:map'
-_A9='tag:yaml.org,2002:seq'
-_A8='tag:yaml.org,2002:set'
-_A7='tag:yaml.org,2002:pairs'
-_A6='tag:yaml.org,2002:omap'
-_A5='tag:yaml.org,2002:timestamp'
-_A4='tag:yaml.org,2002:binary'
-_A3='tag:yaml.org,2002:float'
-_A2='tag:yaml.org,2002:int'
-_A1='tag:yaml.org,2002:bool'
-_A0='tag:yaml.org,2002:null'
-_z='could not determine a constructor for the tag %r'
-_y='second'
-_x='minute'
-_w='day'
-_v='month'
-_u='year'
-_t='failed to construct timestamp from "{}"'
-_s='decodebytes'
-_r='failed to convert base64 data into ascii: %s'
-_q='.nan'
-_p='.inf'
-_o='expected a mapping or list of mappings for merging, but found %s'
-_n='expected a mapping for merging, but found %s'
-_m=' Duplicate keys will become an error in future releases, and are errors\n by default when using the new API.\n '
-_l='\n To suppress this check see:\n http://yaml.readthedocs.io/en/latest/api.html#duplicate-keys\n '
-_k='tag:yaml.org,2002:merge'
-_j=' Duplicate keys will become an error in future releases, and are errors\n by default when using the new API.\n '
-_i='\n To suppress this check see:\n http://yaml.readthedocs.io/en/latest/api.html#duplicate-keys\n '
-_h='expected a sequence node, but found %s'
-_g='expected a scalar node, but found %s'
-_f='typ'
-_e='while constructing a Python module'
-_d='expected a single mapping item, but found %d items'
-_c='expected a mapping of length 1, but found %s'
-_b='expected a sequence, but found %s'
-_a='failed to decode base64 data: %s'
-_Z='tag:yaml.org,2002:value'
-_Y='found duplicate key "{}"'
-_X='found unhashable key'
-_W='found unacceptable key (%s)'
-_V='__setstate__'
-_U='tz_hour'
-_T='hour'
-_S='ascii'
-_R='tag:yaml.org,2002:str'
-_Q='utf-8'
-_P='expected a mapping node, but found %s'
-_O='tz_minute'
-_N='e'
-_M='+-'
-_L='while constructing an ordered map'
-_K='tz_sign'
-_J='-'
+_A5='expected the empty value, but found %r'
+_A4='cannot find module %r (%s)'
+_A3='expected non-empty name appended to the tag'
+_A2='tag:yaml.org,2002:map'
+_A1='tag:yaml.org,2002:seq'
+_A0='tag:yaml.org,2002:set'
+_z='tag:yaml.org,2002:pairs'
+_y='tag:yaml.org,2002:omap'
+_x='tag:yaml.org,2002:timestamp'
+_w='tag:yaml.org,2002:binary'
+_v='tag:yaml.org,2002:float'
+_u='tag:yaml.org,2002:int'
+_t='tag:yaml.org,2002:bool'
+_s='tag:yaml.org,2002:null'
+_r='could not determine a constructor for the tag %r'
+_q='second'
+_p='minute'
+_o='failed to construct timestamp from "{}"'
+_n='decodebytes'
+_m='failed to convert base64 data into ascii: %s'
+_l='expected a mapping or list of mappings for merging, but found %s'
+_k='expected a mapping for merging, but found %s'
+_j=' Duplicate keys will become an error in future releases, and are errors\n by default when using the new API.\n '
+_i='\n To suppress this check see:\n http://yaml.readthedocs.io/en/latest/api.html#duplicate-keys\n '
+_h='tag:yaml.org,2002:merge'
+_g=' Duplicate keys will become an error in future releases, and are errors\n by default when using the new API.\n '
+_f='\n To suppress this check see:\n http://yaml.readthedocs.io/en/latest/api.html#duplicate-keys\n '
+_e='expected a sequence node, but found %s'
+_d='expected a scalar node, but found %s'
+_c='while constructing a Python module'
+_b='expected a single mapping item, but found %d items'
+_a='expected a mapping of length 1, but found %s'
+_Z='expected a sequence, but found %s'
+_Y='failed to decode base64 data: %s'
+_X='tag:yaml.org,2002:value'
+_W='found duplicate key "{}"'
+_V='found unhashable key'
+_U='found unacceptable key (%s)'
+_T='__setstate__'
+_S='tz_hour'
+_R='hour'
+_Q='ascii'
+_P='tag:yaml.org,2002:str'
+_O='utf-8'
+_N='expected a mapping node, but found %s'
+_M='tz_minute'
+_L='+-'
+_K='while constructing an ordered map'
+_J='tz_sign'
_I='fraction'
_H='.'
_G=':'
@@ -93,12 +85,12 @@ class BaseConstructor:
self.loader=loader;self.yaml_base_dict_type=dict;self.yaml_base_list_type=list;self.constructed_objects={};self.recursive_objects={};self.state_generators=[];self.deep_construct=_B;self._preserve_quotes=preserve_quotes;self.allow_duplicate_keys=version_tnf((0,15,1),(0,16))
@property
def composer(self):
- if hasattr(self.loader,_f):return self.loader.composer
+ if hasattr(self.loader,'typ'):return self.loader.composer
try:return self.loader._composer
except AttributeError:sys.stdout.write('slt {}\n'.format(type(self)));sys.stdout.write('slc {}\n'.format(self.loader._composer));sys.stdout.write('{}\n'.format(dir(self)));raise
@property
def resolver(self):
- if hasattr(self.loader,_f):return self.loader.resolver
+ if hasattr(self.loader,'typ'):return self.loader.resolver
return self.loader._resolver
def check_data(self):return self.composer.check_node()
def get_data(self):
@@ -143,13 +135,13 @@ class BaseConstructor:
else:self.state_generators.append(generator)
return data
def construct_scalar(self,node):
- if not isinstance(node,ScalarNode):raise ConstructorError(_A,_A,_g%node.id,node.start_mark)
+ if not isinstance(node,ScalarNode):raise ConstructorError(_A,_A,_d%node.id,node.start_mark)
return node.value
def construct_sequence(self,node,deep=_B):
- if not isinstance(node,SequenceNode):raise ConstructorError(_A,_A,_h%node.id,node.start_mark)
+ if not isinstance(node,SequenceNode):raise ConstructorError(_A,_A,_e%node.id,node.start_mark)
return[self.construct_object(child,deep=deep)for child in node.value]
def construct_mapping(self,node,deep=_B):
- if not isinstance(node,MappingNode):raise ConstructorError(_A,_A,_P%node.id,node.start_mark)
+ if not isinstance(node,MappingNode):raise ConstructorError(_A,_A,_N%node.id,node.start_mark)
total_mapping=self.yaml_base_dict_type()
if getattr(node,'merge',_A)is not _A:todo=[(node.merge,_B),(node.value,_B)]
else:todo=[(node.value,_C)]
@@ -161,8 +153,8 @@ class BaseConstructor:
if isinstance(key,list):key=tuple(key)
if PY2:
try:hash(key)
- except TypeError as exc:raise ConstructorError(_E,node.start_mark,_W%exc,key_node.start_mark)
- elif not isinstance(key,Hashable):raise ConstructorError(_E,node.start_mark,_X,key_node.start_mark)
+ except TypeError as exc:raise ConstructorError(_E,node.start_mark,_U%exc,key_node.start_mark)
+ elif not isinstance(key,Hashable):raise ConstructorError(_E,node.start_mark,_V,key_node.start_mark)
value=self.construct_object(value_node,deep=deep)
if check:
if self.check_mapping_key(node,key_node,mapping,key,value):mapping[key]=value
@@ -174,24 +166,24 @@ class BaseConstructor:
if not self.allow_duplicate_keys:
mk=mapping.get(key)
if PY2:
- if isinstance(key,unicode):key=key.encode(_Q)
- if isinstance(value,unicode):value=value.encode(_Q)
- if isinstance(mk,unicode):mk=mk.encode(_Q)
- args=[_E,node.start_mark,'found duplicate key "{}" with value "{}" (original value: "{}")'.format(key,value,mk),key_node.start_mark,_i,_j]
- if self.allow_duplicate_keys is _A:warnings.warn(DuplicateKeyFutureWarning(*args))
- else:raise DuplicateKeyError(*args)
+ if isinstance(key,unicode):key=key.encode(_O)
+ if isinstance(value,unicode):value=value.encode(_O)
+ if isinstance(mk,unicode):mk=mk.encode(_O)
+ args=[_E,node.start_mark,'found duplicate key "{}" with value "{}" (original value: "{}")'.format(key,value,mk),key_node.start_mark,_f,_g]
+ if self.allow_duplicate_keys is _A:warnings.warn(DuplicateKeyFutureWarning(*(args)))
+ else:raise DuplicateKeyError(*(args))
return _B
return _C
def check_set_key(self,node,key_node,setting,key):
if key in setting:
if not self.allow_duplicate_keys:
if PY2:
- if isinstance(key,unicode):key=key.encode(_Q)
- args=['while constructing a set',node.start_mark,_Y.format(key),key_node.start_mark,_i,_j]
- if self.allow_duplicate_keys is _A:warnings.warn(DuplicateKeyFutureWarning(*args))
- else:raise DuplicateKeyError(*args)
+ if isinstance(key,unicode):key=key.encode(_O)
+ args=['while constructing a set',node.start_mark,_W.format(key),key_node.start_mark,_f,_g]
+ if self.allow_duplicate_keys is _A:warnings.warn(DuplicateKeyFutureWarning(*(args)))
+ else:raise DuplicateKeyError(*(args))
def construct_pairs(self,node,deep=_B):
- if not isinstance(node,MappingNode):raise ConstructorError(_A,_A,_P%node.id,node.start_mark)
+ if not isinstance(node,MappingNode):raise ConstructorError(_A,_A,_N%node.id,node.start_mark)
pairs=[]
for (key_node,value_node) in node.value:key=self.construct_object(key_node,deep=deep);value=self.construct_object(value_node,deep=deep);pairs.append((key,value))
return pairs
@@ -207,29 +199,29 @@ class SafeConstructor(BaseConstructor):
def construct_scalar(self,node):
if isinstance(node,MappingNode):
for (key_node,value_node) in node.value:
- if key_node.tag==_Z:return self.construct_scalar(value_node)
+ if key_node.tag==_X:return self.construct_scalar(value_node)
return BaseConstructor.construct_scalar(self,node)
def flatten_mapping(self,node):
merge=[];index=0
while index<len(node.value):
key_node,value_node=node.value[index]
- if key_node.tag==_k:
+ if key_node.tag==_h:
if merge:
if self.allow_duplicate_keys:del node.value[index];index+=1;continue
- args=[_E,node.start_mark,_Y.format(key_node.value),key_node.start_mark,_l,_m]
- if self.allow_duplicate_keys is _A:warnings.warn(DuplicateKeyFutureWarning(*args))
- else:raise DuplicateKeyError(*args)
+ args=[_E,node.start_mark,_W.format(key_node.value),key_node.start_mark,_i,_j]
+ if self.allow_duplicate_keys is _A:warnings.warn(DuplicateKeyFutureWarning(*(args)))
+ else:raise DuplicateKeyError(*(args))
del node.value[index]
if isinstance(value_node,MappingNode):self.flatten_mapping(value_node);merge.extend(value_node.value)
elif isinstance(value_node,SequenceNode):
submerge=[]
for subnode in value_node.value:
- if not isinstance(subnode,MappingNode):raise ConstructorError(_E,node.start_mark,_n%subnode.id,subnode.start_mark)
+ if not isinstance(subnode,MappingNode):raise ConstructorError(_E,node.start_mark,_k%subnode.id,subnode.start_mark)
self.flatten_mapping(subnode);submerge.append(subnode.value)
submerge.reverse()
for value in submerge:merge.extend(value)
- else:raise ConstructorError(_E,node.start_mark,_o%value_node.id,value_node.start_mark)
- elif key_node.tag==_Z:key_node.tag=_R;index+=1
+ else:raise ConstructorError(_E,node.start_mark,_l%value_node.id,value_node.start_mark)
+ elif key_node.tag==_X:key_node.tag=_P;index+=1
else:index+=1
if bool(merge):node.merge=merge;node.value=merge+node.value
def construct_mapping(self,node,deep=_B):
@@ -240,8 +232,8 @@ class SafeConstructor(BaseConstructor):
def construct_yaml_bool(self,node):value=self.construct_scalar(node);return self.bool_values[value.lower()]
def construct_yaml_int(self,node):
value_s=to_str(self.construct_scalar(node));value_s=value_s.replace(_D,'');sign=+1
- if value_s[0]==_J:sign=-1
- if value_s[0]in _M:value_s=value_s[1:]
+ if value_s[0]=='-':sign=-1
+ if value_s[0]in _L:value_s=value_s[1:]
if value_s==_F:return 0
elif value_s.startswith('0b'):return sign*int(value_s[2:],2)
elif value_s.startswith('0x'):return sign*int(value_s[2:],16)
@@ -257,93 +249,93 @@ class SafeConstructor(BaseConstructor):
nan_value=-inf_value/inf_value
def construct_yaml_float(self,node):
value_so=to_str(self.construct_scalar(node));value_s=value_so.replace(_D,'').lower();sign=+1
- if value_s[0]==_J:sign=-1
- if value_s[0]in _M:value_s=value_s[1:]
- if value_s==_p:return sign*self.inf_value
- elif value_s==_q:return self.nan_value
+ if value_s[0]=='-':sign=-1
+ if value_s[0]in _L:value_s=value_s[1:]
+ if value_s=='.inf':return sign*self.inf_value
+ elif value_s=='.nan':return self.nan_value
elif self.resolver.processing_version!=(1,2)and _G in value_s:
digits=[float(part)for part in value_s.split(_G)];digits.reverse();base=1;value=0.0
for digit in digits:value+=digit*base;base*=60
return sign*value
else:
- if self.resolver.processing_version!=(1,2)and _N in value_s:
- mantissa,exponent=value_s.split(_N)
+ if self.resolver.processing_version!=(1,2)and'e'in value_s:
+ mantissa,exponent=value_s.split('e')
if _H not in mantissa:warnings.warn(MantissaNoDotYAML1_1Warning(node,value_so))
return sign*float(value_s)
if PY3:
def construct_yaml_binary(self,node):
- try:value=self.construct_scalar(node).encode(_S)
- except UnicodeEncodeError as exc:raise ConstructorError(_A,_A,_r%exc,node.start_mark)
+ try:value=self.construct_scalar(node).encode(_Q)
+ except UnicodeEncodeError as exc:raise ConstructorError(_A,_A,_m%exc,node.start_mark)
try:
- if hasattr(base64,_s):return base64.decodebytes(value)
+ if hasattr(base64,_n):return base64.decodebytes(value)
else:return base64.decodestring(value)
- except binascii.Error as exc:raise ConstructorError(_A,_A,_a%exc,node.start_mark)
+ except binascii.Error as exc:raise ConstructorError(_A,_A,_Y%exc,node.start_mark)
else:
def construct_yaml_binary(self,node):
value=self.construct_scalar(node)
try:return to_str(value).decode('base64')
- except (binascii.Error,UnicodeEncodeError)as exc:raise ConstructorError(_A,_A,_a%exc,node.start_mark)
+ except (binascii.Error,UnicodeEncodeError)as exc:raise ConstructorError(_A,_A,_Y%exc,node.start_mark)
timestamp_regexp=RegExp('^(?P<year>[0-9][0-9][0-9][0-9])\n -(?P<month>[0-9][0-9]?)\n -(?P<day>[0-9][0-9]?)\n (?:((?P<t>[Tt])|[ \\t]+) # explictly not retaining extra spaces\n (?P<hour>[0-9][0-9]?)\n :(?P<minute>[0-9][0-9])\n :(?P<second>[0-9][0-9])\n (?:\\.(?P<fraction>[0-9]*))?\n (?:[ \\t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)\n (?::(?P<tz_minute>[0-9][0-9]))?))?)?$',re.X)
def construct_yaml_timestamp(self,node,values=_A):
if values is _A:
try:match=self.timestamp_regexp.match(node.value)
except TypeError:match=_A
- if match is _A:raise ConstructorError(_A,_A,_t.format(node.value),node.start_mark)
+ if match is _A:raise ConstructorError(_A,_A,_o.format(node.value),node.start_mark)
values=match.groupdict()
- year=int(values[_u]);month=int(values[_v]);day=int(values[_w])
- if not values[_T]:return datetime.date(year,month,day)
- hour=int(values[_T]);minute=int(values[_x]);second=int(values[_y]);fraction=0
+ year=int(values['year']);month=int(values['month']);day=int(values['day'])
+ if not values[_R]:return datetime.date(year,month,day)
+ hour=int(values[_R]);minute=int(values[_p]);second=int(values[_q]);fraction=0
if values[_I]:
fraction_s=values[_I][:6]
while len(fraction_s)<6:fraction_s+=_F
fraction=int(fraction_s)
if len(values[_I])>6 and int(values[_I][6])>4:fraction+=1
delta=_A
- if values[_K]:
- tz_hour=int(values[_U]);minutes=values[_O];tz_minute=int(minutes)if minutes else 0;delta=datetime.timedelta(hours=tz_hour,minutes=tz_minute)
- if values[_K]==_J:delta=-delta
+ if values[_J]:
+ tz_hour=int(values[_S]);minutes=values[_M];tz_minute=int(minutes)if minutes else 0;delta=datetime.timedelta(hours=tz_hour,minutes=tz_minute)
+ if values[_J]=='-':delta=-delta
data=datetime.datetime(year,month,day,hour,minute,second,fraction)
if delta:data-=delta
return data
def construct_yaml_omap(self,node):
omap=ordereddict();yield omap
- if not isinstance(node,SequenceNode):raise ConstructorError(_L,node.start_mark,_b%node.id,node.start_mark)
+ if not isinstance(node,SequenceNode):raise ConstructorError(_K,node.start_mark,_Z%node.id,node.start_mark)
for subnode in node.value:
- if not isinstance(subnode,MappingNode):raise ConstructorError(_L,node.start_mark,_c%subnode.id,subnode.start_mark)
- if len(subnode.value)!=1:raise ConstructorError(_L,node.start_mark,_d%len(subnode.value),subnode.start_mark)
+ if not isinstance(subnode,MappingNode):raise ConstructorError(_K,node.start_mark,_a%subnode.id,subnode.start_mark)
+ if len(subnode.value)!=1:raise ConstructorError(_K,node.start_mark,_b%len(subnode.value),subnode.start_mark)
key_node,value_node=subnode.value[0];key=self.construct_object(key_node);assert key not in omap;value=self.construct_object(value_node);omap[key]=value
def construct_yaml_pairs(self,node):
A='while constructing pairs';pairs=[];yield pairs
- if not isinstance(node,SequenceNode):raise ConstructorError(A,node.start_mark,_b%node.id,node.start_mark)
+ if not isinstance(node,SequenceNode):raise ConstructorError(A,node.start_mark,_Z%node.id,node.start_mark)
for subnode in node.value:
- if not isinstance(subnode,MappingNode):raise ConstructorError(A,node.start_mark,_c%subnode.id,subnode.start_mark)
- if len(subnode.value)!=1:raise ConstructorError(A,node.start_mark,_d%len(subnode.value),subnode.start_mark)
+ if not isinstance(subnode,MappingNode):raise ConstructorError(A,node.start_mark,_a%subnode.id,subnode.start_mark)
+ if len(subnode.value)!=1:raise ConstructorError(A,node.start_mark,_b%len(subnode.value),subnode.start_mark)
key_node,value_node=subnode.value[0];key=self.construct_object(key_node);value=self.construct_object(value_node);pairs.append((key,value))
def construct_yaml_set(self,node):data=set();yield data;value=self.construct_mapping(node);data.update(value)
def construct_yaml_str(self,node):
value=self.construct_scalar(node)
if PY3:return value
- try:return value.encode(_S)
+ try:return value.encode(_Q)
except UnicodeEncodeError:return value
def construct_yaml_seq(self,node):data=self.yaml_base_list_type();yield data;data.extend(self.construct_sequence(node))
def construct_yaml_map(self,node):data=self.yaml_base_dict_type();yield data;value=self.construct_mapping(node);data.update(value)
def construct_yaml_object(self,node,cls):
data=cls.__new__(cls);yield data
- if hasattr(data,_V):state=self.construct_mapping(node,deep=_C);data.__setstate__(state)
+ if hasattr(data,_T):state=self.construct_mapping(node,deep=_C);data.__setstate__(state)
else:state=self.construct_mapping(node);data.__dict__.update(state)
- def construct_undefined(self,node):raise ConstructorError(_A,_A,_z%utf8(node.tag),node.start_mark)
-SafeConstructor.add_constructor(_A0,SafeConstructor.construct_yaml_null)
-SafeConstructor.add_constructor(_A1,SafeConstructor.construct_yaml_bool)
-SafeConstructor.add_constructor(_A2,SafeConstructor.construct_yaml_int)
-SafeConstructor.add_constructor(_A3,SafeConstructor.construct_yaml_float)
-SafeConstructor.add_constructor(_A4,SafeConstructor.construct_yaml_binary)
-SafeConstructor.add_constructor(_A5,SafeConstructor.construct_yaml_timestamp)
-SafeConstructor.add_constructor(_A6,SafeConstructor.construct_yaml_omap)
-SafeConstructor.add_constructor(_A7,SafeConstructor.construct_yaml_pairs)
-SafeConstructor.add_constructor(_A8,SafeConstructor.construct_yaml_set)
-SafeConstructor.add_constructor(_R,SafeConstructor.construct_yaml_str)
-SafeConstructor.add_constructor(_A9,SafeConstructor.construct_yaml_seq)
-SafeConstructor.add_constructor(_AA,SafeConstructor.construct_yaml_map)
+ def construct_undefined(self,node):raise ConstructorError(_A,_A,_r%utf8(node.tag),node.start_mark)
+SafeConstructor.add_constructor(_s,SafeConstructor.construct_yaml_null)
+SafeConstructor.add_constructor(_t,SafeConstructor.construct_yaml_bool)
+SafeConstructor.add_constructor(_u,SafeConstructor.construct_yaml_int)
+SafeConstructor.add_constructor(_v,SafeConstructor.construct_yaml_float)
+SafeConstructor.add_constructor(_w,SafeConstructor.construct_yaml_binary)
+SafeConstructor.add_constructor(_x,SafeConstructor.construct_yaml_timestamp)
+SafeConstructor.add_constructor(_y,SafeConstructor.construct_yaml_omap)
+SafeConstructor.add_constructor(_z,SafeConstructor.construct_yaml_pairs)
+SafeConstructor.add_constructor(_A0,SafeConstructor.construct_yaml_set)
+SafeConstructor.add_constructor(_P,SafeConstructor.construct_yaml_str)
+SafeConstructor.add_constructor(_A1,SafeConstructor.construct_yaml_seq)
+SafeConstructor.add_constructor(_A2,SafeConstructor.construct_yaml_map)
SafeConstructor.add_constructor(_A,SafeConstructor.construct_undefined)
if PY2:
class classobj:0
@@ -352,12 +344,12 @@ class Constructor(SafeConstructor):
def construct_python_unicode(self,node):return self.construct_scalar(node)
if PY3:
def construct_python_bytes(self,node):
- try:value=self.construct_scalar(node).encode(_S)
- except UnicodeEncodeError as exc:raise ConstructorError(_A,_A,_r%exc,node.start_mark)
+ try:value=self.construct_scalar(node).encode(_Q)
+ except UnicodeEncodeError as exc:raise ConstructorError(_A,_A,_m%exc,node.start_mark)
try:
- if hasattr(base64,_s):return base64.decodebytes(value)
+ if hasattr(base64,_n):return base64.decodebytes(value)
else:return base64.decodestring(value)
- except binascii.Error as exc:raise ConstructorError(_A,_A,_a%exc,node.start_mark)
+ except binascii.Error as exc:raise ConstructorError(_A,_A,_Y%exc,node.start_mark)
def construct_python_long(self,node):
val=self.construct_yaml_int(node)
if PY3:return val
@@ -365,13 +357,13 @@ class Constructor(SafeConstructor):
def construct_python_complex(self,node):return complex(self.construct_scalar(node))
def construct_python_tuple(self,node):return tuple(self.construct_sequence(node))
def find_python_module(self,name,mark):
- if not name:raise ConstructorError(_e,mark,_AB,mark)
+ if not name:raise ConstructorError(_c,mark,_A3,mark)
try:__import__(name)
- except ImportError as exc:raise ConstructorError(_e,mark,_AC%(utf8(name),exc),mark)
+ except ImportError as exc:raise ConstructorError(_c,mark,_A4%(utf8(name),exc),mark)
return sys.modules[name]
def find_python_name(self,name,mark):
A='while constructing a Python object'
- if not name:raise ConstructorError(A,mark,_AB,mark)
+ if not name:raise ConstructorError(A,mark,_A3,mark)
if _H in name:
lname=name.split(_H);lmodule_name=lname;lobject_name=[]
while len(lmodule_name)>1:
@@ -380,7 +372,7 @@ class Constructor(SafeConstructor):
except ImportError:continue
else:module_name=builtins_module;lobject_name=[name]
try:__import__(module_name)
- except ImportError as exc:raise ConstructorError(A,mark,_AC%(utf8(module_name),exc),mark)
+ except ImportError as exc:raise ConstructorError(A,mark,_A4%(utf8(module_name),exc),mark)
module=sys.modules[module_name];object_name=_H.join(lobject_name);obj=module
while lobject_name:
if not hasattr(obj,lobject_name[0]):raise ConstructorError(A,mark,'cannot find %r in the module %r'%(utf8(object_name),module.__name__),mark)
@@ -388,31 +380,31 @@ class Constructor(SafeConstructor):
return obj
def construct_python_name(self,suffix,node):
value=self.construct_scalar(node)
- if value:raise ConstructorError('while constructing a Python name',node.start_mark,_AD%utf8(value),node.start_mark)
+ if value:raise ConstructorError('while constructing a Python name',node.start_mark,_A5%utf8(value),node.start_mark)
return self.find_python_name(suffix,node.start_mark)
def construct_python_module(self,suffix,node):
value=self.construct_scalar(node)
- if value:raise ConstructorError(_e,node.start_mark,_AD%utf8(value),node.start_mark)
+ if value:raise ConstructorError(_c,node.start_mark,_A5%utf8(value),node.start_mark)
return self.find_python_module(suffix,node.start_mark)
def make_python_instance(self,suffix,node,args=_A,kwds=_A,newobj=_B):
if not args:args=[]
if not kwds:kwds={}
cls=self.find_python_name(suffix,node.start_mark)
if PY3:
- if newobj and isinstance(cls,type):return cls.__new__(cls,*args,**kwds)
- else:return cls(*args,**kwds)
+ if newobj and isinstance(cls,type):return cls.__new__(cls,*(args),**kwds)
+ else:return cls(*(args),**kwds)
elif newobj and isinstance(cls,type(classobj))and not args and not kwds:instance=classobj();instance.__class__=cls;return instance
- elif newobj and isinstance(cls,type):return cls.__new__(cls,*args,**kwds)
- else:return cls(*args,**kwds)
+ elif newobj and isinstance(cls,type):return cls.__new__(cls,*(args),**kwds)
+ else:return cls(*(args),**kwds)
def set_python_instance_state(self,instance,state):
- if hasattr(instance,_V):instance.__setstate__(state)
+ if hasattr(instance,_T):instance.__setstate__(state)
else:
slotstate={}
if isinstance(state,tuple)and len(state)==2:state,slotstate=state
if hasattr(instance,'__dict__'):instance.__dict__.update(state)
elif state:slotstate.update(state)
for (key,value) in slotstate.items():setattr(instance,key,value)
- def construct_python_object(self,suffix,node):instance=self.make_python_instance(suffix,node,newobj=_C);self.recursive_objects[node]=instance;yield instance;deep=hasattr(instance,_V);state=self.construct_mapping(node,deep=deep);self.set_python_instance_state(instance,state)
+ def construct_python_object(self,suffix,node):instance=self.make_python_instance(suffix,node,newobj=_C);self.recursive_objects[node]=instance;yield instance;deep=hasattr(instance,_T);state=self.construct_mapping(node,deep=deep);self.set_python_instance_state(instance,state)
def construct_python_object_apply(self,suffix,node,newobj=_B):
if isinstance(node,SequenceNode):args=self.construct_sequence(node,deep=_C);kwds={};state={};listitems=[];dictitems={}
else:value=self.construct_mapping(node,deep=_C);args=value.get('args',[]);kwds=value.get('kwds',{});state=value.get('state',{});listitems=value.get('listitems',[]);dictitems=value.get('dictitems',{})
@@ -443,7 +435,7 @@ Constructor.add_multi_constructor('tag:yaml.org,2002:python/object/new:',Constru
class RoundTripConstructor(SafeConstructor):
def construct_scalar(self,node):
A='\x07'
- if not isinstance(node,ScalarNode):raise ConstructorError(_A,_A,_g%node.id,node.start_mark)
+ if not isinstance(node,ScalarNode):raise ConstructorError(_A,_A,_d%node.id,node.start_mark)
if node.style=='|'and isinstance(node.value,text_type):
lss=LiteralScalarString(node.value,anchor=node.anchor)
if node.comment and node.comment[1]:lss.comment=node.comment[1][0]
@@ -469,8 +461,8 @@ class RoundTripConstructor(SafeConstructor):
except ValueError:underscore=_A
except IndexError:underscore=_A
value_s=value_su.replace(_D,'');sign=+1
- if value_s[0]==_J:sign=-1
- if value_s[0]in _M:value_s=value_s[1:]
+ if value_s[0]=='-':sign=-1
+ if value_s[0]in _L:value_s=value_s[1:]
if value_s==_F:return 0
elif value_s.startswith('0b'):
if self.resolver.processing_version>(1,1)and value_s[2]==_F:width=len(value_s[2:])
@@ -500,7 +492,6 @@ class RoundTripConstructor(SafeConstructor):
elif node.anchor:return ScalarInt(sign*int(value_s),width=_A,anchor=node.anchor)
else:return sign*int(value_s)
def construct_yaml_float(self,node):
- A='E'
def leading_zeros(v):
lead0=0;idx=0
while idx<len(v)and v[idx]in'0.':
@@ -508,32 +499,32 @@ class RoundTripConstructor(SafeConstructor):
idx+=1
return lead0
m_sign=_B;value_so=to_str(self.construct_scalar(node));value_s=value_so.replace(_D,'').lower();sign=+1
- if value_s[0]==_J:sign=-1
- if value_s[0]in _M:m_sign=value_s[0];value_s=value_s[1:]
- if value_s==_p:return sign*self.inf_value
- if value_s==_q:return self.nan_value
+ if value_s[0]=='-':sign=-1
+ if value_s[0]in _L:m_sign=value_s[0];value_s=value_s[1:]
+ if value_s=='.inf':return sign*self.inf_value
+ if value_s=='.nan':return self.nan_value
if self.resolver.processing_version!=(1,2)and _G in value_s:
digits=[float(part)for part in value_s.split(_G)];digits.reverse();base=1;value=0.0
for digit in digits:value+=digit*base;base*=60
return sign*value
- if _N in value_s:
- try:mantissa,exponent=value_so.split(_N);exp=_N
- except ValueError:mantissa,exponent=value_so.split(A);exp=A
+ if'e'in value_s:
+ try:mantissa,exponent=value_so.split('e');exp='e'
+ except ValueError:mantissa,exponent=value_so.split('E');exp='E'
if self.resolver.processing_version!=(1,2):
if _H not in mantissa:warnings.warn(MantissaNoDotYAML1_1Warning(node,value_so))
lead0=leading_zeros(mantissa);width=len(mantissa);prec=mantissa.find(_H)
if m_sign:width-=1
- e_width=len(exponent);e_sign=exponent[0]in _M;return ScalarFloat(sign*float(value_s),width=width,prec=prec,m_sign=m_sign,m_lead0=lead0,exp=exp,e_width=e_width,e_sign=e_sign,anchor=node.anchor)
+ e_width=len(exponent);e_sign=exponent[0]in _L;return ScalarFloat(sign*float(value_s),width=width,prec=prec,m_sign=m_sign,m_lead0=lead0,exp=exp,e_width=e_width,e_sign=e_sign,anchor=node.anchor)
width=len(value_so);prec=value_so.index(_H);lead0=leading_zeros(value_so);return ScalarFloat(sign*float(value_s),width=width,prec=prec,m_sign=m_sign,m_lead0=lead0,anchor=node.anchor)
def construct_yaml_str(self,node):
value=self.construct_scalar(node)
if isinstance(value,ScalarString):return value
if PY3:return value
- try:return value.encode(_S)
+ try:return value.encode(_Q)
except AttributeError:return value
except UnicodeEncodeError:return value
def construct_rt_sequence(self,node,seqtyp,deep=_B):
- if not isinstance(node,SequenceNode):raise ConstructorError(_A,_A,_h%node.id,node.start_mark)
+ if not isinstance(node,SequenceNode):raise ConstructorError(_A,_A,_e%node.id,node.start_mark)
ret_val=[]
if node.comment:
seqtyp._yaml_add_comment(node.comment[:2])
@@ -553,25 +544,25 @@ class RoundTripConstructor(SafeConstructor):
merge_map_list=[];index=0
while index<len(node.value):
key_node,value_node=node.value[index]
- if key_node.tag==_k:
+ if key_node.tag==_h:
if merge_map_list:
if self.allow_duplicate_keys:del node.value[index];index+=1;continue
- args=[_E,node.start_mark,_Y.format(key_node.value),key_node.start_mark,_l,_m]
- if self.allow_duplicate_keys is _A:warnings.warn(DuplicateKeyFutureWarning(*args))
- else:raise DuplicateKeyError(*args)
+ args=[_E,node.start_mark,_W.format(key_node.value),key_node.start_mark,_i,_j]
+ if self.allow_duplicate_keys is _A:warnings.warn(DuplicateKeyFutureWarning(*(args)))
+ else:raise DuplicateKeyError(*(args))
del node.value[index]
if isinstance(value_node,MappingNode):merge_map_list.append((index,constructed(value_node)))
elif isinstance(value_node,SequenceNode):
for subnode in value_node.value:
- if not isinstance(subnode,MappingNode):raise ConstructorError(_E,node.start_mark,_n%subnode.id,subnode.start_mark)
+ if not isinstance(subnode,MappingNode):raise ConstructorError(_E,node.start_mark,_k%subnode.id,subnode.start_mark)
merge_map_list.append((index,constructed(subnode)))
- else:raise ConstructorError(_E,node.start_mark,_o%value_node.id,value_node.start_mark)
- elif key_node.tag==_Z:key_node.tag=_R;index+=1
+ else:raise ConstructorError(_E,node.start_mark,_l%value_node.id,value_node.start_mark)
+ elif key_node.tag==_X:key_node.tag=_P;index+=1
else:index+=1
return merge_map_list
def _sentinel(self):0
def construct_mapping(self,node,maptyp,deep=_B):
- if not isinstance(node,MappingNode):raise ConstructorError(_A,_A,_P%node.id,node.start_mark)
+ if not isinstance(node,MappingNode):raise ConstructorError(_A,_A,_N%node.id,node.start_mark)
merge_map=self.flatten_mapping(node)
if node.comment:
maptyp._yaml_add_comment(node.comment[:2])
@@ -595,8 +586,8 @@ class RoundTripConstructor(SafeConstructor):
key=key_m
if PY2:
try:hash(key)
- except TypeError as exc:raise ConstructorError(_E,node.start_mark,_W%exc,key_node.start_mark)
- elif not isinstance(key,Hashable):raise ConstructorError(_E,node.start_mark,_X,key_node.start_mark)
+ except TypeError as exc:raise ConstructorError(_E,node.start_mark,_U%exc,key_node.start_mark)
+ elif not isinstance(key,Hashable):raise ConstructorError(_E,node.start_mark,_V,key_node.start_mark)
value=self.construct_object(value_node,deep=deep)
if self.check_mapping_key(node,key_node,maptyp,key,value):
if key_node.comment and len(key_node.comment)>4 and key_node.comment[4]:
@@ -608,7 +599,7 @@ class RoundTripConstructor(SafeConstructor):
maptyp._yaml_set_kv_line_col(key,[key_node.start_mark.line,key_node.start_mark.column,value_node.start_mark.line,value_node.start_mark.column]);maptyp[key]=value;last_key,last_value=key,value
if merge_map:maptyp.add_yaml_merge(merge_map)
def construct_setting(self,node,typ,deep=_B):
- if not isinstance(node,MappingNode):raise ConstructorError(_A,_A,_P%node.id,node.start_mark)
+ if not isinstance(node,MappingNode):raise ConstructorError(_A,_A,_N%node.id,node.start_mark)
if node.comment:
typ._yaml_add_comment(node.comment[:2])
if len(node.comment)>2:typ.yaml_end_comment_extend(node.comment[2],clear=_C)
@@ -621,8 +612,8 @@ class RoundTripConstructor(SafeConstructor):
if isinstance(key,list):key=tuple(key)
if PY2:
try:hash(key)
- except TypeError as exc:raise ConstructorError(_E,node.start_mark,_W%exc,key_node.start_mark)
- elif not isinstance(key,Hashable):raise ConstructorError(_E,node.start_mark,_X,key_node.start_mark)
+ except TypeError as exc:raise ConstructorError(_E,node.start_mark,_U%exc,key_node.start_mark)
+ elif not isinstance(key,Hashable):raise ConstructorError(_E,node.start_mark,_V,key_node.start_mark)
value=self.construct_object(value_node,deep=deep);self.check_set_key(node,key_node,typ,key)
if key_node.comment:typ._yaml_add_comment(key_node.comment,key=key)
if value_node.comment:typ._yaml_add_comment(value_node.comment,value=key)
@@ -638,7 +629,7 @@ class RoundTripConstructor(SafeConstructor):
elif node.flow_style is _B:data.fa.set_block_style()
def construct_yaml_object(self,node,cls):
data=cls.__new__(cls);yield data
- if hasattr(data,_V):state=SafeConstructor.construct_mapping(self,node,deep=_C);data.__setstate__(state)
+ if hasattr(data,_T):state=SafeConstructor.construct_mapping(self,node,deep=_C);data.__setstate__(state)
else:state=SafeConstructor.construct_mapping(self,node);data.__dict__.update(state)
def construct_yaml_omap(self,node):
omap=CommentedOrderedMap();omap._yaml_set_line_col(node.start_mark.line,node.start_mark.column)
@@ -648,10 +639,10 @@ class RoundTripConstructor(SafeConstructor):
if node.comment:
omap._yaml_add_comment(node.comment[:2])
if len(node.comment)>2:omap.yaml_end_comment_extend(node.comment[2],clear=_C)
- if not isinstance(node,SequenceNode):raise ConstructorError(_L,node.start_mark,_b%node.id,node.start_mark)
+ if not isinstance(node,SequenceNode):raise ConstructorError(_K,node.start_mark,_Z%node.id,node.start_mark)
for subnode in node.value:
- if not isinstance(subnode,MappingNode):raise ConstructorError(_L,node.start_mark,_c%subnode.id,subnode.start_mark)
- if len(subnode.value)!=1:raise ConstructorError(_L,node.start_mark,_d%len(subnode.value),subnode.start_mark)
+ if not isinstance(subnode,MappingNode):raise ConstructorError(_K,node.start_mark,_a%subnode.id,subnode.start_mark)
+ if len(subnode.value)!=1:raise ConstructorError(_K,node.start_mark,_b%len(subnode.value),subnode.start_mark)
key_node,value_node=subnode.value[0];key=self.construct_object(key_node);assert key not in omap;value=self.construct_object(value_node)
if key_node.comment:omap._yaml_add_comment(key_node.comment,key=key)
if subnode.comment:omap._yaml_add_comment(subnode.comment,key=key)
@@ -679,30 +670,30 @@ class RoundTripConstructor(SafeConstructor):
if node.anchor:data3.yaml_set_anchor(node.anchor)
data3.extend(self.construct_sequence(node));return
except:pass
- raise ConstructorError(_A,_A,_z%utf8(node.tag),node.start_mark)
+ raise ConstructorError(_A,_A,_r%utf8(node.tag),node.start_mark)
def construct_yaml_timestamp(self,node,values=_A):
B='t';A='tz'
try:match=self.timestamp_regexp.match(node.value)
except TypeError:match=_A
- if match is _A:raise ConstructorError(_A,_A,_t.format(node.value),node.start_mark)
+ if match is _A:raise ConstructorError(_A,_A,_o.format(node.value),node.start_mark)
values=match.groupdict()
- if not values[_T]:return SafeConstructor.construct_yaml_timestamp(self,node,values)
- for part in [B,_K,_U,_O]:
+ if not values[_R]:return SafeConstructor.construct_yaml_timestamp(self,node,values)
+ for part in [B,_J,_S,_M]:
if values[part]:break
else:return SafeConstructor.construct_yaml_timestamp(self,node,values)
- year=int(values[_u]);month=int(values[_v]);day=int(values[_w]);hour=int(values[_T]);minute=int(values[_x]);second=int(values[_y]);fraction=0
+ year=int(values['year']);month=int(values['month']);day=int(values['day']);hour=int(values[_R]);minute=int(values[_p]);second=int(values[_q]);fraction=0
if values[_I]:
fraction_s=values[_I][:6]
while len(fraction_s)<6:fraction_s+=_F
fraction=int(fraction_s)
if len(values[_I])>6 and int(values[_I][6])>4:fraction+=1
delta=_A
- if values[_K]:
- tz_hour=int(values[_U]);minutes=values[_O];tz_minute=int(minutes)if minutes else 0;delta=datetime.timedelta(hours=tz_hour,minutes=tz_minute)
- if values[_K]==_J:delta=-delta
+ if values[_J]:
+ tz_hour=int(values[_S]);minutes=values[_M];tz_minute=int(minutes)if minutes else 0;delta=datetime.timedelta(hours=tz_hour,minutes=tz_minute)
+ if values[_J]=='-':delta=-delta
if delta:
- dt=datetime.datetime(year,month,day,hour,minute);dt-=delta;data=TimeStamp(dt.year,dt.month,dt.day,dt.hour,dt.minute,second,fraction);data._yaml['delta']=delta;tz=values[_K]+values[_U]
- if values[_O]:tz+=_G+values[_O]
+ dt=datetime.datetime(year,month,day,hour,minute);dt-=delta;data=TimeStamp(dt.year,dt.month,dt.day,dt.hour,dt.minute,second,fraction);data._yaml['delta']=delta;tz=values[_J]+values[_S]
+ if values[_M]:tz+=_G+values[_M]
data._yaml[A]=tz
else:
data=TimeStamp(year,month,day,hour,minute,second,fraction)
@@ -713,16 +704,16 @@ class RoundTripConstructor(SafeConstructor):
b=SafeConstructor.construct_yaml_bool(self,node)
if node.anchor:return ScalarBoolean(b,anchor=node.anchor)
return b
-RoundTripConstructor.add_constructor(_A0,RoundTripConstructor.construct_yaml_null)
-RoundTripConstructor.add_constructor(_A1,RoundTripConstructor.construct_yaml_bool)
-RoundTripConstructor.add_constructor(_A2,RoundTripConstructor.construct_yaml_int)
-RoundTripConstructor.add_constructor(_A3,RoundTripConstructor.construct_yaml_float)
-RoundTripConstructor.add_constructor(_A4,RoundTripConstructor.construct_yaml_binary)
-RoundTripConstructor.add_constructor(_A5,RoundTripConstructor.construct_yaml_timestamp)
-RoundTripConstructor.add_constructor(_A6,RoundTripConstructor.construct_yaml_omap)
-RoundTripConstructor.add_constructor(_A7,RoundTripConstructor.construct_yaml_pairs)
-RoundTripConstructor.add_constructor(_A8,RoundTripConstructor.construct_yaml_set)
-RoundTripConstructor.add_constructor(_R,RoundTripConstructor.construct_yaml_str)
-RoundTripConstructor.add_constructor(_A9,RoundTripConstructor.construct_yaml_seq)
-RoundTripConstructor.add_constructor(_AA,RoundTripConstructor.construct_yaml_map)
+RoundTripConstructor.add_constructor(_s,RoundTripConstructor.construct_yaml_null)
+RoundTripConstructor.add_constructor(_t,RoundTripConstructor.construct_yaml_bool)
+RoundTripConstructor.add_constructor(_u,RoundTripConstructor.construct_yaml_int)
+RoundTripConstructor.add_constructor(_v,RoundTripConstructor.construct_yaml_float)
+RoundTripConstructor.add_constructor(_w,RoundTripConstructor.construct_yaml_binary)
+RoundTripConstructor.add_constructor(_x,RoundTripConstructor.construct_yaml_timestamp)
+RoundTripConstructor.add_constructor(_y,RoundTripConstructor.construct_yaml_omap)
+RoundTripConstructor.add_constructor(_z,RoundTripConstructor.construct_yaml_pairs)
+RoundTripConstructor.add_constructor(_A0,RoundTripConstructor.construct_yaml_set)
+RoundTripConstructor.add_constructor(_P,RoundTripConstructor.construct_yaml_str)
+RoundTripConstructor.add_constructor(_A1,RoundTripConstructor.construct_yaml_seq)
+RoundTripConstructor.add_constructor(_A2,RoundTripConstructor.construct_yaml_map)
RoundTripConstructor.add_constructor(_A,RoundTripConstructor.construct_undefined)
\ No newline at end of file
diff --git a/dynaconf/vendor/ruamel/yaml/emitter.py b/dynaconf/vendor/ruamel/yaml/emitter.py
index 036530a..c411fcf 100644
--- a/dynaconf/vendor/ruamel/yaml/emitter.py
+++ b/dynaconf/vendor/ruamel/yaml/emitter.py
@@ -1,21 +1,14 @@
from __future__ import absolute_import,print_function
-_a='\x07'
-_Z='\ufeff'
-_Y='\ue000'
-_X='\ud7ff'
-_W='\x85'
-_V='%%%02X'
-_U='version'
-_T="-;/?:@&=+$,_.~*'()[]"
-_S='---'
-_R=' \n\x85\u2028\u2029'
-_Q='\xa0'
-_P='a'
-_O='0'
-_N=','
-_M='...'
-_L='\\'
-_K='['
+_T='\ufeff'
+_S='\ue000'
+_R='\ud7ff'
+_Q='%%%02X'
+_P='version'
+_O="-;/?:@&=+$,_.~*'()[]"
+_N=' \n\x85\u2028\u2029'
+_M='\xa0'
+_L='...'
+_K='\\'
_J="'"
_I='?'
_H='"'
@@ -113,7 +106,7 @@ class Emitter:
def expect_first_document_start(self):return self.expect_document_start(first=_A)
def expect_document_start(self,first=_C):
if isinstance(self.event,DocumentStartEvent):
- if(self.event.version or self.event.tags)and self.open_ended:self.write_indicator(_M,_A);self.write_indent()
+ if(self.event.version or self.event.tags)and self.open_ended:self.write_indicator(_L,_A);self.write_indent()
if self.event.version:version_text=self.prepare_version(self.event.version);self.write_version_directive(version_text)
self.tag_prefixes=self.DEFAULT_TAG_PREFIXES.copy()
if self.event.tags:
@@ -121,17 +114,17 @@ class Emitter:
for handle in handles:prefix=self.event.tags[handle];self.tag_prefixes[prefix]=handle;handle_text=self.prepare_tag_handle(handle);prefix_text=self.prepare_tag_prefix(prefix);self.write_tag_directive(handle_text,prefix_text)
implicit=first and not self.event.explicit and not self.canonical and not self.event.version and not self.event.tags and not self.check_empty_document()
if not implicit:
- self.write_indent();self.write_indicator(_S,_A)
+ self.write_indent();self.write_indicator('---',_A)
if self.canonical:self.write_indent()
self.state=self.expect_document_root
elif isinstance(self.event,StreamEndEvent):
- if self.open_ended:self.write_indicator(_M,_A);self.write_indent()
+ if self.open_ended:self.write_indicator(_L,_A);self.write_indent()
self.write_stream_end();self.state=self.expect_nothing
else:raise EmitterError('expected DocumentStartEvent, but got %s'%(self.event,))
def expect_document_end(self):
if isinstance(self.event,DocumentEndEvent):
self.write_indent()
- if self.event.explicit:self.write_indicator(_M,_A);self.write_indent()
+ if self.event.explicit:self.write_indicator(_L,_A);self.write_indent()
self.flush_stream();self.state=self.expect_document_start
else:raise EmitterError('expected DocumentEndEvent, but got %s'%(self.event,))
def expect_document_root(self):self.states.append(self.expect_document_end);self.expect_node(root=_A)
@@ -161,10 +154,10 @@ class Emitter:
if self.event.anchor is _B:raise EmitterError('anchor is not specified for alias')
self.process_anchor('*');self.state=self.states.pop()
def expect_scalar(self):self.increase_indent(flow=_A);self.process_scalar();self.indent=self.indents.pop();self.state=self.states.pop()
- def expect_flow_sequence(self):ind=self.indents.seq_flow_align(self.best_sequence_indent,self.column);self.write_indicator(_D*ind+_K,_A,whitespace=_A);self.increase_indent(flow=_A,sequence=_A);self.flow_context.append(_K);self.state=self.expect_first_flow_sequence_item
+ def expect_flow_sequence(self):ind=self.indents.seq_flow_align(self.best_sequence_indent,self.column);self.write_indicator(_D*ind+'[',_A,whitespace=_A);self.increase_indent(flow=_A,sequence=_A);self.flow_context.append('[');self.state=self.expect_first_flow_sequence_item
def expect_first_flow_sequence_item(self):
if isinstance(self.event,SequenceEndEvent):
- self.indent=self.indents.pop();popped=self.flow_context.pop();assert popped==_K;self.write_indicator(']',_C)
+ self.indent=self.indents.pop();popped=self.flow_context.pop();assert popped=='[';self.write_indicator(']',_C)
if self.event.comment and self.event.comment[0]:self.write_post_comment(self.event)
elif self.flow_level==0:self.write_line_break()
self.state=self.states.pop()
@@ -173,19 +166,19 @@ class Emitter:
self.states.append(self.expect_flow_sequence_item);self.expect_node(sequence=_A)
def expect_flow_sequence_item(self):
if isinstance(self.event,SequenceEndEvent):
- self.indent=self.indents.pop();popped=self.flow_context.pop();assert popped==_K
- if self.canonical:self.write_indicator(_N,_C);self.write_indent()
+ self.indent=self.indents.pop();popped=self.flow_context.pop();assert popped=='['
+ if self.canonical:self.write_indicator(',',_C);self.write_indent()
self.write_indicator(']',_C)
if self.event.comment and self.event.comment[0]:self.write_post_comment(self.event)
else:self.no_newline=_C
self.state=self.states.pop()
else:
- self.write_indicator(_N,_C)
+ self.write_indicator(',',_C)
if self.canonical or self.column>self.best_width:self.write_indent()
self.states.append(self.expect_flow_sequence_item);self.expect_node(sequence=_A)
def expect_flow_mapping(self,single=_C):
ind=self.indents.seq_flow_align(self.best_sequence_indent,self.column);map_init='{'
- if single and self.flow_level and self.flow_context[-1]==_K and not self.canonical and not self.brace_single_entry_mapping_in_flow_sequence:map_init=''
+ if single and self.flow_level and self.flow_context[-1]=='['and not self.canonical and not self.brace_single_entry_mapping_in_flow_sequence:map_init=''
self.write_indicator(_D*ind+map_init,_A,whitespace=_A);self.flow_context.append(map_init);self.increase_indent(flow=_A,sequence=_C);self.state=self.expect_first_flow_mapping_key
def expect_first_flow_mapping_key(self):
if isinstance(self.event,MappingEndEvent):
@@ -200,13 +193,13 @@ class Emitter:
def expect_flow_mapping_key(self):
if isinstance(self.event,MappingEndEvent):
self.indent=self.indents.pop();popped=self.flow_context.pop();assert popped in['{','']
- if self.canonical:self.write_indicator(_N,_C);self.write_indent()
+ if self.canonical:self.write_indicator(',',_C);self.write_indent()
if popped!='':self.write_indicator('}',_C)
if self.event.comment and self.event.comment[0]:self.write_post_comment(self.event)
else:self.no_newline=_C
self.state=self.states.pop()
else:
- self.write_indicator(_N,_C)
+ self.write_indicator(',',_C)
if self.canonical or self.column>self.best_width:self.write_indent()
if not self.canonical and self.check_simple_key():self.states.append(self.expect_flow_mapping_simple_value);self.expect_node(mapping=_A,simple_key=_A)
else:self.write_indicator(_I,_A);self.states.append(self.expect_flow_mapping_value);self.expect_node(mapping=_A)
@@ -324,23 +317,23 @@ class Emitter:
if not handle:raise EmitterError('tag handle must not be empty')
if handle[0]!=_G or handle[-1]!=_G:raise EmitterError("tag handle must start and end with '!': %r"%utf8(handle))
for ch in handle[1:-1]:
- if not(_O<=ch<='9'or'A'<=ch<='Z'or _P<=ch<='z'or ch in'-_'):raise EmitterError('invalid character %r in the tag handle: %r'%(utf8(ch),utf8(handle)))
+ if not('0'<=ch<='9'or'A'<=ch<='Z'or'a'<=ch<='z'or ch in'-_'):raise EmitterError('invalid character %r in the tag handle: %r'%(utf8(ch),utf8(handle)))
return handle
def prepare_tag_prefix(self,prefix):
if not prefix:raise EmitterError('tag prefix must not be empty')
chunks=[];start=end=0
if prefix[0]==_G:end=1
- ch_set=_T
+ ch_set=_O
if self.dumper:
- version=getattr(self.dumper,_U,(1,2))
+ version=getattr(self.dumper,_P,(1,2))
if version is _B or version>=(1,2):ch_set+='#'
while end<len(prefix):
ch=prefix[end]
- if _O<=ch<='9'or'A'<=ch<='Z'or _P<=ch<='z'or ch in ch_set:end+=1
+ if'0'<=ch<='9'or'A'<=ch<='Z'or'a'<=ch<='z'or ch in ch_set:end+=1
else:
if start<end:chunks.append(prefix[start:end])
start=end=end+1;data=utf8(ch)
- for ch in data:chunks.append(_V%ord(ch))
+ for ch in data:chunks.append(_Q%ord(ch))
if start<end:chunks.append(prefix[start:end])
return ''.join(chunks)
def prepare_tag(self,tag):
@@ -349,17 +342,17 @@ class Emitter:
handle=_B;suffix=tag;prefixes=sorted(self.tag_prefixes.keys())
for prefix in prefixes:
if tag.startswith(prefix)and(prefix==_G or len(prefix)<len(tag)):handle=self.tag_prefixes[prefix];suffix=tag[len(prefix):]
- chunks=[];start=end=0;ch_set=_T
+ chunks=[];start=end=0;ch_set=_O
if self.dumper:
- version=getattr(self.dumper,_U,(1,2))
+ version=getattr(self.dumper,_P,(1,2))
if version is _B or version>=(1,2):ch_set+='#'
while end<len(suffix):
ch=suffix[end]
- if _O<=ch<='9'or'A'<=ch<='Z'or _P<=ch<='z'or ch in ch_set or ch==_G and handle!=_G:end+=1
+ if'0'<=ch<='9'or'A'<=ch<='Z'or'a'<=ch<='z'or ch in ch_set or ch==_G and handle!=_G:end+=1
else:
if start<end:chunks.append(suffix[start:end])
start=end=end+1;data=utf8(ch)
- for ch in data:chunks.append(_V%ord(ch))
+ for ch in data:chunks.append(_Q%ord(ch))
if start<end:chunks.append(suffix[start:end])
suffix_text=''.join(chunks)
if handle:return'%s%s'%(handle,suffix_text)
@@ -373,7 +366,7 @@ class Emitter:
A='\x00 \t\r\n\x85\u2028\u2029'
if not scalar:return ScalarAnalysis(scalar=scalar,empty=_A,multiline=_C,allow_flow_plain=_C,allow_block_plain=_A,allow_single_quoted=_A,allow_double_quoted=_A,allow_block=_C)
block_indicators=_C;flow_indicators=_C;line_breaks=_C;special_characters=_C;leading_space=_C;leading_break=_C;trailing_space=_C;trailing_break=_C;break_space=_C;space_break=_C
- if scalar.startswith(_S)or scalar.startswith(_M):block_indicators=_A;flow_indicators=_A
+ if scalar.startswith('---')or scalar.startswith(_L):block_indicators=_A;flow_indicators=_A
preceeded_by_whitespace=_A;followed_by_whitespace=len(scalar)==1 or scalar[1]in A;previous_space=_C;previous_break=_C;index=0
while index<len(scalar):
ch=scalar[index]
@@ -392,7 +385,7 @@ class Emitter:
if ch=='#'and preceeded_by_whitespace:flow_indicators=_A;block_indicators=_A
if ch in _F:line_breaks=_A
if not(ch==_E or _D<=ch<='~'):
- if(ch==_W or _Q<=ch<=_X or _Y<=ch<='�'or self.unicode_supplementary and'𐀀'<=ch<='\U0010ffff')and ch!=_Z:
+ if(ch=='\x85'or _M<=ch<=_R or _S<=ch<='�'or self.unicode_supplementary and'𐀀'<=ch<='\U0010ffff')and ch!=_T:
if not self.allow_unicode:special_characters=_A
else:special_characters=_A
if ch==_D:
@@ -422,7 +415,7 @@ class Emitter:
def flush_stream(self):
if hasattr(self.stream,'flush'):self.stream.flush()
def write_stream_start(self):
- if self.encoding and self.encoding.startswith('utf-16'):self.stream.write(_Z.encode(self.encoding))
+ if self.encoding and self.encoding.startswith('utf-16'):self.stream.write(_T.encode(self.encoding))
def write_stream_end(self):self.flush_stream()
def write_indicator(self,indicator,need_whitespace,whitespace=_C,indention=_C):
if self.whitespace or not need_whitespace:data=indicator
@@ -476,7 +469,7 @@ class Emitter:
if br==_E:self.write_line_break()
else:self.write_line_break(br)
self.write_indent();start=end
- elif ch is _B or ch in _R or ch==_J:
+ elif ch is _B or ch in _N or ch==_J:
if start<end:
data=text[start:end];self.column+=len(data)
if bool(self.encoding):data=data.encode(self.encoding)
@@ -488,7 +481,7 @@ class Emitter:
if ch is not _B:spaces=ch==_D;breaks=ch in _F
end+=1
self.write_indicator(_J,_C)
- ESCAPE_REPLACEMENTS={'\x00':_O,_a:_P,'\x08':'b','\t':'t',_E:'n','\x0b':'v','\x0c':'f','\r':'r','\x1b':'e',_H:_H,_L:_L,_W:'N',_Q:'_','\u2028':'L','\u2029':'P'}
+ ESCAPE_REPLACEMENTS={'\x00':'0','\x07':'a','\x08':'b','\t':'t',_E:'n','\x0b':'v','\x0c':'f','\r':'r','\x1b':'e',_H:_H,_K:_K,'\x85':'N',_M:'_','\u2028':'L','\u2029':'P'}
def write_double_quoted(self,text,split=_A):
if self.root_context:
if self.requested_indent is not _B:
@@ -498,13 +491,13 @@ class Emitter:
while end<=len(text):
ch=_B
if end<len(text):ch=text[end]
- if ch is _B or ch in'"\\\x85\u2028\u2029\ufeff'or not(_D<=ch<='~'or self.allow_unicode and(_Q<=ch<=_X or _Y<=ch<='�')):
+ if ch is _B or ch in'"\\\x85\u2028\u2029\ufeff'or not(_D<=ch<='~'or self.allow_unicode and(_M<=ch<=_R or _S<=ch<='�')):
if start<end:
data=text[start:end];self.column+=len(data)
if bool(self.encoding):data=data.encode(self.encoding)
self.stream.write(data);start=end
if ch is not _B:
- if ch in self.ESCAPE_REPLACEMENTS:data=_L+self.ESCAPE_REPLACEMENTS[ch]
+ if ch in self.ESCAPE_REPLACEMENTS:data=_K+self.ESCAPE_REPLACEMENTS[ch]
elif ch<='ÿ':data='\\x%02X'%ord(ch)
elif ch<='\uffff':data='\\u%04X'%ord(ch)
else:data='\\U%08X'%ord(ch)
@@ -512,13 +505,13 @@ class Emitter:
if bool(self.encoding):data=data.encode(self.encoding)
self.stream.write(data);start=end+1
if 0<end<len(text)-1 and(ch==_D or start>=end)and self.column+(end-start)>self.best_width and split:
- data=text[start:end]+_L
+ data=text[start:end]+_K
if start<end:start=end
self.column+=len(data)
if bool(self.encoding):data=data.encode(self.encoding)
self.stream.write(data);self.write_indent();self.whitespace=_C;self.indention=_C
if text[start]==_D:
- data=_L;self.column+=len(data)
+ data=_K;self.column+=len(data)
if bool(self.encoding):data=data.encode(self.encoding)
self.stream.write(data)
end+=1
@@ -526,7 +519,7 @@ class Emitter:
def determine_block_hints(self,text):
indent=0;indicator='';hints=''
if text:
- if text[0]in _R:indent=self.best_sequence_indent;hints+=text_type(indent)
+ if text[0]in _N:indent=self.best_sequence_indent;hints+=text_type(indent)
elif self.root_context:
for end in ['\n---','\n...']:
pos=0
@@ -570,7 +563,7 @@ class Emitter:
data=text[start:end];self.column+=len(data)
if bool(self.encoding):data=data.encode(self.encoding)
self.stream.write(data)
- if ch==_a:
+ if ch=='\x07':
if end<len(text)-1 and not text[end+2].isspace():self.write_line_break();self.write_indent();end+=2
else:raise EmitterError('unexcpected fold indicator \\a before space')
if ch is _B:self.write_line_break()
@@ -635,7 +628,7 @@ class Emitter:
if br==_E:self.write_line_break()
else:self.write_line_break(br)
self.write_indent();self.whitespace=_C;self.indention=_C;start=end
- elif ch is _B or ch in _R:
+ elif ch is _B or ch in _N:
data=text[start:end];self.column+=len(data)
if self.encoding:data=data.encode(self.encoding)
try:self.stream.write(data)
diff --git a/dynaconf/vendor/ruamel/yaml/error.py b/dynaconf/vendor/ruamel/yaml/error.py
index 52652cc..cbcb115 100644
--- a/dynaconf/vendor/ruamel/yaml/error.py
+++ b/dynaconf/vendor/ruamel/yaml/error.py
@@ -1,9 +1,5 @@
from __future__ import absolute_import
-_I='once'
-_H=' in "%s", line %d, column %d'
-_G='line'
-_F='index'
-_E='name'
+_E=' in "%s", line %d, column %d'
_D='column'
_C=False
_B='\n'
@@ -13,9 +9,9 @@ from .compat import utf8
if _C:from typing import Any,Dict,Optional,List,Text
__all__=['FileMark','StringMark','CommentMark','YAMLError','MarkedYAMLError','ReusedAnchorWarning','UnsafeLoaderWarning','MarkedYAMLWarning','MarkedYAMLFutureWarning']
class StreamMark:
- __slots__=_E,_F,_G,_D
+ __slots__='name','index','line',_D
def __init__(A,name,index,line,column):A.name=name;A.index=index;A.line=line;A.column=column
- def __str__(A):B=_H%(A.name,A.line+1,A.column+1);return B
+ def __str__(A):B=_E%(A.name,A.line+1,A.column+1);return B
def __eq__(A,other):
B=other
if A.line!=B.line or A.column!=B.column:return _C
@@ -24,22 +20,22 @@ class StreamMark:
def __ne__(A,other):return not A.__eq__(other)
class FileMark(StreamMark):__slots__=()
class StringMark(StreamMark):
- __slots__=_E,_F,_G,_D,'buffer','pointer'
+ __slots__='name','index','line',_D,'buffer','pointer'
def __init__(A,name,index,line,column,buffer,pointer):StreamMark.__init__(A,name,index,line,column);A.buffer=buffer;A.pointer=pointer
def get_snippet(A,indent=4,max_length=75):
- L=' ';K=' ... ';J='\x00\r\n\x85\u2028\u2029';F=max_length;E=indent
+ J=' ... ';I='\x00\r\n\x85\u2028\u2029';F=max_length;E=indent
if A.buffer is _A:return _A
D='';B=A.pointer
- while B>0 and A.buffer[B-1]not in J:
+ while B>0 and A.buffer[B-1]not in I:
B-=1
- if A.pointer-B>F/2-1:D=K;B+=5;break
+ if A.pointer-B>F/2-1:D=J;B+=5;break
G='';C=A.pointer
- while C<len(A.buffer)and A.buffer[C]not in J:
+ while C<len(A.buffer)and A.buffer[C]not in I:
C+=1
- if C-A.pointer>F/2-1:G=K;C-=5;break
- I=utf8(A.buffer[B:C]);H='^';H='^ (line: {})'.format(A.line+1);return L*E+D+I+G+_B+L*(E+A.pointer-B+len(D))+H
+ if C-A.pointer>F/2-1:G=J;C-=5;break
+ K=utf8(A.buffer[B:C]);H='^';H='^ (line: {})'.format(A.line+1);return' '*E+D+K+G+_B+' '*(E+A.pointer-B+len(D))+H
def __str__(A):
- B=A.get_snippet();C=_H%(A.name,A.line+1,A.column+1)
+ B=A.get_snippet();C=_E%(A.name,A.line+1,A.column+1)
if B is not _A:C+=':\n'+B
return C
class CommentMark:
@@ -71,11 +67,11 @@ class MarkedYAMLWarning(YAMLWarning):
return _B.join(B)
class ReusedAnchorWarning(YAMLWarning):0
class UnsafeLoaderWarning(YAMLWarning):text="\nThe default 'Loader' for 'load(stream)' without further arguments can be unsafe.\nUse 'load(stream, Loader=ruamel.yaml.Loader)' explicitly if that is OK.\nAlternatively include the following in your code:\n\n import warnings\n warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)\n\nIn most other cases you should consider using 'safe_load(stream)'"
-warnings.simplefilter(_I,UnsafeLoaderWarning)
+warnings.simplefilter('once',UnsafeLoaderWarning)
class MantissaNoDotYAML1_1Warning(YAMLWarning):
def __init__(A,node,flt_str):A.node=node;A.flt=flt_str
def __str__(A):B=A.node.start_mark.line;C=A.node.start_mark.column;return '\nIn YAML 1.1 floating point values should have a dot (\'.\') in their mantissa.\nSee the Floating-Point Language-Independent Type for YAML™ Version 1.1 specification\n( http://yaml.org/type/float.html ). This dot is not required for JSON nor for YAML 1.2\n\nCorrect your float: "{}" on line: {}, column: {}\n\nor alternatively include the following in your code:\n\n import warnings\n warnings.simplefilter(\'ignore\', ruamel.yaml.error.MantissaNoDotYAML1_1Warning)\n\n'.format(A.flt,B,C)
-warnings.simplefilter(_I,MantissaNoDotYAML1_1Warning)
+warnings.simplefilter('once',MantissaNoDotYAML1_1Warning)
class YAMLFutureWarning(Warning):0
class MarkedYAMLFutureWarning(YAMLFutureWarning):
def __init__(A,context=_A,context_mark=_A,problem=_A,problem_mark=_A,note=_A,warn=_A):A.context=context;A.context_mark=context_mark;A.problem=problem;A.problem_mark=problem_mark;A.note=note;A.warn=warn
diff --git a/dynaconf/vendor/ruamel/yaml/events.py b/dynaconf/vendor/ruamel/yaml/events.py
index 8c1356e..545eae2 100644
--- a/dynaconf/vendor/ruamel/yaml/events.py
+++ b/dynaconf/vendor/ruamel/yaml/events.py
@@ -1,7 +1,5 @@
-_H='explicit'
-_G='style'
-_F='flow_style'
-_E='value'
+_F='explicit'
+_E='flow_style'
_D='anchor'
_C='implicit'
_B='tag'
@@ -15,14 +13,14 @@ class Event:
if B is CommentCheck:B=_A
A.comment=B
def __repr__(A):
- C=[B for B in[_D,_B,_C,_E,_F,_G]if hasattr(A,B)];B=', '.join(['%s=%r'%(B,getattr(A,B))for B in C])
+ C=[B for B in[_D,_B,_C,'value',_E,'style']if hasattr(A,B)];B=', '.join(['%s=%r'%(B,getattr(A,B))for B in C])
if A.comment not in[_A,CommentCheck]:B+=', comment={!r}'.format(A.comment)
return'%s(%s)'%(A.__class__.__name__,B)
class NodeEvent(Event):
__slots__=_D,
def __init__(A,anchor,start_mark=_A,end_mark=_A,comment=_A):Event.__init__(A,start_mark,end_mark,comment);A.anchor=anchor
class CollectionStartEvent(NodeEvent):
- __slots__=_B,_C,_F,'nr_items'
+ __slots__=_B,_C,_E,'nr_items'
def __init__(A,anchor,tag,implicit,start_mark=_A,end_mark=_A,flow_style=_A,comment=_A,nr_items=_A):NodeEvent.__init__(A,anchor,start_mark,end_mark,comment);A.tag=tag;A.implicit=implicit;A.flow_style=flow_style;A.nr_items=nr_items
class CollectionEndEvent(Event):__slots__=()
class StreamStartEvent(Event):
@@ -30,14 +28,14 @@ class StreamStartEvent(Event):
def __init__(A,start_mark=_A,end_mark=_A,encoding=_A,comment=_A):Event.__init__(A,start_mark,end_mark,comment);A.encoding=encoding
class StreamEndEvent(Event):__slots__=()
class DocumentStartEvent(Event):
- __slots__=_H,'version','tags'
+ __slots__=_F,'version','tags'
def __init__(A,start_mark=_A,end_mark=_A,explicit=_A,version=_A,tags=_A,comment=_A):Event.__init__(A,start_mark,end_mark,comment);A.explicit=explicit;A.version=version;A.tags=tags
class DocumentEndEvent(Event):
- __slots__=_H,
+ __slots__=_F,
def __init__(A,start_mark=_A,end_mark=_A,explicit=_A,comment=_A):Event.__init__(A,start_mark,end_mark,comment);A.explicit=explicit
class AliasEvent(NodeEvent):__slots__=()
class ScalarEvent(NodeEvent):
- __slots__=_B,_C,_E,_G
+ __slots__=_B,_C,'value','style'
def __init__(A,anchor,tag,implicit,value,start_mark=_A,end_mark=_A,style=_A,comment=_A):NodeEvent.__init__(A,anchor,start_mark,end_mark,comment);A.tag=tag;A.implicit=implicit;A.value=value;A.style=style
class SequenceStartEvent(CollectionStartEvent):__slots__=()
class SequenceEndEvent(CollectionEndEvent):__slots__=()
diff --git a/dynaconf/vendor/ruamel/yaml/main.py b/dynaconf/vendor/ruamel/yaml/main.py
index acd2e93..bab6902 100644
--- a/dynaconf/vendor/ruamel/yaml/main.py
+++ b/dynaconf/vendor/ruamel/yaml/main.py
@@ -1,13 +1,8 @@
from __future__ import absolute_import,unicode_literals,print_function
-_Q='_emitter'
-_P='_serializer'
-_O='write'
-_N='{}.dump(_all) takes two positional argument but at least three were given ({!r})'
-_M='read'
-_L='_stream'
-_K='typ'
-_J='utf-8'
-_I='base'
+_L='_emitter'
+_K='_serializer'
+_J='{}.dump(_all) takes two positional argument but at least three were given ({!r})'
+_I='_stream'
_H='{}.__init__() takes no positional argument but at least one was given ({!r})'
_G='yaml_tag'
_F='open'
@@ -45,13 +40,13 @@ class YAML:
self.Resolver=ruamel.yaml.resolver.VersionedResolver;self.allow_unicode=_C;self.Reader=_A;self.Representer=_A;self.Constructor=_A;self.Scanner=_A;self.Serializer=_A;self.default_flow_style=_A;typ_found=1;setup_rt=_B
if _E in self.typ:setup_rt=_C
elif'safe'in self.typ:self.Emitter=ruamel.yaml.emitter.Emitter if pure or CEmitter is _A else CEmitter;self.Representer=ruamel.yaml.representer.SafeRepresenter;self.Parser=ruamel.yaml.parser.Parser if pure or CParser is _A else CParser;self.Composer=ruamel.yaml.composer.Composer;self.Constructor=ruamel.yaml.constructor.SafeConstructor
- elif _I in self.typ:self.Emitter=ruamel.yaml.emitter.Emitter;self.Representer=ruamel.yaml.representer.BaseRepresenter;self.Parser=ruamel.yaml.parser.Parser if pure or CParser is _A else CParser;self.Composer=ruamel.yaml.composer.Composer;self.Constructor=ruamel.yaml.constructor.BaseConstructor
+ elif'base'in self.typ:self.Emitter=ruamel.yaml.emitter.Emitter;self.Representer=ruamel.yaml.representer.BaseRepresenter;self.Parser=ruamel.yaml.parser.Parser if pure or CParser is _A else CParser;self.Composer=ruamel.yaml.composer.Composer;self.Constructor=ruamel.yaml.constructor.BaseConstructor
elif'unsafe'in self.typ:self.Emitter=ruamel.yaml.emitter.Emitter if pure or CEmitter is _A else CEmitter;self.Representer=ruamel.yaml.representer.Representer;self.Parser=ruamel.yaml.parser.Parser if pure or CParser is _A else CParser;self.Composer=ruamel.yaml.composer.Composer;self.Constructor=ruamel.yaml.constructor.Constructor
else:setup_rt=_C;typ_found=0
if setup_rt:self.default_flow_style=_B;self.Emitter=ruamel.yaml.emitter.Emitter;self.Serializer=ruamel.yaml.serializer.Serializer;self.Representer=ruamel.yaml.representer.RoundTripRepresenter;self.Scanner=ruamel.yaml.scanner.RoundTripScanner;self.Parser=ruamel.yaml.parser.RoundTripParser;self.Composer=ruamel.yaml.composer.Composer;self.Constructor=ruamel.yaml.constructor.RoundTripConstructor
- del setup_rt;self.stream=_A;self.canonical=_A;self.old_indent=_A;self.width=_A;self.line_break=_A;self.map_indent=_A;self.sequence_indent=_A;self.sequence_dash_offset=0;self.compact_seq_seq=_A;self.compact_seq_map=_A;self.sort_base_mapping_type_on_output=_A;self.top_level_colon_align=_A;self.prefix_colon=_A;self.version=_A;self.preserve_quotes=_A;self.allow_duplicate_keys=_B;self.encoding=_J;self.explicit_start=_A;self.explicit_end=_A;self.tags=_A;self.default_style=_A;self.top_level_block_style_scalar_no_indent_error_1_1=_B;self.scalar_after_indicator=_A;self.brace_single_entry_mapping_in_flow_sequence=_B
+ del setup_rt;self.stream=_A;self.canonical=_A;self.old_indent=_A;self.width=_A;self.line_break=_A;self.map_indent=_A;self.sequence_indent=_A;self.sequence_dash_offset=0;self.compact_seq_seq=_A;self.compact_seq_map=_A;self.sort_base_mapping_type_on_output=_A;self.top_level_colon_align=_A;self.prefix_colon=_A;self.version=_A;self.preserve_quotes=_A;self.allow_duplicate_keys=_B;self.encoding='utf-8';self.explicit_start=_A;self.explicit_end=_A;self.tags=_A;self.default_style=_A;self.top_level_block_style_scalar_no_indent_error_1_1=_B;self.scalar_after_indicator=_A;self.brace_single_entry_mapping_in_flow_sequence=_B
for module in self.plug_ins:
- if getattr(module,_K,_A)in self.typ:typ_found+=1;module.init_typ(self);break
+ if getattr(module,'typ',_A)in self.typ:typ_found+=1;module.init_typ(self);break
if typ_found==0:raise NotImplementedError('typ "{}"not recognised (need to install plug-in?)'.format(self.typ))
@property
def reader(self):
@@ -66,7 +61,7 @@ class YAML:
attr=_D+sys._getframe().f_code.co_name
if not hasattr(self,attr):
if self.Parser is not CParser:setattr(self,attr,self.Parser(loader=self))
- elif getattr(self,_L,_A)is _A:return _A
+ elif getattr(self,_I,_A)is _A:return _A
else:setattr(self,attr,CParser(self._stream))
return getattr(self,attr)
@property
@@ -96,7 +91,7 @@ class YAML:
if self.compact_seq_seq is not _A:_emitter.compact_seq_seq=self.compact_seq_seq
if self.compact_seq_map is not _A:_emitter.compact_seq_map=self.compact_seq_map
else:
- if getattr(self,_L,_A)is _A:return _A
+ if getattr(self,_I,_A)is _A:return _A
return _A
return getattr(self,attr)
@property
@@ -113,7 +108,7 @@ class YAML:
setattr(self,attr,repres)
return getattr(self,attr)
def load(self,stream):
- if not hasattr(stream,_M)and hasattr(stream,_F):
+ if not hasattr(stream,'read')and hasattr(stream,_F):
with stream.open('rb')as fp:return self.load(fp)
constructor,parser=self.get_constructor_parser(stream)
try:return constructor.get_single_data()
@@ -125,7 +120,7 @@ class YAML:
except AttributeError:pass
def load_all(self,stream,_kw=enforce):
if _kw is not enforce:raise TypeError(_H.format(self.__class__.__name__,_kw))
- if not hasattr(stream,_M)and hasattr(stream,_F):
+ if not hasattr(stream,'read')and hasattr(stream,_F):
with stream.open('r')as fp:
for d in self.load_all(fp,_kw=enforce):yield d
return
@@ -166,14 +161,14 @@ class YAML:
return self.dump_all([data],stream,_kw,transform=transform)
def dump_all(self,documents,stream,_kw=enforce,transform=_A):
if self._context_manager:raise NotImplementedError
- if _kw is not enforce:raise TypeError(_N.format(self.__class__.__name__,_kw))
+ if _kw is not enforce:raise TypeError(_J.format(self.__class__.__name__,_kw))
self._output=stream;self._context_manager=YAMLContextManager(self,transform=transform)
for data in documents:self._context_manager.dump(data)
self._context_manager.teardown_output();self._output=_A;self._context_manager=_A
def Xdump_all(self,documents,stream,_kw=enforce,transform=_A):
- if not hasattr(stream,_O)and hasattr(stream,_F):
+ if not hasattr(stream,'write')and hasattr(stream,_F):
with stream.open('w')as fp:return self.dump_all(documents,fp,_kw,transform=transform)
- if _kw is not enforce:raise TypeError(_N.format(self.__class__.__name__,_kw))
+ if _kw is not enforce:raise TypeError(_J.format(self.__class__.__name__,_kw))
if self.top_level_colon_align is _C:tlca=max([len(str(x))for x in documents[0]])
else:tlca=self.top_level_colon_align
if transform is not _A:
@@ -190,7 +185,7 @@ class YAML:
finally:
try:self.emitter.dispose()
except AttributeError:raise
- delattr(self,_P);delattr(self,_Q)
+ delattr(self,_K);delattr(self,_L)
if transform:
val=stream.getvalue()
if self.encoding:val=val.decode(self.encoding)
@@ -207,7 +202,7 @@ class YAML:
self.Emitter=ruamel.yaml.emitter.Emitter;self.emitter.stream=stream;self.emitter.top_level_colon_align=tlca
if self.scalar_after_indicator is not _A:self.emitter.scalar_after_indicator=self.scalar_after_indicator
return self.serializer,self.representer,self.emitter
- rslvr=ruamel.yaml.resolver.BaseResolver if _I in self.typ else ruamel.yaml.resolver.Resolver
+ rslvr=ruamel.yaml.resolver.BaseResolver if'base'in self.typ else ruamel.yaml.resolver.Resolver
class XDumper(CEmitter,self.Representer,rslvr):
def __init__(selfx,stream,default_style=_A,default_flow_style=_A,canonical=_A,indent=_A,width=_A,allow_unicode=_A,line_break=_A,encoding=_A,explicit_start=_A,explicit_end=_A,version=_A,tags=_A,block_seq_indent=_A,top_level_colon_align=_A,prefix_colon=_A):CEmitter.__init__(selfx,stream,canonical=canonical,indent=indent,width=width,encoding=encoding,allow_unicode=allow_unicode,line_break=line_break,explicit_start=explicit_start,explicit_end=explicit_end,version=version,tags=tags);selfx._emitter=selfx._serializer=selfx._representer=selfx;self.Representer.__init__(selfx,default_style=default_style,default_flow_style=default_flow_style);rslvr.__init__(selfx)
self._stream=stream;dumper=XDumper(stream,default_style=self.default_style,default_flow_style=self.default_flow_style,canonical=self.canonical,indent=self.old_indent,width=self.width,allow_unicode=self.allow_unicode,line_break=self.line_break,explicit_start=self.explicit_start,explicit_end=self.explicit_end,version=self.version,tags=self.tags);self._emitter=self._serializer=dumper;return dumper,dumper,dumper
@@ -215,8 +210,8 @@ class YAML:
if _E in self.typ:from dynaconf.vendor.ruamel.yaml.comments import CommentedMap;return CommentedMap(**kw)
else:return dict(**kw)
def seq(self,*args):
- if _E in self.typ:from dynaconf.vendor.ruamel.yaml.comments import CommentedSeq;return CommentedSeq(*args)
- else:return list(*args)
+ if _E in self.typ:from dynaconf.vendor.ruamel.yaml.comments import CommentedSeq;return CommentedSeq(*(args))
+ else:return list(*(args))
def official_plug_ins(self):bd=os.path.dirname(__file__);gpbd=os.path.dirname(os.path.dirname(bd));res=[x.replace(gpbd,'')[1:-3]for x in glob.glob(bd+'/*/__plug_in__.py')];return res
def register_class(self,cls):
tag=getattr(cls,_G,'!'+cls.__name__)
@@ -241,7 +236,7 @@ class YAML:
except AttributeError:pass
def __enter__(self):self._context_manager=YAMLContextManager(self);return self
def __exit__(self,typ,value,traceback):
- if typ:nprint(_K,typ)
+ if typ:nprint('typ',typ)
self._context_manager.teardown_output();self._context_manager=_A
def _indent(self,mapping=_A,sequence=_A,offset=_A):
if mapping is not _A:self.map_indent=mapping
@@ -259,7 +254,7 @@ class YAML:
class YAMLContextManager:
def __init__(self,yaml,transform=_A):
self._yaml=yaml;self._output_inited=_B;self._output_path=_A;self._output=self._yaml._output;self._transform=transform
- if not hasattr(self._output,_O)and hasattr(self._output,_F):self._output_path=self._output;self._output=self._output_path.open('w')
+ if not hasattr(self._output,'write')and hasattr(self._output,_F):self._output_path=self._output;self._output=self._output_path.open('w')
if self._transform is not _A:
self._fstream=self._output
if self._yaml.encoding is _A:self._output=StringIO()
@@ -269,7 +264,7 @@ class YAMLContextManager:
else:return
try:self._yaml.emitter.dispose()
except AttributeError:raise
- try:delattr(self._yaml,_P);delattr(self._yaml,_Q)
+ try:delattr(self._yaml,_K);delattr(self._yaml,_L)
except AttributeError:raise
if self._transform:
val=self._output.getvalue()
@@ -352,7 +347,7 @@ def emit(events,stream=_A,Dumper=Dumper,canonical=_A,indent=_A,width=_A,allow_un
try:dumper._emitter.dispose()
except AttributeError:raise;dumper.dispose()
if getvalue is not _A:return getvalue()
-enc=_A if PY3 else _J
+enc=_A if PY3 else'utf-8'
def serialize_all(nodes,stream=_A,Dumper=Dumper,canonical=_A,indent=_A,width=_A,allow_unicode=_A,line_break=_A,encoding=enc,explicit_start=_A,explicit_end=_A,version=_A,tags=_A):
getvalue=_A
if stream is _A:
diff --git a/dynaconf/vendor/ruamel/yaml/nodes.py b/dynaconf/vendor/ruamel/yaml/nodes.py
index ffbd8cb..886be14 100644
--- a/dynaconf/vendor/ruamel/yaml/nodes.py
+++ b/dynaconf/vendor/ruamel/yaml/nodes.py
@@ -8,16 +8,16 @@ class Node:
def __init__(A,tag,value,start_mark,end_mark,comment=_A,anchor=_A):A.tag=tag;A.value=value;A.start_mark=start_mark;A.end_mark=end_mark;A.comment=comment;A.anchor=anchor
def __repr__(A):B=A.value;B=repr(B);return'%s(tag=%r, value=%s)'%(A.__class__.__name__,A.tag,B)
def dump(A,indent=0):
- F=' {}comment: {})\n';D=' ';B=indent
+ E=' {}comment: {})\n';D=' ';B=indent
if isinstance(A.value,string_types):
sys.stdout.write('{}{}(tag={!r}, value={!r})\n'.format(D*B,A.__class__.__name__,A.tag,A.value))
- if A.comment:sys.stdout.write(F.format(D*B,A.comment))
+ if A.comment:sys.stdout.write(E.format(D*B,A.comment))
return
sys.stdout.write('{}{}(tag={!r})\n'.format(D*B,A.__class__.__name__,A.tag))
- if A.comment:sys.stdout.write(F.format(D*B,A.comment))
+ if A.comment:sys.stdout.write(E.format(D*B,A.comment))
for C in A.value:
if isinstance(C,tuple):
- for E in C:E.dump(B+1)
+ for F in C:F.dump(B+1)
elif isinstance(C,Node):C.dump(B+1)
else:sys.stdout.write('Node value type? {}\n'.format(type(C)))
class ScalarNode(Node):
diff --git a/dynaconf/vendor/ruamel/yaml/parser.py b/dynaconf/vendor/ruamel/yaml/parser.py
index 2fc791c..173b1e9 100644
--- a/dynaconf/vendor/ruamel/yaml/parser.py
+++ b/dynaconf/vendor/ruamel/yaml/parser.py
@@ -1,7 +1,5 @@
from __future__ import absolute_import
-_F='expected <block end>, but found %r'
-_E='typ'
-_D='!'
+_D='expected <block end>, but found %r'
_C=True
_B=False
_A=None
@@ -14,7 +12,7 @@ if _B:from typing import Any,Dict,Optional,List
__all__=['Parser','RoundTripParser','ParserError']
class ParserError(MarkedYAMLError):0
class Parser:
- DEFAULT_TAGS={_D:_D,'!!':'tag:yaml.org,2002:'}
+ DEFAULT_TAGS={'!':'!','!!':'tag:yaml.org,2002:'}
def __init__(self,loader):
self.loader=loader
if self.loader is not _A and getattr(self.loader,'_parser',_A)is _A:self.loader._parser=self
@@ -23,11 +21,11 @@ class Parser:
def dispose(self):self.reset_parser()
@property
def scanner(self):
- if hasattr(self.loader,_E):return self.loader.scanner
+ if hasattr(self.loader,'typ'):return self.loader.scanner
return self.loader._scanner
@property
def resolver(self):
- if hasattr(self.loader,_E):return self.loader.resolver
+ if hasattr(self.loader,'typ'):return self.loader.resolver
return self.loader._resolver
def check_event(self,*choices):
if self.current_event is _A:
@@ -109,14 +107,14 @@ class Parser:
tag=self.transform_tag(handle,suffix)
else:tag=suffix
if start_mark is _A:start_mark=end_mark=self.scanner.peek_token().start_mark
- event=_A;implicit=tag is _A or tag==_D
+ event=_A;implicit=tag is _A or tag=='!'
if indentless_sequence and self.scanner.check_token(BlockEntryToken):
comment=_A;pt=self.scanner.peek_token()
if pt.comment and pt.comment[0]:comment=[pt.comment[0],[]];pt.comment[0]=_A
end_mark=self.scanner.peek_token().end_mark;event=SequenceStartEvent(anchor,tag,implicit,start_mark,end_mark,flow_style=_B,comment=comment);self.state=self.parse_indentless_sequence_entry;return event
if self.scanner.check_token(ScalarToken):
token=self.scanner.get_token();end_mark=token.end_mark
- if token.plain and tag is _A or tag==_D:implicit=_C,_B
+ if token.plain and tag is _A or tag=='!':implicit=_C,_B
elif tag is _A:implicit=_B,_C
else:implicit=_B,_B
event=ScalarEvent(anchor,tag,implicit,token.value,start_mark,end_mark,style=token.style,comment=token.comment);self.state=self.states.pop()
@@ -139,7 +137,7 @@ class Parser:
token=self.scanner.get_token();token.move_comment(self.scanner.peek_token())
if not self.scanner.check_token(BlockEntryToken,BlockEndToken):self.states.append(self.parse_block_sequence_entry);return self.parse_block_node()
else:self.state=self.parse_block_sequence_entry;return self.process_empty_scalar(token.end_mark)
- if not self.scanner.check_token(BlockEndToken):token=self.scanner.peek_token();raise ParserError('while parsing a block collection',self.marks[-1],_F%token.id,token.start_mark)
+ if not self.scanner.check_token(BlockEndToken):token=self.scanner.peek_token();raise ParserError('while parsing a block collection',self.marks[-1],_D%token.id,token.start_mark)
token=self.scanner.get_token();event=SequenceEndEvent(token.start_mark,token.end_mark,comment=token.comment);self.state=self.states.pop();self.marks.pop();return event
def parse_indentless_sequence_entry(self):
if self.scanner.check_token(BlockEntryToken):
@@ -154,7 +152,7 @@ class Parser:
if not self.scanner.check_token(KeyToken,ValueToken,BlockEndToken):self.states.append(self.parse_block_mapping_value);return self.parse_block_node_or_indentless_sequence()
else:self.state=self.parse_block_mapping_value;return self.process_empty_scalar(token.end_mark)
if self.resolver.processing_version>(1,1)and self.scanner.check_token(ValueToken):self.state=self.parse_block_mapping_value;return self.process_empty_scalar(self.scanner.peek_token().start_mark)
- if not self.scanner.check_token(BlockEndToken):token=self.scanner.peek_token();raise ParserError('while parsing a block mapping',self.marks[-1],_F%token.id,token.start_mark)
+ if not self.scanner.check_token(BlockEndToken):token=self.scanner.peek_token();raise ParserError('while parsing a block mapping',self.marks[-1],_D%token.id,token.start_mark)
token=self.scanner.get_token();token.move_comment(self.scanner.peek_token());event=MappingEndEvent(token.start_mark,token.end_mark,comment=token.comment);self.state=self.states.pop();self.marks.pop();return event
def parse_block_mapping_value(self):
if self.scanner.check_token(ValueToken):
diff --git a/dynaconf/vendor/ruamel/yaml/reader.py b/dynaconf/vendor/ruamel/yaml/reader.py
index 06bd083..5f87e47 100644
--- a/dynaconf/vendor/ruamel/yaml/reader.py
+++ b/dynaconf/vendor/ruamel/yaml/reader.py
@@ -1,15 +1,12 @@
from __future__ import absolute_import
-_F='\ufeff'
-_E='\x00'
-_D=False
-_C='ascii'
-_B='\n'
+_C='\ufeff'
+_B='ascii'
_A=None
import codecs
from .error import YAMLError,FileMark,StringMark,YAMLStreamError
from .compat import text_type,binary_type,PY3,UNICODE_SIZE
from .util import RegExp
-if _D:from typing import Any,Dict,Optional,List,Union,Text,Tuple,Optional
+if False:from typing import Any,Dict,Optional,List,Union,Text,Tuple,Optional
__all__=['Reader','ReaderError']
class ReaderError(YAMLError):
def __init__(A,name,position,character,encoding,reason):A.name=name;A.character=character;A.position=position;A.encoding=encoding;A.reason=reason
@@ -31,11 +28,11 @@ class Reader:
B=val;A=self
if B is _A:return
A._stream=_A
- if isinstance(B,text_type):A.name='<unicode string>';A.check_printable(B);A.buffer=B+_E
+ if isinstance(B,text_type):A.name='<unicode string>';A.check_printable(B);A.buffer=B+'\x00'
elif isinstance(B,binary_type):A.name='<byte string>';A.raw_buffer=B;A.determine_encoding()
else:
if not hasattr(B,'read'):raise YAMLStreamError('stream argument needs to have a read() method')
- A._stream=B;A.name=getattr(A.stream,'name','<file>');A.eof=_D;A.raw_buffer=_A;A.determine_encoding()
+ A._stream=B;A.name=getattr(A.stream,'name','<file>');A.eof=False;A.raw_buffer=_A;A.determine_encoding()
def peek(A,index=0):
B=index
try:return A.buffer[A.pointer+B]
@@ -49,16 +46,16 @@ class Reader:
if A.pointer+B+1>=len(A.buffer):A.update(B+1)
while B!=0:
C=A.buffer[A.pointer];A.pointer+=1;A.index+=1
- if C in'\n\x85\u2028\u2029'or C=='\r'and A.buffer[A.pointer]!=_B:A.line+=1;A.column=0
- elif C!=_F:A.column+=1
+ if C in'\n\x85\u2028\u2029'or C=='\r'and A.buffer[A.pointer]!='\n':A.line+=1;A.column=0
+ elif C!=_C:A.column+=1
B-=1
def forward(A,length=1):
B=length
if A.pointer+B+1>=len(A.buffer):A.update(B+1)
while B!=0:
C=A.buffer[A.pointer];A.pointer+=1;A.index+=1
- if C==_B or C=='\r'and A.buffer[A.pointer]!=_B:A.line+=1;A.column=0
- elif C!=_F:A.column+=1
+ if C=='\n'or C=='\r'and A.buffer[A.pointer]!='\n':A.line+=1;A.column=0
+ elif C!=_C:A.column+=1
B-=1
def get_mark(A):
if A.stream is _A:return StringMark(A.name,A.index,A.line,A.column,A.buffer,A.pointer)
@@ -72,12 +69,12 @@ class Reader:
A.update(1)
if UNICODE_SIZE==2:NON_PRINTABLE=RegExp('[^\t\n\r -~\x85\xa0-\ud7ff\ue000-�]')
else:NON_PRINTABLE=RegExp('[^\t\n\r -~\x85\xa0-\ud7ff\ue000-�𐀀-\U0010ffff]')
- _printable_ascii=('\t\n\r'+''.join(map(chr,range(32,127)))).encode(_C)
+ _printable_ascii=('\t\n\r'+''.join(map(chr,range(32,127)))).encode(_B)
@classmethod
def _get_non_printable_ascii(D,data):
- A=data.encode(_C);B=A.translate(_A,D._printable_ascii)
+ A=data.encode(_B);B=A.translate(_A,D._printable_ascii)
if not B:return _A
- C=B[:1];return A.index(C),C.decode(_C)
+ C=B[:1];return A.index(C),C.decode(_B)
@classmethod
def _get_non_printable_regex(B,data):
A=B.NON_PRINTABLE.search(data)
@@ -106,7 +103,7 @@ class Reader:
raise ReaderError(A.name,D,F,B.encoding,B.reason)
else:C=A.raw_buffer;E=len(C)
A.check_printable(C);A.buffer+=C;A.raw_buffer=A.raw_buffer[E:]
- if A.eof:A.buffer+=_E;A.raw_buffer=_A;break
+ if A.eof:A.buffer+='\x00';A.raw_buffer=_A;break
def update_raw(A,size=_A):
C=size
if C is _A:C=4096 if PY3 else 1024
diff --git a/dynaconf/vendor/ruamel/yaml/representer.py b/dynaconf/vendor/ruamel/yaml/representer.py
index dc6bc3d..861bb50 100644
--- a/dynaconf/vendor/ruamel/yaml/representer.py
+++ b/dynaconf/vendor/ruamel/yaml/representer.py
@@ -1,28 +1,17 @@
from __future__ import print_function,absolute_import,division
-_e='tag:yaml.org,2002:'
-_d='state'
-_c='args'
-_b='tag:yaml.org,2002:python/object:'
-_a='__getstate__'
-_Z='tag:yaml.org,2002:set'
-_Y='-.inf'
-_X='.inf'
-_W='.nan'
-_V='base64'
-_U='utf-8'
-_T='null'
-_S='typ'
-_R='tag:yaml.org,2002:python/object/new:'
-_Q='tag:yaml.org,2002:timestamp'
-_P='tag:yaml.org,2002:map'
-_O='tag:yaml.org,2002:seq'
-_N='tag:yaml.org,2002:float'
-_M='tag:yaml.org,2002:binary'
-_L='tag:yaml.org,2002:null'
-_K=0.0
-_J='|'
-_I='%s.%s'
-_H='.'
+_T='tag:yaml.org,2002:'
+_S='tag:yaml.org,2002:python/object:'
+_R='__getstate__'
+_Q='tag:yaml.org,2002:set'
+_P='base64'
+_O='tag:yaml.org,2002:python/object/new:'
+_N='tag:yaml.org,2002:timestamp'
+_M='tag:yaml.org,2002:map'
+_L='tag:yaml.org,2002:seq'
+_K='tag:yaml.org,2002:float'
+_J='tag:yaml.org,2002:binary'
+_I='tag:yaml.org,2002:null'
+_H='%s.%s'
_G='tag:yaml.org,2002:int'
_F='comment'
_E='ascii'
@@ -60,7 +49,7 @@ class BaseRepresenter:
@property
def serializer(self):
try:
- if hasattr(self.dumper,_S):return self.dumper.serializer
+ if hasattr(self.dumper,'typ'):return self.dumper.serializer
return self.dumper._serializer
except AttributeError:return self
def represent(self,data):node=self.represent_data(data);self.serializer.serialize(node);self.represented_objects={};self.object_keeper=[];self.alias_key=_A
@@ -145,20 +134,20 @@ class SafeRepresenter(BaseRepresenter):
if data is _A or isinstance(data,tuple)and data==():return _B
if isinstance(data,(binary_type,text_type,bool,int,float)):return _B
return _C
- def represent_none(self,data):return self.represent_scalar(_L,_T)
+ def represent_none(self,data):return self.represent_scalar(_I,'null')
if PY3:
def represent_str(self,data):return self.represent_scalar(_D,data)
def represent_binary(self,data):
if hasattr(base64,'encodebytes'):data=base64.encodebytes(data).decode(_E)
else:data=base64.encodestring(data).decode(_E)
- return self.represent_scalar(_M,data,style=_J)
+ return self.represent_scalar(_J,data,style='|')
else:
def represent_str(self,data):
tag=_A;style=_A
try:data=unicode(data,_E);tag=_D
except UnicodeDecodeError:
- try:data=unicode(data,_U);tag=_D
- except UnicodeDecodeError:data=data.encode(_V);tag=_M;style=_J
+ try:data=unicode(data,'utf-8');tag=_D
+ except UnicodeDecodeError:data=data.encode(_P);tag=_J;style='|'
return self.represent_scalar(tag,data,style=style)
def represent_unicode(self,data):return self.represent_scalar(_D,data)
def represent_bool(self,data,anchor=_A):
@@ -173,25 +162,25 @@ class SafeRepresenter(BaseRepresenter):
inf_value=1e+300
while repr(inf_value)!=repr(inf_value*inf_value):inf_value*=inf_value
def represent_float(self,data):
- if data!=data or data==_K and data==1.0:value=_W
- elif data==self.inf_value:value=_X
- elif data==-self.inf_value:value=_Y
+ if data!=data or data==0.0 and data==1.0:value='.nan'
+ elif data==self.inf_value:value='.inf'
+ elif data==-self.inf_value:value='-.inf'
else:
value=to_unicode(repr(data)).lower()
if getattr(self.serializer,'use_version',_A)==(1,1):
- if _H not in value and'e'in value:value=value.replace('e','.0e',1)
- return self.represent_scalar(_N,value)
- def represent_list(self,data):return self.represent_sequence(_O,data)
- def represent_dict(self,data):return self.represent_mapping(_P,data)
+ if'.'not in value and'e'in value:value=value.replace('e','.0e',1)
+ return self.represent_scalar(_K,value)
+ def represent_list(self,data):return self.represent_sequence(_L,data)
+ def represent_dict(self,data):return self.represent_mapping(_M,data)
def represent_ordereddict(self,data):return self.represent_omap('tag:yaml.org,2002:omap',data)
def represent_set(self,data):
value={}
for key in data:value[key]=_A
- return self.represent_mapping(_Z,value)
- def represent_date(self,data):value=to_unicode(data.isoformat());return self.represent_scalar(_Q,value)
- def represent_datetime(self,data):value=to_unicode(data.isoformat(' '));return self.represent_scalar(_Q,value)
+ return self.represent_mapping(_Q,value)
+ def represent_date(self,data):value=to_unicode(data.isoformat());return self.represent_scalar(_N,value)
+ def represent_datetime(self,data):value=to_unicode(data.isoformat(' '));return self.represent_scalar(_N,value)
def represent_yaml_object(self,tag,data,cls,flow_style=_A):
- if hasattr(data,_a):state=data.__getstate__()
+ if hasattr(data,_R):state=data.__getstate__()
else:state=data.__dict__.copy()
return self.represent_mapping(tag,state,flow_style=flow_style)
def represent_undefined(self,data):raise RepresenterError('cannot represent an object: %s'%(data,))
@@ -218,8 +207,8 @@ class Representer(SafeRepresenter):
tag=_A;style=_A
try:data=unicode(data,_E);tag=_D
except UnicodeDecodeError:
- try:data=unicode(data,_U);tag='tag:yaml.org,2002:python/str'
- except UnicodeDecodeError:data=data.encode(_V);tag=_M;style=_J
+ try:data=unicode(data,'utf-8');tag='tag:yaml.org,2002:python/str'
+ except UnicodeDecodeError:data=data.encode(_P);tag=_J;style='|'
return self.represent_scalar(tag,data,style=style)
def represent_unicode(self,data):
tag=_A
@@ -231,28 +220,28 @@ class Representer(SafeRepresenter):
if int(data)is not data:tag='tag:yaml.org,2002:python/long'
return self.represent_scalar(tag,to_unicode(data))
def represent_complex(self,data):
- if data.imag==_K:data='%r'%data.real
- elif data.real==_K:data='%rj'%data.imag
+ if data.imag==0.0:data='%r'%data.real
+ elif data.real==0.0:data='%rj'%data.imag
elif data.imag>0:data='%r+%rj'%(data.real,data.imag)
else:data='%r%rj'%(data.real,data.imag)
return self.represent_scalar('tag:yaml.org,2002:python/complex',data)
def represent_tuple(self,data):return self.represent_sequence('tag:yaml.org,2002:python/tuple',data)
def represent_name(self,data):
- try:name=_I%(data.__module__,data.__qualname__)
- except AttributeError:name=_I%(data.__module__,data.__name__)
+ try:name=_H%(data.__module__,data.__qualname__)
+ except AttributeError:name=_H%(data.__module__,data.__name__)
return self.represent_scalar('tag:yaml.org,2002:python/name:'+name,'')
def represent_module(self,data):return self.represent_scalar('tag:yaml.org,2002:python/module:'+data.__name__,'')
if PY2:
def represent_instance(self,data):
- cls=data.__class__;class_name=_I%(cls.__module__,cls.__name__);args=_A;state=_A
+ cls=data.__class__;class_name=_H%(cls.__module__,cls.__name__);args=_A;state=_A
if hasattr(data,'__getinitargs__'):args=list(data.__getinitargs__())
- if hasattr(data,_a):state=data.__getstate__()
+ if hasattr(data,_R):state=data.__getstate__()
else:state=data.__dict__
- if args is _A and isinstance(state,dict):return self.represent_mapping(_b+class_name,state)
- if isinstance(state,dict)and not state:return self.represent_sequence(_R+class_name,args)
+ if args is _A and isinstance(state,dict):return self.represent_mapping(_S+class_name,state)
+ if isinstance(state,dict)and not state:return self.represent_sequence(_O+class_name,args)
value={}
- if bool(args):value[_c]=args
- value[_d]=state;return self.represent_mapping(_R+class_name,value)
+ if bool(args):value['args']=args
+ value['state']=state;return self.represent_mapping(_O+class_name,value)
def represent_object(self,data):
cls=type(data)
if cls in copyreg.dispatch_table:reduce=copyreg.dispatch_table[cls](data)
@@ -263,15 +252,15 @@ class Representer(SafeRepresenter):
if state is _A:state={}
if listitems is not _A:listitems=list(listitems)
if dictitems is not _A:dictitems=dict(dictitems)
- if function.__name__=='__newobj__':function=args[0];args=args[1:];tag=_R;newobj=_B
+ if function.__name__=='__newobj__':function=args[0];args=args[1:];tag=_O;newobj=_B
else:tag='tag:yaml.org,2002:python/object/apply:';newobj=_C
- try:function_name=_I%(function.__module__,function.__qualname__)
- except AttributeError:function_name=_I%(function.__module__,function.__name__)
- if not args and not listitems and not dictitems and isinstance(state,dict)and newobj:return self.represent_mapping(_b+function_name,state)
+ try:function_name=_H%(function.__module__,function.__qualname__)
+ except AttributeError:function_name=_H%(function.__module__,function.__name__)
+ if not args and not listitems and not dictitems and isinstance(state,dict)and newobj:return self.represent_mapping(_S+function_name,state)
if not listitems and not dictitems and isinstance(state,dict)and not state:return self.represent_sequence(tag+function_name,args)
value={}
- if args:value[_c]=args
- if state or not isinstance(state,dict):value[_d]=state
+ if args:value['args']=args
+ if state or not isinstance(state,dict):value['state']=state
if listitems:value['listitems']=listitems
if dictitems:value['dictitems']=dictitems
return self.represent_mapping(tag+function_name,value)
@@ -289,7 +278,7 @@ Representer.add_multi_representer(type,Representer.represent_name)
from .comments import CommentedMap,CommentedOrderedMap,CommentedSeq,CommentedKeySeq,CommentedKeyMap,CommentedSet,comment_attrib,merge_attrib,TaggedScalar
class RoundTripRepresenter(SafeRepresenter):
def __init__(self,default_style=_A,default_flow_style=_A,dumper=_A):
- if not hasattr(dumper,_S)and default_flow_style is _A:default_flow_style=_C
+ if not hasattr(dumper,'typ')and default_flow_style is _A:default_flow_style=_C
SafeRepresenter.__init__(self,default_style=default_style,default_flow_style=default_flow_style,dumper=dumper)
def ignore_aliases(self,data):
try:
@@ -297,10 +286,10 @@ class RoundTripRepresenter(SafeRepresenter):
except AttributeError:pass
return SafeRepresenter.ignore_aliases(self,data)
def represent_none(self,data):
- if len(self.represented_objects)==0 and not self.serializer.use_explicit_start:return self.represent_scalar(_L,_T)
- return self.represent_scalar(_L,'')
+ if len(self.represented_objects)==0 and not self.serializer.use_explicit_start:return self.represent_scalar(_I,'null')
+ return self.represent_scalar(_I,'')
def represent_literal_scalarstring(self,data):
- tag=_A;style=_J;anchor=data.yaml_anchor(any=_B)
+ tag=_A;style='|';anchor=data.yaml_anchor(any=_B)
if PY2 and not isinstance(data,unicode):data=unicode(data,_E)
tag=_D;return self.represent_scalar(tag,data,style=style,anchor=anchor)
represent_preserved_scalarstring=represent_literal_scalarstring
@@ -353,35 +342,35 @@ class RoundTripRepresenter(SafeRepresenter):
else:s=format(data,'X')
anchor=data.yaml_anchor(any=_B);return self.insert_underscore('0x',s,data._underscore,anchor=anchor)
def represent_scalar_float(self,data):
- C='+';B='{:{}0{}d}';A='0';value=_A;anchor=data.yaml_anchor(any=_B)
- if data!=data or data==_K and data==1.0:value=_W
- elif data==self.inf_value:value=_X
- elif data==-self.inf_value:value=_Y
- if value:return self.represent_scalar(_N,value,anchor=anchor)
+ B='{:{}0{}d}';A='0';value=_A;anchor=data.yaml_anchor(any=_B)
+ if data!=data or data==0.0 and data==1.0:value='.nan'
+ elif data==self.inf_value:value='.inf'
+ elif data==-self.inf_value:value='-.inf'
+ if value:return self.represent_scalar(_K,value,anchor=anchor)
if data._exp is _A and data._prec>0 and data._prec==data._width-1:value='{}{:d}.'.format(data._m_sign if data._m_sign else'',abs(int(data)))
elif data._exp is _A:
prec=data._prec;ms=data._m_sign if data._m_sign else'';value='{}{:0{}.{}f}'.format(ms,abs(data),data._width-len(ms),data._width-prec-1)
- if prec==0 or prec==1 and ms!='':value=value.replace('0.',_H)
+ if prec==0 or prec==1 and ms!='':value=value.replace('0.','.')
while len(value)<data._width:value+=A
else:
m,es='{:{}.{}e}'.format(data,data._width,data._width+(1 if data._m_sign else 0)).split('e');w=data._width if data._prec>0 else data._width+1
if data<0:w+=1
- m=m[:w];e=int(es);m1,m2=m.split(_H)
+ m=m[:w];e=int(es);m1,m2=m.split('.')
while len(m1)+len(m2)<data._width-(1 if data._prec>=0 else 0):m2+=A
- if data._m_sign and data>0:m1=C+m1
- esgn=C if data._e_sign else''
+ if data._m_sign and data>0:m1='+'+m1
+ esgn='+'if data._e_sign else''
if data._prec<0:
if m2!=A:e-=len(m2)
else:m2=''
while len(m1)+len(m2)-(1 if data._m_sign else 0)<data._width:m2+=A;e-=1
value=m1+m2+data._exp+B.format(e,esgn,data._e_width)
- elif data._prec==0:e-=len(m2);value=m1+m2+_H+data._exp+B.format(e,esgn,data._e_width)
+ elif data._prec==0:e-=len(m2);value=m1+m2+'.'+data._exp+B.format(e,esgn,data._e_width)
else:
if data._m_lead0>0:m2=A*(data._m_lead0-1)+m1+m2;m1=A;m2=m2[:-data._m_lead0];e+=data._m_lead0
while len(m1)<data._prec:m1+=m2[0];m2=m2[1:];e-=1
- value=m1+_H+m2+data._exp+B.format(e,esgn,data._e_width)
+ value=m1+'.'+m2+data._exp+B.format(e,esgn,data._e_width)
if value is _A:value=to_unicode(repr(data)).lower()
- return self.represent_scalar(_N,value,anchor=anchor)
+ return self.represent_scalar(_K,value,anchor=anchor)
def represent_sequence(self,tag,sequence,flow_style=_A):
value=[]
try:flow_style=sequence.fa.flow_style(flow_style)
@@ -420,8 +409,8 @@ class RoundTripRepresenter(SafeRepresenter):
if nc is not _A:assert val is _A or val==nc;comments[idx]=nc
node.comment=comments;return node
def represent_key(self,data):
- if isinstance(data,CommentedKeySeq):self.alias_key=_A;return self.represent_sequence(_O,data,flow_style=_B)
- if isinstance(data,CommentedKeyMap):self.alias_key=_A;return self.represent_mapping(_P,data,flow_style=_B)
+ if isinstance(data,CommentedKeySeq):self.alias_key=_A;return self.represent_sequence(_L,data,flow_style=_B)
+ if isinstance(data,CommentedKeyMap):self.alias_key=_A;return self.represent_mapping(_M,data,flow_style=_B)
return SafeRepresenter.represent_key(self,data)
def represent_mapping(self,tag,mapping,flow_style=_A):
value=[]
@@ -499,7 +488,7 @@ class RoundTripRepresenter(SafeRepresenter):
else:node.flow_style=best_style
return node
def represent_set(self,setting):
- flow_style=_C;tag=_Z;value=[];flow_style=setting.fa.flow_style(flow_style)
+ flow_style=_C;tag=_Q;value=[];flow_style=setting.fa.flow_style(flow_style)
try:anchor=setting.yaml_anchor()
except AttributeError:anchor=_A
node=MappingNode(tag,value,flow_style=flow_style,anchor=anchor)
@@ -528,24 +517,24 @@ class RoundTripRepresenter(SafeRepresenter):
try:t=data.tag.value
except AttributeError:t=_A
if t:
- if t.startswith('!!'):tag=_e+t[2:]
+ if t.startswith('!!'):tag=_T+t[2:]
else:tag=t
- else:tag=_P
+ else:tag=_M
return self.represent_mapping(tag,data)
def represent_list(self,data):
try:t=data.tag.value
except AttributeError:t=_A
if t:
- if t.startswith('!!'):tag=_e+t[2:]
+ if t.startswith('!!'):tag=_T+t[2:]
else:tag=t
- else:tag=_O
+ else:tag=_L
return self.represent_sequence(tag,data)
def represent_datetime(self,data):
- B='tz';A='delta';inter='T'if data._yaml['t']else' ';_yaml=data._yaml
+ A='delta';inter='T'if data._yaml['t']else' ';_yaml=data._yaml
if _yaml[A]:data+=_yaml[A];value=data.isoformat(inter)
else:value=data.isoformat(inter)
- if _yaml[B]:value+=_yaml[B]
- return self.represent_scalar(_Q,to_unicode(value))
+ if _yaml['tz']:value+=_yaml['tz']
+ return self.represent_scalar(_N,to_unicode(value))
def represent_tagged_scalar(self,data):
try:tag=data.tag.value
except AttributeError:tag=_A
diff --git a/dynaconf/vendor/ruamel/yaml/resolver.py b/dynaconf/vendor/ruamel/yaml/resolver.py
index 7377ca5..9bef98b 100644
--- a/dynaconf/vendor/ruamel/yaml/resolver.py
+++ b/dynaconf/vendor/ruamel/yaml/resolver.py
@@ -1,12 +1,10 @@
from __future__ import absolute_import
-_J='yaml_implicit_resolvers'
-_I='typ'
-_H='-+0123456789'
-_G='tag:yaml.org,2002:int'
-_F='-+0123456789.'
-_E='tag:yaml.org,2002:float'
-_D='tag:yaml.org,2002:bool'
-_C=True
+_H='yaml_implicit_resolvers'
+_G='-+0123456789'
+_F='tag:yaml.org,2002:int'
+_E='-+0123456789.'
+_D='tag:yaml.org,2002:float'
+_C='tag:yaml.org,2002:bool'
_B=False
_A=None
import re
@@ -16,7 +14,7 @@ from .error import *
from .nodes import MappingNode,ScalarNode,SequenceNode
from .util import RegExp
__all__=['BaseResolver','Resolver','VersionedResolver']
-implicit_resolvers=[([(1,2)],_D,RegExp('^(?:true|True|TRUE|false|False|FALSE)$',re.X),list('tTfF')),([(1,1)],_D,RegExp('^(?:y|Y|yes|Yes|YES|n|N|no|No|NO\n |true|True|TRUE|false|False|FALSE\n |on|On|ON|off|Off|OFF)$',re.X),list('yYnNtTfFoO')),([(1,2)],_E,RegExp('^(?:\n [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?\n |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)\n |[-+]?\\.[0-9_]+(?:[eE][-+][0-9]+)?\n |[-+]?\\.(?:inf|Inf|INF)\n |\\.(?:nan|NaN|NAN))$',re.X),list(_F)),([(1,1)],_E,RegExp('^(?:\n [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?\n |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)\n |\\.[0-9_]+(?:[eE][-+][0-9]+)?\n |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]* # sexagesimal float\n |[-+]?\\.(?:inf|Inf|INF)\n |\\.(?:nan|NaN|NAN))$',re.X),list(_F)),([(1,2)],_G,RegExp('^(?:[-+]?0b[0-1_]+\n |[-+]?0o?[0-7_]+\n |[-+]?[0-9_]+\n |[-+]?0x[0-9a-fA-F_]+)$',re.X),list(_H)),([(1,1)],_G,RegExp('^(?:[-+]?0b[0-1_]+\n |[-+]?0?[0-7_]+\n |[-+]?(?:0|[1-9][0-9_]*)\n |[-+]?0x[0-9a-fA-F_]+\n |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$',re.X),list(_H)),([(1,2),(1,1)],'tag:yaml.org,2002:merge',RegExp('^(?:<<)$'),['<']),([(1,2),(1,1)],'tag:yaml.org,2002:null',RegExp('^(?: ~\n |null|Null|NULL\n | )$',re.X),['~','n','N','']),([(1,2),(1,1)],'tag:yaml.org,2002:timestamp',RegExp('^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\n |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]?\n (?:[Tt]|[ \\t]+)[0-9][0-9]?\n :[0-9][0-9] :[0-9][0-9] (?:\\.[0-9]*)?\n (?:[ \\t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$',re.X),list('0123456789')),([(1,2),(1,1)],'tag:yaml.org,2002:value',RegExp('^(?:=)$'),['=']),([(1,2),(1,1)],'tag:yaml.org,2002:yaml',RegExp('^(?:!|&|\\*)$'),list('!&*'))]
+implicit_resolvers=[([(1,2)],_C,RegExp('^(?:true|True|TRUE|false|False|FALSE)$',re.X),list('tTfF')),([(1,1)],_C,RegExp('^(?:y|Y|yes|Yes|YES|n|N|no|No|NO\n |true|True|TRUE|false|False|FALSE\n |on|On|ON|off|Off|OFF)$',re.X),list('yYnNtTfFoO')),([(1,2)],_D,RegExp('^(?:\n [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?\n |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)\n |[-+]?\\.[0-9_]+(?:[eE][-+][0-9]+)?\n |[-+]?\\.(?:inf|Inf|INF)\n |\\.(?:nan|NaN|NAN))$',re.X),list(_E)),([(1,1)],_D,RegExp('^(?:\n [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?\n |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)\n |\\.[0-9_]+(?:[eE][-+][0-9]+)?\n |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]* # sexagesimal float\n |[-+]?\\.(?:inf|Inf|INF)\n |\\.(?:nan|NaN|NAN))$',re.X),list(_E)),([(1,2)],_F,RegExp('^(?:[-+]?0b[0-1_]+\n |[-+]?0o?[0-7_]+\n |[-+]?[0-9_]+\n |[-+]?0x[0-9a-fA-F_]+)$',re.X),list(_G)),([(1,1)],_F,RegExp('^(?:[-+]?0b[0-1_]+\n |[-+]?0?[0-7_]+\n |[-+]?(?:0|[1-9][0-9_]*)\n |[-+]?0x[0-9a-fA-F_]+\n |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$',re.X),list(_G)),([(1,2),(1,1)],'tag:yaml.org,2002:merge',RegExp('^(?:<<)$'),['<']),([(1,2),(1,1)],'tag:yaml.org,2002:null',RegExp('^(?: ~\n |null|Null|NULL\n | )$',re.X),['~','n','N','']),([(1,2),(1,1)],'tag:yaml.org,2002:timestamp',RegExp('^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\n |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]?\n (?:[Tt]|[ \\t]+)[0-9][0-9]?\n :[0-9][0-9] :[0-9][0-9] (?:\\.[0-9]*)?\n (?:[ \\t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$',re.X),list('0123456789')),([(1,2),(1,1)],'tag:yaml.org,2002:value',RegExp('^(?:=)$'),['=']),([(1,2),(1,1)],'tag:yaml.org,2002:yaml',RegExp('^(?:!|&|\\*)$'),list('!&*'))]
class ResolverError(YAMLError):0
class BaseResolver:
DEFAULT_SCALAR_TAG='tag:yaml.org,2002:str';DEFAULT_SEQUENCE_TAG='tag:yaml.org,2002:seq';DEFAULT_MAPPING_TAG='tag:yaml.org,2002:map';yaml_implicit_resolvers={};yaml_path_resolvers={}
@@ -27,17 +25,17 @@ class BaseResolver:
@property
def parser(self):
if self.loadumper is not _A:
- if hasattr(self.loadumper,_I):return self.loadumper.parser
+ if hasattr(self.loadumper,'typ'):return self.loadumper.parser
return self.loadumper._parser
return _A
@classmethod
def add_implicit_resolver_base(cls,tag,regexp,first):
- if _J not in cls.__dict__:cls.yaml_implicit_resolvers=dict(((k,cls.yaml_implicit_resolvers[k][:])for k in cls.yaml_implicit_resolvers))
+ if _H not in cls.__dict__:cls.yaml_implicit_resolvers=dict(((k,cls.yaml_implicit_resolvers[k][:])for k in cls.yaml_implicit_resolvers))
if first is _A:first=[_A]
for ch in first:cls.yaml_implicit_resolvers.setdefault(ch,[]).append((tag,regexp))
@classmethod
def add_implicit_resolver(cls,tag,regexp,first):
- if _J not in cls.__dict__:cls.yaml_implicit_resolvers=dict(((k,cls.yaml_implicit_resolvers[k][:])for k in cls.yaml_implicit_resolvers))
+ if _H not in cls.__dict__:cls.yaml_implicit_resolvers=dict(((k,cls.yaml_implicit_resolvers[k][:])for k in cls.yaml_implicit_resolvers))
if first is _A:first=[_A]
for ch in first:cls.yaml_implicit_resolvers.setdefault(ch,[]).append((tag,regexp))
implicit_resolvers.append(([(1,2),(1,1)],tag,regexp,first))
@@ -48,7 +46,7 @@ class BaseResolver:
for element in path:
if isinstance(element,(list,tuple)):
if len(element)==2:node_check,index_check=element
- elif len(element)==1:node_check=element[0];index_check=_C
+ elif len(element)==1:node_check=element[0];index_check=True
else:raise ResolverError('Invalid path element: %s'%(element,))
else:node_check=_A;index_check=element
if node_check is str:node_check=ScalarNode
@@ -85,13 +83,13 @@ class BaseResolver:
if current_node.tag!=node_check:return _B
elif node_check is not _A:
if not isinstance(current_node,node_check):return _B
- if index_check is _C and current_index is not _A:return _B
+ if index_check is True and current_index is not _A:return _B
if(index_check is _B or index_check is _A)and current_index is _A:return _B
if isinstance(index_check,string_types):
if not(isinstance(current_index,ScalarNode)and index_check==current_index.value):return _B
elif isinstance(index_check,int)and not isinstance(index_check,bool):
if index_check!=current_index:return _B
- return _C
+ return True
def resolve(self,kind,value,implicit):
if kind is ScalarNode and implicit[0]:
if value=='':resolvers=self.yaml_implicit_resolvers.get('',[])
@@ -151,7 +149,7 @@ class VersionedResolver(BaseResolver):
try:version=self.loadumper._scanner.yaml_version
except AttributeError:
try:
- if hasattr(self.loadumper,_I):version=self.loadumper.version
+ if hasattr(self.loadumper,'typ'):version=self.loadumper.version
else:version=self.loadumper._serializer.use_version
except AttributeError:version=_A
if version is _A:
diff --git a/dynaconf/vendor/ruamel/yaml/scalarbool.py b/dynaconf/vendor/ruamel/yaml/scalarbool.py
index 84c7cc2..fb951c4 100644
--- a/dynaconf/vendor/ruamel/yaml/scalarbool.py
+++ b/dynaconf/vendor/ruamel/yaml/scalarbool.py
@@ -6,7 +6,7 @@ if _B:from typing import Text,Any,Dict,List
__all__=['ScalarBoolean']
class ScalarBoolean(int):
def __new__(D,*E,**A):
- B=A.pop('anchor',_A);C=int.__new__(D,*E,**A)
+ B=A.pop('anchor',_A);C=int.__new__(D,*(E),**A)
if B is not _A:C.yaml_set_anchor(B,always_dump=True)
return C
@property
diff --git a/dynaconf/vendor/ruamel/yaml/scalarfloat.py b/dynaconf/vendor/ruamel/yaml/scalarfloat.py
index fab3a1b..443b9d7 100644
--- a/dynaconf/vendor/ruamel/yaml/scalarfloat.py
+++ b/dynaconf/vendor/ruamel/yaml/scalarfloat.py
@@ -8,7 +8,7 @@ if _B:from typing import Text,Any,Dict,List
__all__=['ScalarFloat','ExponentialFloat','ExponentialCapsFloat']
class ScalarFloat(float):
def __new__(D,*E,**A):
- F=A.pop('width',_A);G=A.pop('prec',_A);H=A.pop('m_sign',_A);I=A.pop('m_lead0',0);J=A.pop('exp',_A);K=A.pop('e_width',_A);L=A.pop('e_sign',_A);M=A.pop('underscore',_A);C=A.pop('anchor',_A);B=float.__new__(D,*E,**A);B._width=F;B._prec=G;B._m_sign=H;B._m_lead0=I;B._exp=J;B._e_width=K;B._e_sign=L;B._underscore=M
+ F=A.pop('width',_A);G=A.pop('prec',_A);H=A.pop('m_sign',_A);I=A.pop('m_lead0',0);J=A.pop('exp',_A);K=A.pop('e_width',_A);L=A.pop('e_sign',_A);M=A.pop('underscore',_A);C=A.pop('anchor',_A);B=float.__new__(D,*(E),**A);B._width=F;B._prec=G;B._m_sign=H;B._m_lead0=I;B._exp=J;B._e_width=K;B._e_sign=L;B._underscore=M
if C is not _A:B.yaml_set_anchor(C,always_dump=True)
return B
def __iadd__(A,a):return float(A)+a;B=type(A)(A+a);B._width=A._width;B._underscore=A._underscore[:]if A._underscore is not _A else _A;return B
diff --git a/dynaconf/vendor/ruamel/yaml/scalarint.py b/dynaconf/vendor/ruamel/yaml/scalarint.py
index e61b7eb..925ac41 100644
--- a/dynaconf/vendor/ruamel/yaml/scalarint.py
+++ b/dynaconf/vendor/ruamel/yaml/scalarint.py
@@ -7,7 +7,7 @@ if _B:from typing import Text,Any,Dict,List
__all__=['ScalarInt','BinaryInt','OctalInt','HexInt','HexCapsInt','DecimalInt']
class ScalarInt(no_limit_int):
def __new__(D,*E,**A):
- F=A.pop('width',_A);G=A.pop('underscore',_A);C=A.pop('anchor',_A);B=no_limit_int.__new__(D,*E,**A);B._width=F;B._underscore=G
+ F=A.pop('width',_A);G=A.pop('underscore',_A);C=A.pop('anchor',_A);B=no_limit_int.__new__(D,*(E),**A);B._width=F;B._underscore=G
if C is not _A:B.yaml_set_anchor(C,always_dump=True)
return B
def __iadd__(A,a):B=type(A)(A+a);B._width=A._width;B._underscore=A._underscore[:]if A._underscore is not _A else _A;return B
diff --git a/dynaconf/vendor/ruamel/yaml/scalarstring.py b/dynaconf/vendor/ruamel/yaml/scalarstring.py
index 53b9c39..bc7f1f8 100644
--- a/dynaconf/vendor/ruamel/yaml/scalarstring.py
+++ b/dynaconf/vendor/ruamel/yaml/scalarstring.py
@@ -1,6 +1,5 @@
from __future__ import print_function,absolute_import,division,unicode_literals
-_D='comment'
-_C='\n'
+_C='comment'
_B=False
_A=None
from .compat import text_type
@@ -10,7 +9,7 @@ __all__=['ScalarString','LiteralScalarString','FoldedScalarString','SingleQuoted
class ScalarString(text_type):
__slots__=Anchor.attrib
def __new__(D,*E,**A):
- B=A.pop('anchor',_A);C=text_type.__new__(D,*E,**A)
+ B=A.pop('anchor',_A);C=text_type.__new__(D,*(E),**A)
if B is not _A:C.yaml_set_anchor(B,always_dump=True)
return C
def replace(A,old,new,maxreplace=-1):return type(A)(text_type.replace(A,old,new,maxreplace))
@@ -25,11 +24,11 @@ class ScalarString(text_type):
return _A
def yaml_set_anchor(A,value,always_dump=_B):A.anchor.value=value;A.anchor.always_dump=always_dump
class LiteralScalarString(ScalarString):
- __slots__=_D;style='|'
+ __slots__=_C;style='|'
def __new__(A,value,anchor=_A):return ScalarString.__new__(A,value,anchor=anchor)
PreservedScalarString=LiteralScalarString
class FoldedScalarString(ScalarString):
- __slots__='fold_pos',_D;style='>'
+ __slots__='fold_pos',_C;style='>'
def __new__(A,value,anchor=_A):return ScalarString.__new__(A,value,anchor=anchor)
class SingleQuotedScalarString(ScalarString):
__slots__=();style="'"
@@ -40,10 +39,10 @@ class DoubleQuotedScalarString(ScalarString):
class PlainScalarString(ScalarString):
__slots__=();style=''
def __new__(A,value,anchor=_A):return ScalarString.__new__(A,value,anchor=anchor)
-def preserve_literal(s):return LiteralScalarString(s.replace('\r\n',_C).replace('\r',_C))
+def preserve_literal(s):return LiteralScalarString(s.replace('\r\n','\n').replace('\r','\n'))
def walk_tree(base,map=_A):
A=base;from dynaconf.vendor.ruamel.yaml.compat import string_types as E,MutableMapping as G,MutableSequence as H
- if map is _A:map={_C:preserve_literal}
+ if map is _A:map={'\n':preserve_literal}
if isinstance(A,G):
for F in A:
C=A[F]
diff --git a/dynaconf/vendor/ruamel/yaml/scanner.py b/dynaconf/vendor/ruamel/yaml/scanner.py
index dfbd1b4..6b344e6 100644
--- a/dynaconf/vendor/ruamel/yaml/scanner.py
+++ b/dynaconf/vendor/ruamel/yaml/scanner.py
@@ -1,36 +1,23 @@
from __future__ import print_function,absolute_import,division,unicode_literals
-_o='\u2028\u2029'
-_n='\r\n'
-_m='\r\n\x85'
-_l='while scanning a quoted scalar'
-_k='0123456789ABCDEFabcdef'
-_j=' \r\n\x85\u2028\u2029'
-_i='\x07'
-_h='expected a comment or a line break, but found %r'
-_g='directive'
-_f='\ufeff'
-_e="could not find expected ':'"
-_d='while scanning a simple key'
-_c='typ'
-_b='\\'
-_a='\t'
-_Z="expected ' ', but found %r"
-_Y='while scanning a %s'
-_X='\r\n\x85\u2028\u2029'
-_W='while scanning a block scalar'
-_V='expected alphabetic or numeric character, but found %r'
-_U='a'
-_T='...'
-_S='---'
-_R='>'
-_Q='9'
-_P='"'
-_O=':'
-_N='-'
-_M=' \t'
-_L='\x00 \r\n\x85\u2028\u2029'
-_K='0'
-_J="'"
+_b='\u2028\u2029'
+_a='\r\n\x85'
+_Z='while scanning a quoted scalar'
+_Y='0123456789ABCDEFabcdef'
+_X=' \r\n\x85\u2028\u2029'
+_W='expected a comment or a line break, but found %r'
+_V='directive'
+_U='\ufeff'
+_T="could not find expected ':'"
+_S='while scanning a simple key'
+_R="expected ' ', but found %r"
+_Q='while scanning a %s'
+_P='\r\n\x85\u2028\u2029'
+_O='while scanning a block scalar'
+_N='expected alphabetic or numeric character, but found %r'
+_M='...'
+_L='---'
+_K=' \t'
+_J='\x00 \r\n\x85\u2028\u2029'
_I='\x00'
_H='!'
_G='while scanning a directive'
@@ -47,7 +34,7 @@ if _B:from typing import Any,Dict,Optional,List,Union,Text;from .compat import V
__all__=['Scanner','RoundTripScanner','ScannerError']
_THE_END='\n\x00\r\x85\u2028\u2029'
_THE_END_SPACE_TAB=' \n\x00\t\r\x85\u2028\u2029'
-_SPACE_TAB=_M
+_SPACE_TAB=_K
class ScannerError(MarkedYAMLError):0
class SimpleKey:
def __init__(self,token_number,required,index,line,column,mark):self.token_number=token_number;self.required=required;self.index=index;self.line=line;self.column=column;self.mark=mark
@@ -63,12 +50,12 @@ class Scanner:
def reader(self):
try:return self._scanner_reader
except AttributeError:
- if hasattr(self.loader,_c):self._scanner_reader=self.loader.reader
+ if hasattr(self.loader,'typ'):self._scanner_reader=self.loader.reader
else:self._scanner_reader=self.loader._reader
return self._scanner_reader
@property
def scanner_processing_version(self):
- if hasattr(self.loader,_c):return self.loader.resolver.processing_version
+ if hasattr(self.loader,'typ'):return self.loader.resolver.processing_version
return self.loader.processing_version
def check_token(self,*choices):
while self.need_more_tokens():self.fetch_more_tokens()
@@ -96,23 +83,23 @@ class Scanner:
self.stale_possible_simple_keys();self.unwind_indent(self.reader.column);ch=self.reader.peek()
if ch==_I:return self.fetch_stream_end()
if ch=='%'and self.check_directive():return self.fetch_directive()
- if ch==_N and self.check_document_start():return self.fetch_document_start()
+ if ch=='-'and self.check_document_start():return self.fetch_document_start()
if ch=='.'and self.check_document_end():return self.fetch_document_end()
if ch=='[':return self.fetch_flow_sequence_start()
if ch=='{':return self.fetch_flow_mapping_start()
if ch==']':return self.fetch_flow_sequence_end()
if ch=='}':return self.fetch_flow_mapping_end()
if ch==',':return self.fetch_flow_entry()
- if ch==_N and self.check_block_entry():return self.fetch_block_entry()
+ if ch=='-'and self.check_block_entry():return self.fetch_block_entry()
if ch=='?'and self.check_key():return self.fetch_key()
- if ch==_O and self.check_value():return self.fetch_value()
+ if ch==':'and self.check_value():return self.fetch_value()
if ch=='*':return self.fetch_alias()
if ch=='&':return self.fetch_anchor()
if ch==_H:return self.fetch_tag()
if ch=='|'and not self.flow_level:return self.fetch_literal()
- if ch==_R and not self.flow_level:return self.fetch_folded()
- if ch==_J:return self.fetch_single()
- if ch==_P:return self.fetch_double()
+ if ch=='>'and not self.flow_level:return self.fetch_folded()
+ if ch=="'":return self.fetch_single()
+ if ch=='"':return self.fetch_double()
if self.check_plain():return self.fetch_plain()
raise ScannerError('while scanning for the next token',_C,'found character %r that cannot start any token'%utf8(ch),self.reader.get_mark())
def next_possible_simple_key(self):
@@ -125,7 +112,7 @@ class Scanner:
for level in list(self.possible_simple_keys):
key=self.possible_simple_keys[level]
if key.line!=self.reader.line or self.reader.index-key.index>1024:
- if key.required:raise ScannerError(_d,key.mark,_e,self.reader.get_mark())
+ if key.required:raise ScannerError(_S,key.mark,_T,self.reader.get_mark())
del self.possible_simple_keys[level]
def save_possible_simple_key(self):
required=not self.flow_level and self.indent==self.reader.column
@@ -133,7 +120,7 @@ class Scanner:
def remove_possible_simple_key(self):
if self.flow_level in self.possible_simple_keys:
key=self.possible_simple_keys[self.flow_level]
- if key.required:raise ScannerError(_d,key.mark,_e,self.reader.get_mark())
+ if key.required:raise ScannerError(_S,key.mark,_T,self.reader.get_mark())
del self.possible_simple_keys[self.flow_level]
def unwind_indent(self,column):
if bool(self.flow_level):return
@@ -186,10 +173,10 @@ class Scanner:
def fetch_anchor(self):self.save_possible_simple_key();self.allow_simple_key=_B;self.tokens.append(self.scan_anchor(AnchorToken))
def fetch_tag(self):self.save_possible_simple_key();self.allow_simple_key=_B;self.tokens.append(self.scan_tag())
def fetch_literal(self):self.fetch_block_scalar(style='|')
- def fetch_folded(self):self.fetch_block_scalar(style=_R)
+ def fetch_folded(self):self.fetch_block_scalar(style='>')
def fetch_block_scalar(self,style):self.allow_simple_key=_A;self.remove_possible_simple_key();self.tokens.append(self.scan_block_scalar(style))
- def fetch_single(self):self.fetch_flow_scalar(style=_J)
- def fetch_double(self):self.fetch_flow_scalar(style=_P)
+ def fetch_single(self):self.fetch_flow_scalar(style="'")
+ def fetch_double(self):self.fetch_flow_scalar(style='"')
def fetch_flow_scalar(self,style):self.save_possible_simple_key();self.allow_simple_key=_B;self.tokens.append(self.scan_flow_scalar(style))
def fetch_plain(self):self.save_possible_simple_key();self.allow_simple_key=_B;self.tokens.append(self.scan_plain())
def check_directive(self):
@@ -197,11 +184,11 @@ class Scanner:
return _C
def check_document_start(self):
if self.reader.column==0:
- if self.reader.prefix(3)==_S and self.reader.peek(3)in _THE_END_SPACE_TAB:return _A
+ if self.reader.prefix(3)==_L and self.reader.peek(3)in _THE_END_SPACE_TAB:return _A
return _C
def check_document_end(self):
if self.reader.column==0:
- if self.reader.prefix(3)==_T and self.reader.peek(3)in _THE_END_SPACE_TAB:return _A
+ if self.reader.prefix(3)==_M and self.reader.peek(3)in _THE_END_SPACE_TAB:return _A
return _C
def check_block_entry(self):return self.reader.peek(1)in _THE_END_SPACE_TAB
def check_key(self):
@@ -218,16 +205,16 @@ class Scanner:
return _A
return self.reader.peek(1)in _THE_END_SPACE_TAB
def check_plain(self):
- B='?:';A='\x00 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'"%@`';srp=self.reader.peek;ch=srp()
- if self.scanner_processing_version==(1,1):return ch not in A or srp(1)not in _THE_END_SPACE_TAB and(ch==_N or not self.flow_level and ch in B)
+ A='\x00 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'"%@`';srp=self.reader.peek;ch=srp()
+ if self.scanner_processing_version==(1,1):return ch not in A or srp(1)not in _THE_END_SPACE_TAB and(ch=='-'or not self.flow_level and ch in'?:')
if ch not in A:return _A
ch1=srp(1)
- if ch==_N and ch1 not in _THE_END_SPACE_TAB:return _A
- if ch==_O and bool(self.flow_level)and ch1 not in _SPACE_TAB:return _A
- return srp(1)not in _THE_END_SPACE_TAB and(ch==_N or not self.flow_level and ch in B)
+ if ch=='-'and ch1 not in _THE_END_SPACE_TAB:return _A
+ if ch==':'and bool(self.flow_level)and ch1 not in _SPACE_TAB:return _A
+ return srp(1)not in _THE_END_SPACE_TAB and(ch=='-'or not self.flow_level and ch in'?:')
def scan_to_next_token(self):
srp=self.reader.peek;srf=self.reader.forward
- if self.reader.index==0 and srp()==_f:srf()
+ if self.reader.index==0 and srp()==_U:srf()
found=_B;_the_end=_THE_END
while not found:
while srp()==_D:srf()
@@ -247,10 +234,10 @@ class Scanner:
self.scan_directive_ignored_line(start_mark);return DirectiveToken(name,value,start_mark,end_mark)
def scan_directive_name(self,start_mark):
length=0;srp=self.reader.peek;ch=srp(length)
- while _K<=ch<=_Q or'A'<=ch<='Z'or _U<=ch<='z'or ch in'-_:.':length+=1;ch=srp(length)
- if not length:raise ScannerError(_G,start_mark,_V%utf8(ch),self.reader.get_mark())
+ while '0'<=ch<='9'or'A'<=ch<='Z'or'a'<=ch<='z'or ch in'-_:.':length+=1;ch=srp(length)
+ if not length:raise ScannerError(_G,start_mark,_N%utf8(ch),self.reader.get_mark())
value=self.reader.prefix(length);self.reader.forward(length);ch=srp()
- if ch not in _L:raise ScannerError(_G,start_mark,_V%utf8(ch),self.reader.get_mark())
+ if ch not in _J:raise ScannerError(_G,start_mark,_N%utf8(ch),self.reader.get_mark())
return value
def scan_yaml_directive_value(self,start_mark):
srp=self.reader.peek;srf=self.reader.forward
@@ -258,13 +245,13 @@ class Scanner:
major=self.scan_yaml_directive_number(start_mark)
if srp()!='.':raise ScannerError(_G,start_mark,"expected a digit or '.', but found %r"%utf8(srp()),self.reader.get_mark())
srf();minor=self.scan_yaml_directive_number(start_mark)
- if srp()not in _L:raise ScannerError(_G,start_mark,"expected a digit or ' ', but found %r"%utf8(srp()),self.reader.get_mark())
+ if srp()not in _J:raise ScannerError(_G,start_mark,"expected a digit or ' ', but found %r"%utf8(srp()),self.reader.get_mark())
self.yaml_version=major,minor;return self.yaml_version
def scan_yaml_directive_number(self,start_mark):
srp=self.reader.peek;srf=self.reader.forward;ch=srp()
- if not _K<=ch<=_Q:raise ScannerError(_G,start_mark,'expected a digit, but found %r'%utf8(ch),self.reader.get_mark())
+ if not'0'<=ch<='9':raise ScannerError(_G,start_mark,'expected a digit, but found %r'%utf8(ch),self.reader.get_mark())
length=0
- while _K<=srp(length)<=_Q:length+=1
+ while '0'<=srp(length)<='9':length+=1
value=int(self.reader.prefix(length));srf(length);return value
def scan_tag_directive_value(self,start_mark):
srp=self.reader.peek;srf=self.reader.forward
@@ -273,12 +260,12 @@ class Scanner:
while srp()==_D:srf()
prefix=self.scan_tag_directive_prefix(start_mark);return handle,prefix
def scan_tag_directive_handle(self,start_mark):
- value=self.scan_tag_handle(_g,start_mark);ch=self.reader.peek()
- if ch!=_D:raise ScannerError(_G,start_mark,_Z%utf8(ch),self.reader.get_mark())
+ value=self.scan_tag_handle(_V,start_mark);ch=self.reader.peek()
+ if ch!=_D:raise ScannerError(_G,start_mark,_R%utf8(ch),self.reader.get_mark())
return value
def scan_tag_directive_prefix(self,start_mark):
- value=self.scan_tag_uri(_g,start_mark);ch=self.reader.peek()
- if ch not in _L:raise ScannerError(_G,start_mark,_Z%utf8(ch),self.reader.get_mark())
+ value=self.scan_tag_uri(_V,start_mark);ch=self.reader.peek()
+ if ch not in _J:raise ScannerError(_G,start_mark,_R%utf8(ch),self.reader.get_mark())
return value
def scan_directive_ignored_line(self,start_mark):
srp=self.reader.peek;srf=self.reader.forward
@@ -286,7 +273,7 @@ class Scanner:
if srp()==_F:
while srp()not in _THE_END:srf()
ch=srp()
- if ch not in _THE_END:raise ScannerError(_G,start_mark,_h%utf8(ch),self.reader.get_mark())
+ if ch not in _THE_END:raise ScannerError(_G,start_mark,_W%utf8(ch),self.reader.get_mark())
self.scan_line_break()
def scan_anchor(self,TokenClass):
A='while scanning an %s';srp=self.reader.peek;start_mark=self.reader.get_mark();indicator=srp()
@@ -294,20 +281,20 @@ class Scanner:
else:name='anchor'
self.reader.forward();length=0;ch=srp(length)
while check_anchorname_char(ch):length+=1;ch=srp(length)
- if not length:raise ScannerError(A%(name,),start_mark,_V%utf8(ch),self.reader.get_mark())
+ if not length:raise ScannerError(A%(name,),start_mark,_N%utf8(ch),self.reader.get_mark())
value=self.reader.prefix(length);self.reader.forward(length)
- if ch not in'\x00 \t\r\n\x85\u2028\u2029?:,[]{}%@`':raise ScannerError(A%(name,),start_mark,_V%utf8(ch),self.reader.get_mark())
+ if ch not in'\x00 \t\r\n\x85\u2028\u2029?:,[]{}%@`':raise ScannerError(A%(name,),start_mark,_N%utf8(ch),self.reader.get_mark())
end_mark=self.reader.get_mark();return TokenClass(value,start_mark,end_mark)
def scan_tag(self):
A='tag';srp=self.reader.peek;start_mark=self.reader.get_mark();ch=srp(1)
if ch=='<':
handle=_C;self.reader.forward(2);suffix=self.scan_tag_uri(A,start_mark)
- if srp()!=_R:raise ScannerError('while parsing a tag',start_mark,"expected '>', but found %r"%utf8(srp()),self.reader.get_mark())
+ if srp()!='>':raise ScannerError('while parsing a tag',start_mark,"expected '>', but found %r"%utf8(srp()),self.reader.get_mark())
self.reader.forward()
elif ch in _THE_END_SPACE_TAB:handle=_C;suffix=_H;self.reader.forward()
else:
length=1;use_handle=_B
- while ch not in _L:
+ while ch not in _J:
if ch==_H:use_handle=_A;break
length+=1;ch=srp(length)
handle=_H
@@ -315,29 +302,29 @@ class Scanner:
else:handle=_H;self.reader.forward()
suffix=self.scan_tag_uri(A,start_mark)
ch=srp()
- if ch not in _L:raise ScannerError('while scanning a tag',start_mark,_Z%utf8(ch),self.reader.get_mark())
+ if ch not in _J:raise ScannerError('while scanning a tag',start_mark,_R%utf8(ch),self.reader.get_mark())
value=handle,suffix;end_mark=self.reader.get_mark();return TagToken(value,start_mark,end_mark)
def scan_block_scalar(self,style,rt=_B):
- A='|>';srp=self.reader.peek
- if style==_R:folded=_A
+ srp=self.reader.peek
+ if style=='>':folded=_A
else:folded=_B
chunks=[];start_mark=self.reader.get_mark();self.reader.forward();chomping,increment=self.scan_block_scalar_indicators(start_mark);block_scalar_comment=self.scan_block_scalar_ignored_line(start_mark);min_indent=self.indent+1
if increment is _C:
- if min_indent<1 and(style not in A or self.scanner_processing_version==(1,1)and getattr(self.loader,'top_level_block_style_scalar_no_indent_error_1_1',_B)):min_indent=1
+ if min_indent<1 and(style not in'|>'or self.scanner_processing_version==(1,1)and getattr(self.loader,'top_level_block_style_scalar_no_indent_error_1_1',_B)):min_indent=1
breaks,max_indent,end_mark=self.scan_block_scalar_indentation();indent=max(min_indent,max_indent)
else:
if min_indent<1:min_indent=1
indent=min_indent+increment-1;breaks,end_mark=self.scan_block_scalar_breaks(indent)
line_break=''
while self.reader.column==indent and srp()!=_I:
- chunks.extend(breaks);leading_non_space=srp()not in _M;length=0
+ chunks.extend(breaks);leading_non_space=srp()not in _K;length=0
while srp(length)not in _THE_END:length+=1
chunks.append(self.reader.prefix(length));self.reader.forward(length);line_break=self.scan_line_break();breaks,end_mark=self.scan_block_scalar_breaks(indent)
- if style in A and min_indent==0:
+ if style in'|>'and min_indent==0:
if self.check_document_start()or self.check_document_end():break
if self.reader.column==indent and srp()!=_I:
- if rt and folded and line_break==_E:chunks.append(_i)
- if folded and line_break==_E and leading_non_space and srp()not in _M:
+ if rt and folded and line_break==_E:chunks.append('\x07')
+ if folded and line_break==_E and leading_non_space and srp()not in _K:
if not breaks:chunks.append(_D)
else:chunks.append(line_break)
else:break
@@ -353,25 +340,25 @@ class Scanner:
comment_end_mark=self.reader.get_mark();comment=CommentToken(''.join(trailing),end_mark,comment_end_mark);token.add_post_comment(comment)
return token
def scan_block_scalar_indicators(self,start_mark):
- D='expected indentation indicator in the range 1-9, but found 0';C='0123456789';B='+';A='+-';srp=self.reader.peek;chomping=_C;increment=_C;ch=srp()
- if ch in A:
- if ch==B:chomping=_A
+ B='expected indentation indicator in the range 1-9, but found 0';A='0123456789';srp=self.reader.peek;chomping=_C;increment=_C;ch=srp()
+ if ch in'+-':
+ if ch=='+':chomping=_A
else:chomping=_B
self.reader.forward();ch=srp()
- if ch in C:
+ if ch in A:
increment=int(ch)
- if increment==0:raise ScannerError(_W,start_mark,D,self.reader.get_mark())
+ if increment==0:raise ScannerError(_O,start_mark,B,self.reader.get_mark())
self.reader.forward()
- elif ch in C:
+ elif ch in A:
increment=int(ch)
- if increment==0:raise ScannerError(_W,start_mark,D,self.reader.get_mark())
+ if increment==0:raise ScannerError(_O,start_mark,B,self.reader.get_mark())
self.reader.forward();ch=srp()
- if ch in A:
- if ch==B:chomping=_A
+ if ch in'+-':
+ if ch=='+':chomping=_A
else:chomping=_B
self.reader.forward()
ch=srp()
- if ch not in _L:raise ScannerError(_W,start_mark,'expected chomping or indentation indicators, but found %r'%utf8(ch),self.reader.get_mark())
+ if ch not in _J:raise ScannerError(_O,start_mark,'expected chomping or indentation indicators, but found %r'%utf8(ch),self.reader.get_mark())
return chomping,increment
def scan_block_scalar_ignored_line(self,start_mark):
srp=self.reader.peek;srf=self.reader.forward;prefix='';comment=_C
@@ -380,11 +367,11 @@ class Scanner:
comment=prefix
while srp()not in _THE_END:comment+=srp();srf()
ch=srp()
- if ch not in _THE_END:raise ScannerError(_W,start_mark,_h%utf8(ch),self.reader.get_mark())
+ if ch not in _THE_END:raise ScannerError(_O,start_mark,_W%utf8(ch),self.reader.get_mark())
self.scan_line_break();return comment
def scan_block_scalar_indentation(self):
srp=self.reader.peek;srf=self.reader.forward;chunks=[];max_indent=0;end_mark=self.reader.get_mark()
- while srp()in _j:
+ while srp()in _X:
if srp()!=_D:chunks.append(self.scan_line_break());end_mark=self.reader.get_mark()
else:
srf()
@@ -393,17 +380,17 @@ class Scanner:
def scan_block_scalar_breaks(self,indent):
chunks=[];srp=self.reader.peek;srf=self.reader.forward;end_mark=self.reader.get_mark()
while self.reader.column<indent and srp()==_D:srf()
- while srp()in _X:
+ while srp()in _P:
chunks.append(self.scan_line_break());end_mark=self.reader.get_mark()
while self.reader.column<indent and srp()==_D:srf()
return chunks,end_mark
def scan_flow_scalar(self,style):
- if style==_P:double=_A
+ if style=='"':double=_A
else:double=_B
srp=self.reader.peek;chunks=[];start_mark=self.reader.get_mark();quote=srp();self.reader.forward();chunks.extend(self.scan_flow_scalar_non_spaces(double,start_mark))
while srp()!=quote:chunks.extend(self.scan_flow_scalar_spaces(double,start_mark));chunks.extend(self.scan_flow_scalar_non_spaces(double,start_mark))
self.reader.forward();end_mark=self.reader.get_mark();return ScalarToken(''.join(chunks),_B,start_mark,end_mark,style)
- ESCAPE_REPLACEMENTS={_K:_I,_U:_i,'b':'\x08','t':_a,_a:_a,'n':_E,'v':'\x0b','f':'\x0c','r':'\r','e':'\x1b',_D:_D,_P:_P,'/':'/',_b:_b,'N':'\x85','_':'\xa0','L':'\u2028','P':'\u2029'};ESCAPE_CODES={'x':2,'u':4,'U':8}
+ ESCAPE_REPLACEMENTS={'0':_I,'a':'\x07','b':'\x08','t':'\t','\t':'\t','n':_E,'v':'\x0b','f':'\x0c','r':'\r','e':'\x1b',_D:_D,'"':'"','/':'/','\\':'\\','N':'\x85','_':'\xa0','L':'\u2028','P':'\u2029'};ESCAPE_CODES={'x':2,'u':4,'U':8}
def scan_flow_scalar_non_spaces(self,double,start_mark):
A='while scanning a double-quoted scalar';chunks=[];srp=self.reader.peek;srf=self.reader.forward
while _A:
@@ -411,25 +398,25 @@ class Scanner:
while srp(length)not in' \n\'"\\\x00\t\r\x85\u2028\u2029':length+=1
if length!=0:chunks.append(self.reader.prefix(length));srf(length)
ch=srp()
- if not double and ch==_J and srp(1)==_J:chunks.append(_J);srf(2)
- elif double and ch==_J or not double and ch in'"\\':chunks.append(ch);srf()
- elif double and ch==_b:
+ if not double and ch=="'"and srp(1)=="'":chunks.append("'");srf(2)
+ elif double and ch=="'"or not double and ch in'"\\':chunks.append(ch);srf()
+ elif double and ch=='\\':
srf();ch=srp()
if ch in self.ESCAPE_REPLACEMENTS:chunks.append(self.ESCAPE_REPLACEMENTS[ch]);srf()
elif ch in self.ESCAPE_CODES:
length=self.ESCAPE_CODES[ch];srf()
for k in range(length):
- if srp(k)not in _k:raise ScannerError(A,start_mark,'expected escape sequence of %d hexdecimal numbers, but found %r'%(length,utf8(srp(k))),self.reader.get_mark())
+ if srp(k)not in _Y:raise ScannerError(A,start_mark,'expected escape sequence of %d hexdecimal numbers, but found %r'%(length,utf8(srp(k))),self.reader.get_mark())
code=int(self.reader.prefix(length),16);chunks.append(unichr(code));srf(length)
elif ch in'\n\r\x85\u2028\u2029':self.scan_line_break();chunks.extend(self.scan_flow_scalar_breaks(double,start_mark))
else:raise ScannerError(A,start_mark,'found unknown escape character %r'%utf8(ch),self.reader.get_mark())
else:return chunks
def scan_flow_scalar_spaces(self,double,start_mark):
srp=self.reader.peek;chunks=[];length=0
- while srp(length)in _M:length+=1
+ while srp(length)in _K:length+=1
whitespaces=self.reader.prefix(length);self.reader.forward(length);ch=srp()
- if ch==_I:raise ScannerError(_l,start_mark,'found unexpected end of stream',self.reader.get_mark())
- elif ch in _X:
+ if ch==_I:raise ScannerError(_Z,start_mark,'found unexpected end of stream',self.reader.get_mark())
+ elif ch in _P:
line_break=self.scan_line_break();breaks=self.scan_flow_scalar_breaks(double,start_mark)
if line_break!=_E:chunks.append(line_break)
elif not breaks:chunks.append(_D)
@@ -440,9 +427,9 @@ class Scanner:
chunks=[];srp=self.reader.peek;srf=self.reader.forward
while _A:
prefix=self.reader.prefix(3)
- if(prefix==_S or prefix==_T)and srp(3)in _THE_END_SPACE_TAB:raise ScannerError(_l,start_mark,'found unexpected document separator',self.reader.get_mark())
- while srp()in _M:srf()
- if srp()in _X:chunks.append(self.scan_line_break())
+ if(prefix==_L or prefix==_M)and srp(3)in _THE_END_SPACE_TAB:raise ScannerError(_Z,start_mark,'found unexpected document separator',self.reader.get_mark())
+ while srp()in _K:srf()
+ if srp()in _P:chunks.append(self.scan_line_break())
else:return chunks
def scan_plain(self):
srp=self.reader.peek;srf=self.reader.forward;chunks=[];start_mark=self.reader.get_mark();end_mark=start_mark;indent=self.indent+1;spaces=[]
@@ -451,11 +438,11 @@ class Scanner:
if srp()==_F:break
while _A:
ch=srp(length)
- if ch==_O and srp(length+1)not in _THE_END_SPACE_TAB:0
+ if ch==':'and srp(length+1)not in _THE_END_SPACE_TAB:0
elif ch=='?'and self.scanner_processing_version!=(1,1):0
- elif ch in _THE_END_SPACE_TAB or not self.flow_level and ch==_O and srp(length+1)in _THE_END_SPACE_TAB or self.flow_level and ch in',:?[]{}':break
+ elif ch in _THE_END_SPACE_TAB or not self.flow_level and ch==':'and srp(length+1)in _THE_END_SPACE_TAB or self.flow_level and ch in',:?[]{}':break
length+=1
- if self.flow_level and ch==_O and srp(length+1)not in'\x00 \t\r\n\x85\u2028\u2029,[]{}':srf(length);raise ScannerError('while scanning a plain scalar',start_mark,"found unexpected ':'",self.reader.get_mark(),'Please check http://pyyaml.org/wiki/YAMLColonInFlowContext for details.')
+ if self.flow_level and ch==':'and srp(length+1)not in'\x00 \t\r\n\x85\u2028\u2029,[]{}':srf(length);raise ScannerError('while scanning a plain scalar',start_mark,"found unexpected ':'",self.reader.get_mark(),'Please check http://pyyaml.org/wiki/YAMLColonInFlowContext for details.')
if length==0:break
self.allow_simple_key=_B;chunks.extend(spaces);chunks.append(self.reader.prefix(length));srf(length);end_mark=self.reader.get_mark();spaces=self.scan_plain_spaces(indent,start_mark)
if not spaces or srp()==_F or not self.flow_level and self.reader.column<indent:break
@@ -466,15 +453,15 @@ class Scanner:
srp=self.reader.peek;srf=self.reader.forward;chunks=[];length=0
while srp(length)in _D:length+=1
whitespaces=self.reader.prefix(length);self.reader.forward(length);ch=srp()
- if ch in _X:
+ if ch in _P:
line_break=self.scan_line_break();self.allow_simple_key=_A;prefix=self.reader.prefix(3)
- if(prefix==_S or prefix==_T)and srp(3)in _THE_END_SPACE_TAB:return
+ if(prefix==_L or prefix==_M)and srp(3)in _THE_END_SPACE_TAB:return
breaks=[]
- while srp()in _j:
+ while srp()in _X:
if srp()==_D:srf()
else:
breaks.append(self.scan_line_break());prefix=self.reader.prefix(3)
- if(prefix==_S or prefix==_T)and srp(3)in _THE_END_SPACE_TAB:return
+ if(prefix==_L or prefix==_M)and srp(3)in _THE_END_SPACE_TAB:return
if line_break!=_E:chunks.append(line_break)
elif not breaks:chunks.append(_D)
chunks.extend(breaks)
@@ -482,16 +469,16 @@ class Scanner:
return chunks
def scan_tag_handle(self,name,start_mark):
A="expected '!', but found %r";srp=self.reader.peek;ch=srp()
- if ch!=_H:raise ScannerError(_Y%(name,),start_mark,A%utf8(ch),self.reader.get_mark())
+ if ch!=_H:raise ScannerError(_Q%(name,),start_mark,A%utf8(ch),self.reader.get_mark())
length=1;ch=srp(length)
if ch!=_D:
- while _K<=ch<=_Q or'A'<=ch<='Z'or _U<=ch<='z'or ch in'-_':length+=1;ch=srp(length)
- if ch!=_H:self.reader.forward(length);raise ScannerError(_Y%(name,),start_mark,A%utf8(ch),self.reader.get_mark())
+ while '0'<=ch<='9'or'A'<=ch<='Z'or'a'<=ch<='z'or ch in'-_':length+=1;ch=srp(length)
+ if ch!=_H:self.reader.forward(length);raise ScannerError(_Q%(name,),start_mark,A%utf8(ch),self.reader.get_mark())
length+=1
value=self.reader.prefix(length);self.reader.forward(length);return value
def scan_tag_uri(self,name,start_mark):
srp=self.reader.peek;chunks=[];length=0;ch=srp(length)
- while _K<=ch<=_Q or'A'<=ch<='Z'or _U<=ch<='z'or ch in"-;/?:@&=+$,_.!~*'()[]%"or self.scanner_processing_version>(1,1)and ch==_F:
+ while '0'<=ch<='9'or'A'<=ch<='Z'or'a'<=ch<='z'or ch in"-;/?:@&=+$,_.!~*'()[]%"or self.scanner_processing_version>(1,1)and ch==_F:
if ch=='%':chunks.append(self.reader.prefix(length));self.reader.forward(length);length=0;chunks.append(self.scan_uri_escapes(name,start_mark))
else:length+=1
ch=srp(length)
@@ -503,22 +490,22 @@ class Scanner:
while srp()=='%':
srf()
for k in range(2):
- if srp(k)not in _k:raise ScannerError(_Y%(name,),start_mark,'expected URI escape sequence of 2 hexdecimal numbers, but found %r'%utf8(srp(k)),self.reader.get_mark())
+ if srp(k)not in _Y:raise ScannerError(_Q%(name,),start_mark,'expected URI escape sequence of 2 hexdecimal numbers, but found %r'%utf8(srp(k)),self.reader.get_mark())
if PY3:code_bytes.append(int(self.reader.prefix(2),16))
else:code_bytes.append(chr(int(self.reader.prefix(2),16)))
srf(2)
try:
if PY3:value=bytes(code_bytes).decode(A)
else:value=unicode(b''.join(code_bytes),A)
- except UnicodeDecodeError as exc:raise ScannerError(_Y%(name,),start_mark,str(exc),mark)
+ except UnicodeDecodeError as exc:raise ScannerError(_Q%(name,),start_mark,str(exc),mark)
return value
def scan_line_break(self):
ch=self.reader.peek()
- if ch in _m:
- if self.reader.prefix(2)==_n:self.reader.forward(2)
+ if ch in _a:
+ if self.reader.prefix(2)=='\r\n':self.reader.forward(2)
else:self.reader.forward()
return _E
- elif ch in _o:self.reader.forward();return ch
+ elif ch in _b:self.reader.forward();return ch
return''
class RoundTripScanner(Scanner):
def check_token(self,*choices):
@@ -563,7 +550,7 @@ class RoundTripScanner(Scanner):
self.tokens.append(CommentToken(value,start_mark,end_mark))
def scan_to_next_token(self):
srp=self.reader.peek;srf=self.reader.forward
- if self.reader.index==0 and srp()==_f:srf()
+ if self.reader.index==0 and srp()==_U:srf()
found=_B
while not found:
while srp()==_D:srf()
@@ -592,11 +579,11 @@ class RoundTripScanner(Scanner):
return _C
def scan_line_break(self,empty_line=_B):
ch=self.reader.peek()
- if ch in _m:
- if self.reader.prefix(2)==_n:self.reader.forward(2)
+ if ch in _a:
+ if self.reader.prefix(2)=='\r\n':self.reader.forward(2)
else:self.reader.forward()
return _E
- elif ch in _o:self.reader.forward();return ch
+ elif ch in _b:self.reader.forward();return ch
elif empty_line and ch in'\t ':self.reader.forward();return ch
return''
def scan_block_scalar(self,style,rt=_A):return Scanner.scan_block_scalar(self,style,rt=rt)
\ No newline at end of file
diff --git a/dynaconf/vendor/ruamel/yaml/serializer.py b/dynaconf/vendor/ruamel/yaml/serializer.py
index 158ca05..ffb90a6 100644
--- a/dynaconf/vendor/ruamel/yaml/serializer.py
+++ b/dynaconf/vendor/ruamel/yaml/serializer.py
@@ -1,7 +1,6 @@
from __future__ import absolute_import
-_F='serializer is not opened'
-_E='serializer is closed'
-_D='typ'
+_E='serializer is not opened'
+_D='serializer is closed'
_C=False
_B=True
_A=None
@@ -25,25 +24,25 @@ class Serializer:
@property
def emitter(self):
A=self
- if hasattr(A.dumper,_D):return A.dumper.emitter
+ if hasattr(A.dumper,'typ'):return A.dumper.emitter
return A.dumper._emitter
@property
def resolver(self):
A=self
- if hasattr(A.dumper,_D):A.dumper.resolver
+ if hasattr(A.dumper,'typ'):A.dumper.resolver
return A.dumper._resolver
def open(A):
if A.closed is _A:A.emitter.emit(StreamStartEvent(encoding=A.use_encoding));A.closed=_C
- elif A.closed:raise SerializerError(_E)
+ elif A.closed:raise SerializerError(_D)
else:raise SerializerError('serializer is already opened')
def close(A):
- if A.closed is _A:raise SerializerError(_F)
+ if A.closed is _A:raise SerializerError(_E)
elif not A.closed:A.emitter.emit(StreamEndEvent());A.closed=_B
def serialize(A,node):
B=node
if dbg(DBG_NODE):nprint('Serializing nodes');B.dump()
- if A.closed is _A:raise SerializerError(_F)
- elif A.closed:raise SerializerError(_E)
+ if A.closed is _A:raise SerializerError(_E)
+ elif A.closed:raise SerializerError(_D)
A.emitter.emit(DocumentStartEvent(explicit=A.use_explicit_start,version=A.use_version,tags=A.use_tags));A.anchor_node(B);A.serialize_node(B,_A,_A);A.emitter.emit(DocumentEndEvent(explicit=A.use_explicit_end));A.serialized_nodes={};A.anchors={};A.last_anchor_id=0
def anchor_node(B,node):
A=node
diff --git a/dynaconf/vendor/ruamel/yaml/setup.py b/dynaconf/vendor/ruamel/yaml/setup.py
index 690e172..b46b2d1 100644
--- a/dynaconf/vendor/ruamel/yaml/setup.py
+++ b/dynaconf/vendor/ruamel/yaml/setup.py
@@ -1,13 +1,11 @@
from __future__ import print_function,absolute_import,division,unicode_literals
-_V='bdist_wheel'
-_U='--version'
-_T='extra_packages'
-_S='universal'
-_R='nested'
-_Q='setting distdir {}/{}'
-_P='nsp'
-_O='PYDISTBASE'
-_N='True'
+_T='bdist_wheel'
+_S='--version'
+_R='extra_packages'
+_Q='universal'
+_P='nested'
+_O='setting distdir {}/{}'
+_N='PYDISTBASE'
_M='DVDEBUG'
_L='LICENSE'
_K='Jython'
@@ -47,9 +45,9 @@ if os.environ.get(_M,'')=='':
def debug(*args,**kw):0
else:
def debug(*args,**kw):
- with open(os.environ[_M],'a')as fp:kw1=kw.copy();kw1['file']=fp;print('{:%Y-%d-%mT%H:%M:%S}'.format(datetime.datetime.now()),file=fp,end=' ');print(*args,**kw1)
+ with open(os.environ[_M],'a')as fp:kw1=kw.copy();kw1['file']=fp;print('{:%Y-%d-%mT%H:%M:%S}'.format(datetime.datetime.now()),file=fp,end=' ');print(*(args),**kw1)
def literal_eval(node_or_string):
- _safe_names={'None':_A,_N:_D,'False':_C}
+ _safe_names={'None':_A,'True':_D,'False':_C}
if isinstance(node_or_string,string_type):node_or_string=parse(node_or_string,mode='eval')
if isinstance(node_or_string,Expression):node_or_string=node_or_string.body
else:raise TypeError('only string or AST nodes supported')
@@ -78,8 +76,8 @@ def literal_eval(node_or_string):
func_id=getattr(node.func,'id',_A)
if func_id=='dict':return dict(((k.arg,_convert(k.value))for k in node.keywords))
elif func_id=='set':return set(_convert(node.args[0]))
- elif func_id=='date':return datetime.date(*[_convert(k)for k in node.args])
- elif func_id=='datetime':return datetime.datetime(*[_convert(k)for k in node.args])
+ elif func_id=='date':return datetime.date(*([_convert(k)for k in node.args]))
+ elif func_id=='datetime':return datetime.datetime(*([_convert(k)for k in node.args]))
err=SyntaxError('malformed node or string: '+repr(node));err.filename='<string>';err.lineno=node.lineno;err.offset=node.col_offset;err.text=repr(node);err.node=node;raise err
return _convert(node_or_string)
def _package_data(fn):
@@ -138,25 +136,25 @@ class MyInstallLib(install_lib.install_lib):
return alt_files
class MySdist(_sdist):
def initialize_options(self):
- _sdist.initialize_options(self);dist_base=os.environ.get(_O);fpn=getattr(getattr(self,_P,self),_I,_A)
- if fpn and dist_base:print(_Q.format(dist_base,fpn));self.dist_dir=os.path.join(dist_base,fpn)
+ _sdist.initialize_options(self);dist_base=os.environ.get(_N);fpn=getattr(getattr(self,'nsp',self),_I,_A)
+ if fpn and dist_base:print(_O.format(dist_base,fpn));self.dist_dir=os.path.join(dist_base,fpn)
try:
from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
class MyBdistWheel(_bdist_wheel):
def initialize_options(self):
- _bdist_wheel.initialize_options(self);dist_base=os.environ.get(_O);fpn=getattr(getattr(self,_P,self),_I,_A)
- if fpn and dist_base:print(_Q.format(dist_base,fpn));self.dist_dir=os.path.join(dist_base,fpn)
+ _bdist_wheel.initialize_options(self);dist_base=os.environ.get(_N);fpn=getattr(getattr(self,'nsp',self),_I,_A)
+ if fpn and dist_base:print(_O.format(dist_base,fpn));self.dist_dir=os.path.join(dist_base,fpn)
_bdist_wheel_available=_D
except ImportError:_bdist_wheel_available=_C
class NameSpacePackager:
def __init__(self,pkg_data):
- assert isinstance(pkg_data,dict);self._pkg_data=pkg_data;self.full_package_name=self.pn(self._pkg_data[_I]);self._split=_A;self.depth=self.full_package_name.count(_B);self.nested=self._pkg_data.get(_R,_C)
+ assert isinstance(pkg_data,dict);self._pkg_data=pkg_data;self.full_package_name=self.pn(self._pkg_data[_I]);self._split=_A;self.depth=self.full_package_name.count(_B);self.nested=self._pkg_data.get(_P,_C)
if self.nested:NameSpaceInstaller.install_namespaces=lambda x:_A
self.command=_A;self.python_version();self._pkg=[_A,_A]
if sys.argv[0]==_F and sys.argv[1]==_J and'--single-version-externally-managed'not in sys.argv:
- if os.environ.get('READTHEDOCS',_A)==_N:os.system('pip install .');sys.exit(0)
+ if os.environ.get('READTHEDOCS',_A)=='True':os.system('pip install .');sys.exit(0)
if not os.environ.get('RUAMEL_NO_PIP_INSTALL_CHECK',_C):print('error: you have to install with "pip install ."');sys.exit(1)
- if self._pkg_data.get(_S):Distribution.is_pure=lambda *args:_D
+ if self._pkg_data.get(_Q):Distribution.is_pure=lambda *args:_D
else:Distribution.is_pure=lambda *args:_C
for x in sys.argv:
if x[0]=='-'or x==_F:continue
@@ -175,7 +173,7 @@ class NameSpacePackager:
x=os.path.join(d,_H)
if os.path.exists(x):
pd=_package_data(x)
- if pd.get(_R,_C):skip.append(d);continue
+ if pd.get(_P,_C):skip.append(d);continue
self._split.append(self.full_package_name+_B+d)
if sys.version_info<(3,):self._split=[y.encode(_E)if isinstance(y,unicode)else y for y in self._split]
if skip:0
@@ -191,7 +189,7 @@ class NameSpacePackager:
@property
def package_dir(self):
d={self.full_package_name:_B}
- if _T in self._pkg_data:return d
+ if _R in self._pkg_data:return d
if len(self.split)>1:d[self.split[0]]=self.namespace_directories(1)[0]
return d
def create_dirs(self):
@@ -266,9 +264,9 @@ class NameSpacePackager:
def description(self):return self._pkg_data['description']
@property
def status(self):
- A='β';status=self._pkg_data.get('status',A).lower()
+ status=self._pkg_data.get('status','β').lower()
if status in['α','alpha']:return 3,'Alpha'
- elif status in[A,'beta']:return 4,'Beta'
+ elif status in['β','beta']:return 4,'Beta'
elif'stable'in status.lower():return 5,'Production/Stable'
raise NotImplementedError
@property
@@ -319,14 +317,14 @@ class NameSpacePackager:
if isinstance(k,unicode):pd[str(k)]=pd.pop(k)
return pd
@property
- def packages(self):s=self.split;return s+self._pkg_data.get(_T,[])
+ def packages(self):s=self.split;return s+self._pkg_data.get(_R,[])
@property
def python_requires(self):return self._pkg_data.get('python_requires',_A)
@property
def ext_modules(self):
I='Exception:';H='link error';G='compile error:';F='Windows';E='lib';D='src';C='ext_modules';B='test';A='name'
if hasattr(self,'_ext_modules'):return self._ext_modules
- if _U in sys.argv:return _A
+ if _S in sys.argv:return _A
if platform.python_implementation()==_K:return _A
try:
plat=sys.argv.index('--plat-name')
@@ -363,13 +361,13 @@ class NameSpacePackager:
@property
def test_suite(self):return self._pkg_data.get('test_suite')
def wheel(self,kw,setup):
- if _V not in sys.argv:return _C
+ if _T not in sys.argv:return _C
file_name='setup.cfg'
if os.path.exists(file_name):return _C
with open(file_name,'w')as fp:
if os.path.exists(_L):fp.write('[metadata]\nlicense-file = LICENSE\n')
else:print('\n\n>>>>>> LICENSE file not found <<<<<\n\n')
- if self._pkg_data.get(_S):fp.write('[bdist_wheel]\nuniversal = 1\n')
+ if self._pkg_data.get(_Q):fp.write('[bdist_wheel]\nuniversal = 1\n')
try:setup(**kw)
except Exception:raise
finally:os.remove(file_name)
@@ -380,9 +378,9 @@ def main():
nsp=NameSpacePackager(pkg_data);nsp.check();nsp.create_dirs();MySdist.nsp=nsp
if pkg_data.get(A):MySdist.tarfmt=pkg_data.get(A)
cmdclass=dict(install_lib=MyInstallLib,sdist=MySdist)
- if _bdist_wheel_available:MyBdistWheel.nsp=nsp;cmdclass[_V]=MyBdistWheel
+ if _bdist_wheel_available:MyBdistWheel.nsp=nsp;cmdclass[_T]=MyBdistWheel
kw=dict(name=nsp.full_package_name,namespace_packages=nsp.namespace_packages,version=version_str,packages=nsp.packages,python_requires=nsp.python_requires,url=nsp.url,author=nsp.author,author_email=nsp.author_email,cmdclass=cmdclass,package_dir=nsp.package_dir,entry_points=nsp.entry_points(),description=nsp.description,install_requires=nsp.install_requires,extras_require=nsp.extras_require,license=nsp.license,classifiers=nsp.classifiers,keywords=nsp.keywords,package_data=nsp.package_data,ext_modules=nsp.ext_modules,test_suite=nsp.test_suite)
- if _U not in sys.argv and('--verbose'in sys.argv or dump_kw in sys.argv):
+ if _S not in sys.argv and('--verbose'in sys.argv or dump_kw in sys.argv):
for k in sorted(kw):v=kw[k];print(' "{0}": "{1}",'.format(k,v))
if dump_kw in sys.argv:sys.argv.remove(dump_kw)
try:
diff --git a/dynaconf/vendor/ruamel/yaml/timestamp.py b/dynaconf/vendor/ruamel/yaml/timestamp.py
index fafab94..cd1633b 100644
--- a/dynaconf/vendor/ruamel/yaml/timestamp.py
+++ b/dynaconf/vendor/ruamel/yaml/timestamp.py
@@ -1,8 +1,7 @@
from __future__ import print_function,absolute_import,division,unicode_literals
-_A=False
import datetime,copy
-if _A:from typing import Any,Dict,Optional,List
+if False:from typing import Any,Dict,Optional,List
class TimeStamp(datetime.datetime):
- def __init__(A,*B,**C):A._yaml=dict(t=_A,tz=None,delta=0)
- def __new__(A,*B,**C):return datetime.datetime.__new__(A,*B,**C)
+ def __init__(A,*B,**C):A._yaml=dict(t=False,tz=None,delta=0)
+ def __new__(A,*B,**C):return datetime.datetime.__new__(A,*(B),**C)
def __deepcopy__(A,memo):B=TimeStamp(A.year,A.month,A.day,A.hour,A.minute,A.second);B._yaml=copy.deepcopy(A._yaml);return B
\ No newline at end of file
diff --git a/dynaconf/vendor/ruamel/yaml/util.py b/dynaconf/vendor/ruamel/yaml/util.py
index c8c1c6b..24f5cfc 100644
--- a/dynaconf/vendor/ruamel/yaml/util.py
+++ b/dynaconf/vendor/ruamel/yaml/util.py
@@ -1,17 +1,16 @@
from __future__ import absolute_import,print_function
-_B='lazy_self'
-_A=' '
+_A='lazy_self'
from functools import partial
import re
from .compat import text_type,binary_type
if False:from typing import Any,Dict,Optional,List,Text;from .compat import StreamTextType
class LazyEval:
def __init__(A,func,*C,**D):
- def B():B=func(*C,**D);object.__setattr__(A,_B,lambda:B);return B
- object.__setattr__(A,_B,B)
+ def B():B=func(*(C),**D);object.__setattr__(A,_A,lambda:B);return B
+ object.__setattr__(A,_A,B)
def __getattribute__(B,name):
- A=object.__getattribute__(B,_B)
- if name==_B:return A
+ A=object.__getattribute__(B,_A)
+ if name==_A:return A
return getattr(A(),name)
def __setattr__(A,name,value):setattr(A.lazy_self(),name,value)
RegExp=partial(LazyEval,re.compile)
@@ -19,7 +18,7 @@ def load_yaml_guess_indent(stream,**N):
D=stream;B=None;from .main import round_trip_load as O
def K(l):
A=0
- while A<len(l)and l[A]==_A:A+=1
+ while A<len(l)and l[A]==' ':A+=1
return A
if isinstance(D,text_type):F=D
elif isinstance(D,binary_type):F=D.decode('utf-8')
@@ -29,7 +28,7 @@ def load_yaml_guess_indent(stream,**N):
J=C.rstrip();P=J.lstrip()
if P.startswith('- '):
M=K(C);L=M-I;A=M+1
- while C[A]==_A:A+=1
+ while C[A]==' ':A+=1
if C[A]=='#':continue
H=A-I;break
if G is B and E is not B and J:
@@ -38,7 +37,7 @@ def load_yaml_guess_indent(stream,**N):
if A>E:G=A-E
if J.endswith(':'):
I=K(C);A=0
- while C[A]==_A:A+=1
+ while C[A]==' ':A+=1
E=A;continue
E=B
if H is B and G is not B:H=G
@@ -52,18 +51,18 @@ def configobj_walker(cfg):
for A in B.final_comment:
if A.strip():yield A
def _walk_section(s,level=0):
- L=' ';I="'";H='\n';F=level;from configobj import Section as J;assert isinstance(s,J);D=L*F
+ H=level;G="'";F='\n';from configobj import Section as J;assert isinstance(s,J);D=' '*H
for A in s.scalars:
for B in s.comments[A]:yield D+B.strip()
C=s[A]
- if H in C:G=D+L;C='|\n'+G+C.strip().replace(H,H+G)
- elif':'in C:C=I+C.replace(I,"''")+I
+ if F in C:I=D+' ';C='|\n'+I+C.strip().replace(F,F+I)
+ elif':'in C:C=G+C.replace(G,"''")+G
E='{0}{1}: {2}'.format(D,A,C);B=s.inline_comments[A]
- if B:E+=_A+B
+ if B:E+=' '+B
yield E
for A in s.sections:
for B in s.comments[A]:yield D+B.strip()
E='{0}{1}:'.format(D,A);B=s.inline_comments[A]
- if B:E+=_A+B
+ if B:E+=' '+B
yield E
- for K in _walk_section(s[A],level=F+1):yield K
\ No newline at end of file
+ for K in _walk_section(s[A],level=H+1):yield K
\ No newline at end of file
diff --git a/dynaconf/vendor/toml/decoder.py b/dynaconf/vendor/toml/decoder.py
index 315c36e..e003655 100644
--- a/dynaconf/vendor/toml/decoder.py
+++ b/dynaconf/vendor/toml/decoder.py
@@ -1,12 +1,6 @@
-_W='Reserved escape sequence used'
-_V='\\U'
-_U='false'
-_T='true'
-_S='\t'
-_R='}'
-_Q='+'
-_P='_'
-_O='-'
+_Q='Reserved escape sequence used'
+_P='false'
+_O='true'
_N=','
_M=']'
_L=' '
@@ -56,12 +50,12 @@ class CommentValue:
def _strictly_valid_num(n):
n=n.strip()
if not n:return _A
- if n[0]==_P:return _A
- if n[-1]==_P:return _A
+ if n[0]=='_':return _A
+ if n[-1]=='_':return _A
if'_.'in n or'._'in n:return _A
if len(n)==1:return _B
if n[0]=='0'and n[1]not in[_F,'o','b','x']:return _A
- if n[0]==_Q or n[0]==_O:
+ if n[0]=='+'or n[0]=='-':
n=n[1:]
if len(n)>1 and n[0]=='0'and n[1]!=_F:return _A
if'__'in n:return _A
@@ -84,7 +78,7 @@ def load(f,_dict=dict,decoder=_E):
except AttributeError:raise TypeError('You can only load a file descriptor, filename or list')
_groupname_re=re.compile('^[A-Za-z0-9_-]+$')
def loads(s,_dict=dict,decoder=_E):
- q="Invalid group name '";K=decoder;d=[]
+ o="Invalid group name '";K=decoder;d=[]
if K is _E:K=TomlDecoder(_dict)
e=K.get_empty_table();G=e
if not isinstance(s,basestring):raise TypeError('Expecting something like a string')
@@ -104,7 +98,7 @@ def loads(s,_dict=dict,decoder=_E):
elif O==1:
if E.isspace():O=2;continue
elif E==_F:W=_B;continue
- elif E.isalnum()or E==_P or E==_O:continue
+ elif E.isalnum()or E=='_'or E=='-':continue
elif W and B[A-1]==_F and(E==_C or E==_D):J=_B;Q=E;continue
elif O==2:
if E.isspace():
@@ -168,7 +162,7 @@ def loads(s,_dict=dict,decoder=_E):
elif b:B[A]=_L
else:V=_B
k+=1
- elif V and B[A]!=_L and B[A]!=_S:
+ elif V and B[A]!=_L and B[A]!='\t':
V=_A
if not U and not L:
if B[A]==_J:raise TomlDecodeError('Found empty keyname. ',I,A)
@@ -188,9 +182,9 @@ def loads(s,_dict=dict,decoder=_E):
if D[0]==_I:h=C[-1]==_M
elif len(C)>2:h=C[-1]==D[0]and C[-2]==D[0]and C[-3]==D[0]
if h:
- try:o,r=K.load_value(D)
+ try:p,r=K.load_value(D)
except ValueError as Y:raise TomlDecodeError(str(Y),I,N)
- G[T]=o;T=_E;D=''
+ G[T]=p;T=_E;D=''
else:
F=len(D)-1
while F>-1 and D[F]==_H:P=not P;F-=1
@@ -202,8 +196,8 @@ def loads(s,_dict=dict,decoder=_E):
if len(C)==1:raise TomlDecodeError('Opening key group bracket on line by itself.',I,N)
if C[1]==_I:L=_B;C=C[2:];Z=']]'
else:C=C[1:];Z=_M
- A=1;p=K._get_split_on_quotes(C);i=_A
- for m in p:
+ A=1;q=K._get_split_on_quotes(C);i=_A
+ for m in q:
if not i and Z in m:break
A+=m.count(Z);i=not i
C=C.split(Z,A)
@@ -215,10 +209,10 @@ def loads(s,_dict=dict,decoder=_E):
a=H[A];R=A+1
while not a[0]==a[-1]:
R+=1
- if R>len(H)+2:raise TomlDecodeError(q+a+"' Something "+'went wrong.',I,N)
+ if R>len(H)+2:raise TomlDecodeError(o+a+"' Something "+'went wrong.',I,N)
a=_F.join(H[A:R]).strip()
H[A]=a[1:-1];H[A+1:R]=[]
- elif not _groupname_re.match(H[A]):raise TomlDecodeError(q+H[A]+"'. Try quoting it.",I,N)
+ elif not _groupname_re.match(H[A]):raise TomlDecodeError(o+H[A]+"'. Try quoting it.",I,N)
A+=1
G=e
for A in _range(len(H)):
@@ -246,7 +240,7 @@ def loads(s,_dict=dict,decoder=_E):
try:G=G[-1]
except KeyError:pass
elif C[0]==_K:
- if C[-1]!=_R:raise TomlDecodeError('Line breaks are not allowed in inlineobjects',I,N)
+ if C[-1]!='}':raise TomlDecodeError('Line breaks are not allowed in inlineobjects',I,N)
try:K.load_inline_object(C,G,T,P)
except ValueError as Y:raise TomlDecodeError(str(Y),I,N)
elif _J in C:
@@ -255,21 +249,21 @@ def loads(s,_dict=dict,decoder=_E):
if n is not _E:T,D,P=n
return e
def _load_date(val):
- I='Z';A=val;G=0;F=_E
+ A=val;G=0;F=_E
try:
if len(A)>19:
if A[19]==_F:
- if A[-1].upper()==I:C=A[20:-1];D=I
+ if A[-1].upper()=='Z':C=A[20:-1];D='Z'
else:
B=A[20:]
- if _Q in B:E=B.index(_Q);C=B[:E];D=B[E:]
- elif _O in B:E=B.index(_O);C=B[:E];D=B[E:]
+ if'+'in B:E=B.index('+');C=B[:E];D=B[E:]
+ elif'-'in B:E=B.index('-');C=B[:E];D=B[E:]
else:D=_E;C=B
if D is not _E:F=TomlTz(D)
G=int(int(C)*10**(6-len(C)))
else:F=TomlTz(A[19:])
except ValueError:F=_E
- if _O not in A[1:]:return _E
+ if'-'not in A[1:]:return _E
try:
if len(A)==10:H=datetime.date(int(A[:4]),int(A[5:7]),int(A[8:10]))
else:H=datetime.datetime(int(A[:4]),int(A[5:7]),int(A[8:10]),int(A[11:13]),int(A[14:16]),int(A[17:19]),G,F)
@@ -284,14 +278,14 @@ def _load_unicode_escapes(v,hexbytes,prefix):
while A>-1 and D[A]==_H:C=not C;A-=1
v+=E;v+=D;continue
B='';A=0;F=4
- if E==_V:F=8
+ if E=='\\U':F=8
B=''.join(D[A:A+F]).lower()
if B.strip('0123456789abcdef'):raise ValueError(G+B)
if B[0]=='d'and B[1].strip('01234567'):raise ValueError(G+B+'. Only scalar unicode points are allowed.')
v+=unichr(int(B,16));v+=unicode(D[len(B):])
return v
_escapes=['0','b','f','n','r','t',_C]
-_escapedchars=['\x00','\x08','\x0c',_G,'\r',_S,_C]
+_escapedchars=['\x00','\x08','\x0c',_G,'\r','\t',_C]
_escape_to_escapedchars=dict(zip(_escapes,_escapedchars))
def _unescape(v):
A=0;B=_A
@@ -301,7 +295,7 @@ def _unescape(v):
if v[A]in _escapes:v=v[:A-1]+_escape_to_escapedchars[v[A]]+v[A+1:]
elif v[A]==_H:v=v[:A-1]+v[A:]
elif v[A]=='u'or v[A]=='U':A+=1
- else:raise ValueError(_W)
+ else:raise ValueError(_Q)
continue
elif v[A]==_H:B=_B
A+=1
@@ -321,7 +315,7 @@ class TomlDecoder:
try:H,A=C.split(_J,1)
except ValueError:raise ValueError('Invalid inline table encountered')
A=A.strip()
- if A[0]==A[-1]and A[0]in(_C,_D)or(A[0]in'-0123456789'or A in(_T,_U)or A[0]==_I and A[-1]==_M or A[0]==_K and A[-1]==_R):D.append(C)
+ if A[0]==A[-1]and A[0]in(_C,_D)or(A[0]in'-0123456789'or A in(_O,_P)or A[0]==_I and A[-1]==_M or A[0]==_K and A[-1]=='}'):D.append(C)
elif len(B)>0:B[0]=C+_N+B[0]
else:raise ValueError('Invalid inline table value encountered')
for F in D:
@@ -340,19 +334,19 @@ class TomlDecoder:
else:C+=E.split(_D);D=not D
return C
def load_line(E,line,currentlevel,multikey,multibackslash):
- S='Duplicate keys!';L=multikey;K=line;G=multibackslash;D=currentlevel;H=1;M=E._get_split_on_quotes(K);C=_A
+ P='Duplicate keys!';L=multikey;K=line;G=multibackslash;D=currentlevel;H=1;M=E._get_split_on_quotes(K);C=_A
for F in M:
if not C and _J in F:break
H+=F.count(_J);C=not C
A=K.split(_J,H);N=_strictly_valid_num(A[-1])
- if _number_with_underscores.match(A[-1]):A[-1]=A[-1].replace(_P,'')
- while len(A[-1])and(A[-1][0]!=_L and A[-1][0]!=_S and A[-1][0]!=_D and A[-1][0]!=_C and A[-1][0]!=_I and A[-1][0]!=_K and A[-1].strip()!=_T and A[-1].strip()!=_U):
+ if _number_with_underscores.match(A[-1]):A[-1]=A[-1].replace('_','')
+ while len(A[-1])and(A[-1][0]!=_L and A[-1][0]!='\t'and A[-1][0]!=_D and A[-1][0]!=_C and A[-1][0]!=_I and A[-1][0]!=_K and A[-1].strip()!=_O and A[-1].strip()!=_P):
try:float(A[-1]);break
except ValueError:pass
if _load_date(A[-1])is not _E:break
if TIME_RE.match(A[-1]):break
- H+=1;P=A[-1];A=K.split(_J,H)
- if P==A[-1]:raise ValueError('Invalid date or number')
+ H+=1;Q=A[-1];A=K.split(_J,H)
+ if Q==A[-1]:raise ValueError('Invalid date or number')
if N:N=_strictly_valid_num(A[-1])
A=[_J.join(A[:-1]).strip(),A[-1].strip()]
if _F in A[0]:
@@ -370,18 +364,18 @@ class TomlDecoder:
D=D[I]
A[0]=B[-1].strip()
elif(A[0][0]==_C or A[0][0]==_D)and A[0][-1]==A[0][0]:A[0]=_unescape(A[0][1:-1])
- J,Q=E._load_line_multiline_str(A[1])
+ J,R=E._load_line_multiline_str(A[1])
if J>-1:
- while J>-1 and A[1][J+Q]==_H:G=not G;J-=1
+ while J>-1 and A[1][J+R]==_H:G=not G;J-=1
if G:O=A[1][:-1]
else:O=A[1]+_G
L=A[0]
- else:R,T=E.load_value(A[1],N)
- try:D[A[0]];raise ValueError(S)
- except TypeError:raise ValueError(S)
+ else:S,T=E.load_value(A[1],N)
+ try:D[A[0]];raise ValueError(P)
+ except TypeError:raise ValueError(P)
except KeyError:
if L:return L,O,G
- else:D[A[0]]=R
+ else:D[A[0]]=S
def _load_line_multiline_str(C,p):
B=0
if len(p)<3:return-1,B
@@ -394,10 +388,10 @@ class TomlDecoder:
if len(p)>5 and p[-1]==p[0]and p[-2]==p[0]and p[-3]==p[0]:return-1,B
return len(p)-1,B
def load_value(E,v,strictly_valid=_B):
- a='float';Z='int';Y='bool'
+ V='float';U='int';T='bool'
if not v:raise ValueError('Empty value is invalid')
- if v==_T:return _B,Y
- elif v==_U:return _A,Y
+ if v==_O:return _B,T
+ elif v==_P:return _A,T
elif v[0]==_C or v[0]==_D:
F=v[0];B=v[1:].split(F);G=_A;H=0
if len(B)>1 and B[0]==''and B[1]=='':B=B[2:];G=_B
@@ -417,34 +411,34 @@ class TomlDecoder:
elif not G or H>1:I=_B
else:H=0
if F==_C:
- T=v.split(_H)[1:];C=_A
- for A in T:
+ W=v.split(_H)[1:];C=_A
+ for A in W:
if A=='':C=not C
else:
- if A[0]not in _escapes and(A[0]!='u'and A[0]!='U'and not C):raise ValueError(_W)
+ if A[0]not in _escapes and(A[0]!='u'and A[0]!='U'and not C):raise ValueError(_Q)
if C:C=_A
- for L in ['\\u',_V]:
+ for L in ['\\u','\\U']:
if L in v:O=v.split(L);v=_load_unicode_escapes(O[0],O[1:],L)
v=_unescape(v)
if len(v)>1 and v[1]==F and(len(v)<3 or v[1]==v[2]):v=v[2:-2]
return v[1:-1],'str'
elif v[0]==_I:return E.load_array(v),'array'
elif v[0]==_K:P=E.get_empty_inline_table();E.load_inline_object(v,P);return P,'inline_object'
- elif TIME_RE.match(v):U,V,W,b,Q=TIME_RE.match(v).groups();X=datetime.time(int(U),int(V),int(W),int(Q)if Q else 0);return X,'time'
+ elif TIME_RE.match(v):X,Y,Z,b,Q=TIME_RE.match(v).groups();a=datetime.time(int(X),int(Y),int(Z),int(Q)if Q else 0);return a,'time'
else:
R=_load_date(v)
if R is not _E:return R,'date'
if not strictly_valid:raise ValueError('Weirdness with leading zeroes or underscores in your number.')
- D=Z;S=_A
- if v[0]==_O:S=_B;v=v[1:]
- elif v[0]==_Q:v=v[1:]
- v=v.replace(_P,'');M=v.lower()
+ D=U;S=_A
+ if v[0]=='-':S=_B;v=v[1:]
+ elif v[0]=='+':v=v[1:]
+ v=v.replace('_','');M=v.lower()
if _F in v or'x'not in v and('e'in v or'E'in v):
if _F in v and v.split(_F,1)[1]=='':raise ValueError('This float is missing digits after the point')
if v[0]not in'0123456789':raise ValueError("This float doesn't have a leading digit")
- v=float(v);D=a
- elif len(M)==3 and(M=='inf'or M=='nan'):v=float(v);D=a
- if D==Z:v=int(v,0)
+ v=float(v);D=V
+ elif len(M)==3 and(M=='inf'or M=='nan'):v=float(v);D=V
+ if D==U:v=int(v,0)
if S:return 0-v,D
return v,D
def bounded_string(C,s):
@@ -473,8 +467,8 @@ class TomlDecoder:
while K>-1 and a[K]==_H:F=not F;K-=1
F=not F
if not F and a[A]==_K:J+=1
- if F or a[A]!=_R:A+=1;continue
- elif a[A]==_R and J>1:J-=1;A+=1;continue
+ if F or a[A]!='}':A+=1;continue
+ elif a[A]=='}'and J>1:J-=1;A+=1;continue
A+=1;O.append(a[E:A]);E=A+1
while E<len(a[1:])and a[E]!=_K:E+=1
A=E+1
diff --git a/dynaconf/vendor/toml/encoder.py b/dynaconf/vendor/toml/encoder.py
index b77dc20..5cf8b06 100644
--- a/dynaconf/vendor/toml/encoder.py
+++ b/dynaconf/vendor/toml/encoder.py
@@ -1,9 +1,5 @@
-_G=']\n'
-_F=' = '
-_E='\n'
-_D=False
-_C='['
-_B='.'
+_C=' = '
+_B=False
_A=None
import datetime,re,sys
from decimal import Decimal
@@ -24,47 +20,47 @@ def dumps(o,encoder=_A):
for E in D:
B,F=C.dump_sections(D[E],E)
if B or not B and not F:
- if A and A[-2:]!='\n\n':A+=_E
- A+=_C+E+_G
+ if A and A[-2:]!='\n\n':A+='\n'
+ A+='['+E+']\n'
if B:A+=B
- for J in F:I[E+_B+J]=F[J]
+ for J in F:I[E+'.'+J]=F[J]
D=I
return A
def _dump_str(v):
- G="'";F='\\';C='"'
+ D='\\';B='"'
if sys.version_info<(3,)and hasattr(v,'decode')and isinstance(v,str):v=v.decode('utf-8')
v='%r'%v
if v[0]=='u':v=v[1:]
- D=v.startswith(G)
- if D or v.startswith(C):v=v[1:-1]
- if D:v=v.replace("\\'",G);v=v.replace(C,'\\"')
+ E=v.startswith("'")
+ if E or v.startswith(B):v=v[1:-1]
+ if E:v=v.replace("\\'","'");v=v.replace(B,'\\"')
v=v.split('\\x')
while len(v)>1:
A=-1
if not v[0]:v=v[1:]
- v[0]=v[0].replace('\\\\',F);B=v[0][A]!=F
- while v[0][:A]and v[0][A]==F:B=not B;A-=1
- if B:E='x'
- else:E='u00'
- v=[v[0]+E+v[1]]+v[2:]
- return unicode(C+v[0]+C)
+ v[0]=v[0].replace('\\\\',D);C=v[0][A]!=D
+ while v[0][:A]and v[0][A]==D:C=not C;A-=1
+ if C:F='x'
+ else:F='u00'
+ v=[v[0]+F+v[1]]+v[2:]
+ return unicode(B+v[0]+B)
def _dump_float(v):return '{}'.format(v).replace('e+0','e+').replace('e-0','e-')
def _dump_time(v):
A=v.utcoffset()
if A is _A:return v.isoformat()
return v.isoformat()[:-6]
class TomlEncoder:
- def __init__(A,_dict=dict,preserve=_D):A._dict=_dict;A.preserve=preserve;A.dump_funcs={str:_dump_str,unicode:_dump_str,list:A.dump_list,bool:lambda v:unicode(v).lower(),int:lambda v:v,float:_dump_float,Decimal:_dump_float,datetime.datetime:lambda v:v.isoformat().replace('+00:00','Z'),datetime.time:_dump_time,datetime.date:lambda v:v.isoformat()}
+ def __init__(A,_dict=dict,preserve=_B):A._dict=_dict;A.preserve=preserve;A.dump_funcs={str:_dump_str,unicode:_dump_str,list:A.dump_list,bool:lambda v:unicode(v).lower(),int:lambda v:v,float:_dump_float,Decimal:_dump_float,datetime.datetime:lambda v:v.isoformat().replace('+00:00','Z'),datetime.time:_dump_time,datetime.date:lambda v:v.isoformat()}
def get_empty_table(A):return A._dict()
def dump_list(B,v):
- A=_C
+ A='['
for C in v:A+=' '+unicode(B.dump_value(C))+','
A+=']';return A
def dump_inline_table(B,section):
A=section;C=''
if isinstance(A,dict):
D=[]
- for (E,F) in A.items():G=B.dump_inline_table(F);D.append(E+_F+G)
+ for (E,F) in A.items():G=B.dump_inline_table(F);D.append(E+_C+G)
C+='{ '+', '.join(D)+' }\n';return C
else:return unicode(B.dump_value(A))
def dump_value(B,v):
@@ -73,44 +69,44 @@ class TomlEncoder:
return A(v)if A is not _A else B.dump_funcs[str](v)
def dump_sections(C,o,sup):
D=sup;F=''
- if D!=''and D[-1]!=_B:D+=_B
+ if D!=''and D[-1]!='.':D+='.'
M=C._dict();G=''
for A in o:
A=unicode(A);B=A
if not re.match('^[A-Za-z0-9_-]+$',A):B=_dump_str(A)
if not isinstance(o[A],dict):
- N=_D
+ N=_B
if isinstance(o[A],list):
for L in o[A]:
if isinstance(L,dict):N=True
if N:
for L in o[A]:
- H=_E;G+='[['+D+B+']]\n';I,J=C.dump_sections(L,D+B)
+ H='\n';G+='[['+D+B+']]\n';I,J=C.dump_sections(L,D+B)
if I:
- if I[0]==_C:H+=I
+ if I[0]=='[':H+=I
else:G+=I
while J:
O=C._dict()
for K in J:
- E,P=C.dump_sections(J[K],D+B+_B+K)
- if E:H+=_C+D+B+_B+K+_G;H+=E
- for E in P:O[K+_B+E]=P[E]
+ E,P=C.dump_sections(J[K],D+B+'.'+K)
+ if E:H+='['+D+B+'.'+K+']\n';H+=E
+ for E in P:O[K+'.'+E]=P[E]
J=O
G+=H
- elif o[A]is not _A:F+=B+_F+unicode(C.dump_value(o[A]))+_E
- elif C.preserve and isinstance(o[A],InlineTableDict):F+=B+_F+C.dump_inline_table(o[A])
+ elif o[A]is not _A:F+=B+_C+unicode(C.dump_value(o[A]))+'\n'
+ elif C.preserve and isinstance(o[A],InlineTableDict):F+=B+_C+C.dump_inline_table(o[A])
else:M[B]=o[A]
F+=G;return F,M
class TomlPreserveInlineDictEncoder(TomlEncoder):
def __init__(A,_dict=dict):super(TomlPreserveInlineDictEncoder,A).__init__(_dict,True)
class TomlArraySeparatorEncoder(TomlEncoder):
- def __init__(B,_dict=dict,preserve=_D,separator=','):
+ def __init__(B,_dict=dict,preserve=_B,separator=','):
A=separator;super(TomlArraySeparatorEncoder,B).__init__(_dict,preserve)
if A.strip()=='':A=','+A
elif A.strip(' \t\n\r,'):raise ValueError('Invalid separator for arrays')
B.separator=A
def dump_list(D,v):
- B=[];C=_C
+ B=[];C='['
for A in v:B.append(D.dump_value(A))
while B!=[]:
E=[]
@@ -121,10 +117,10 @@ class TomlArraySeparatorEncoder(TomlEncoder):
B=E
C+=']';return C
class TomlNumpyEncoder(TomlEncoder):
- def __init__(A,_dict=dict,preserve=_D):import numpy as B;super(TomlNumpyEncoder,A).__init__(_dict,preserve);A.dump_funcs[B.float16]=_dump_float;A.dump_funcs[B.float32]=_dump_float;A.dump_funcs[B.float64]=_dump_float;A.dump_funcs[B.int16]=A._dump_int;A.dump_funcs[B.int32]=A._dump_int;A.dump_funcs[B.int64]=A._dump_int
+ def __init__(A,_dict=dict,preserve=_B):import numpy as B;super(TomlNumpyEncoder,A).__init__(_dict,preserve);A.dump_funcs[B.float16]=_dump_float;A.dump_funcs[B.float32]=_dump_float;A.dump_funcs[B.float64]=_dump_float;A.dump_funcs[B.int16]=A._dump_int;A.dump_funcs[B.int32]=A._dump_int;A.dump_funcs[B.int64]=A._dump_int
def _dump_int(A,v):return '{}'.format(int(v))
class TomlPreserveCommentEncoder(TomlEncoder):
- def __init__(A,_dict=dict,preserve=_D):from dynaconf.vendor.toml.decoder import CommentValue as B;super(TomlPreserveCommentEncoder,A).__init__(_dict,preserve);A.dump_funcs[B]=lambda v:v.dump(A.dump_value)
+ def __init__(A,_dict=dict,preserve=_B):from dynaconf.vendor.toml.decoder import CommentValue as B;super(TomlPreserveCommentEncoder,A).__init__(_dict,preserve);A.dump_funcs[B]=lambda v:v.dump(A.dump_value)
class TomlPathlibEncoder(TomlEncoder):
def _dump_pathlib_path(A,v):return _dump_str(str(v))
def dump_value(A,v):
diff --git a/dynaconf/vendor/tomllib/__init__.py b/dynaconf/vendor/tomllib/__init__.py
new file mode 100644
index 0000000..663689e
--- /dev/null
+++ b/dynaconf/vendor/tomllib/__init__.py
@@ -0,0 +1,4 @@
+__all__='loads','load','TOMLDecodeError','dump','dumps'
+from ._parser import TOMLDecodeError,load,loads
+from ._writer import dump,dumps
+TOMLDecodeError.__module__=__name__
\ No newline at end of file
diff --git a/dynaconf/vendor/tomllib/_parser.py b/dynaconf/vendor/tomllib/_parser.py
new file mode 100644
index 0000000..b0c4912
--- /dev/null
+++ b/dynaconf/vendor/tomllib/_parser.py
@@ -0,0 +1,298 @@
+from __future__ import annotations
+_H='flags'
+_G='Cannot overwrite a value'
+_F='recursive_flags'
+_E='nested'
+_D='\n'
+_C=False
+_B=None
+_A=True
+from collections.abc import Iterable
+import string
+from types import MappingProxyType
+from typing import Any,BinaryIO,NamedTuple
+from ._re import RE_DATETIME,RE_LOCALTIME,RE_NUMBER,match_to_datetime,match_to_localtime,match_to_number
+from ._types import Key,ParseFloat,Pos
+ASCII_CTRL=frozenset((chr(A)for A in range(32)))|frozenset(chr(127))
+ILLEGAL_BASIC_STR_CHARS=ASCII_CTRL-frozenset('\t')
+ILLEGAL_MULTILINE_BASIC_STR_CHARS=ASCII_CTRL-frozenset('\t\n')
+ILLEGAL_LITERAL_STR_CHARS=ILLEGAL_BASIC_STR_CHARS
+ILLEGAL_MULTILINE_LITERAL_STR_CHARS=ILLEGAL_MULTILINE_BASIC_STR_CHARS
+ILLEGAL_COMMENT_CHARS=ILLEGAL_BASIC_STR_CHARS
+TOML_WS=frozenset(' \t')
+TOML_WS_AND_NEWLINE=TOML_WS|frozenset(_D)
+BARE_KEY_CHARS=frozenset(string.ascii_letters+string.digits+'-_')
+KEY_INITIAL_CHARS=BARE_KEY_CHARS|frozenset('"\'')
+HEXDIGIT_CHARS=frozenset(string.hexdigits)
+BASIC_STR_ESCAPE_REPLACEMENTS=MappingProxyType({'\\b':'\x08','\\t':'\t','\\n':_D,'\\f':'\x0c','\\r':'\r','\\"':'"','\\\\':'\\'})
+class TOMLDecodeError(ValueError):0
+def load(A,*,parse_float=float):
+ B=A.read()
+ try:C=B.decode()
+ except AttributeError:raise TypeError("File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`") from _B
+ return loads(C,parse_float=parse_float)
+def loads(H,*,parse_float=float):
+ E=parse_float;B=H.replace('\r\n',_D);A=0;D=Output(NestedDict(),Flags());F=();E=make_safe_parse_float(E)
+ while _A:
+ A=skip_chars(B,A,TOML_WS)
+ try:C=B[A]
+ except IndexError:break
+ if C==_D:A+=1;continue
+ if C in KEY_INITIAL_CHARS:A=key_value_rule(B,A,D,F,E);A=skip_chars(B,A,TOML_WS)
+ elif C=='[':
+ try:G=B[A+1]
+ except IndexError:G=_B
+ D.flags.finalize_pending()
+ if G=='[':A,F=create_list_rule(B,A,D)
+ else:A,F=create_dict_rule(B,A,D)
+ A=skip_chars(B,A,TOML_WS)
+ elif C!='#':raise suffixed_err(B,A,'Invalid statement')
+ A=skip_comment(B,A)
+ try:C=B[A]
+ except IndexError:break
+ if C!=_D:raise suffixed_err(B,A,'Expected newline or end of document after a statement')
+ A+=1
+ return D.data.dict
+class Flags:
+ FROZEN=0;EXPLICIT_NEST=1
+ def __init__(A):A._flags={};A._pending_flags=set()
+ def add_pending(A,key,flag):A._pending_flags.add((key,flag))
+ def finalize_pending(A):
+ for (B,C) in A._pending_flags:A.set(B,C,recursive=_C)
+ A._pending_flags.clear()
+ def unset_all(C,key):
+ A=C._flags
+ for B in key[:-1]:
+ if B not in A:return
+ A=A[B][_E]
+ A.pop(key[-1],_B)
+ def set(D,key,flag,*,recursive):
+ A=D._flags;E,B=key[:-1],key[-1]
+ for C in E:
+ if C not in A:A[C]={_H:set(),_F:set(),_E:{}}
+ A=A[C][_E]
+ if B not in A:A[B]={_H:set(),_F:set(),_E:{}}
+ A[B][_F if recursive else _H].add(flag)
+ def is_(G,key,flag):
+ C=flag;B=key
+ if not B:return _C
+ A=G._flags
+ for D in B[:-1]:
+ if D not in A:return _C
+ E=A[D]
+ if C in E[_F]:return _A
+ A=E[_E]
+ F=B[-1]
+ if F in A:A=A[F];return C in A[_H]or C in A[_F]
+ return _C
+class NestedDict:
+ def __init__(A):A.dict={}
+ def get_or_create_nest(C,key,*,access_lists=_A):
+ A=C.dict
+ for B in key:
+ if B not in A:A[B]={}
+ A=A[B]
+ if access_lists and isinstance(A,list):A=A[-1]
+ if not isinstance(A,dict):raise KeyError('There is no nest behind this key')
+ return A
+ def append_nest_to_list(D,key):
+ A=D.get_or_create_nest(key[:-1]);B=key[-1]
+ if B in A:
+ C=A[B]
+ if not isinstance(C,list):raise KeyError('An object other than list found behind this key')
+ C.append({})
+ else:A[B]=[{}]
+class Output(NamedTuple):data:NestedDict;flags:Flags
+def skip_chars(src,pos,chars):
+ A=pos
+ try:
+ while src[A]in chars:A+=1
+ except IndexError:pass
+ return A
+def skip_until(src,pos,expect,*,error_on,error_on_eof):
+ E=error_on;D=expect;B=pos;A=src
+ try:C=A.index(D,B)
+ except ValueError:
+ C=len(A)
+ if error_on_eof:raise suffixed_err(A,C,f"Expected {D!r}") from _B
+ if not E.isdisjoint(A[B:C]):
+ while A[B]not in E:B+=1
+ raise suffixed_err(A,B,f"Found invalid character {A[B]!r}")
+ return C
+def skip_comment(src,pos):
+ A=pos
+ try:B=src[A]
+ except IndexError:B=_B
+ if B=='#':return skip_until(src,A+1,_D,error_on=ILLEGAL_COMMENT_CHARS,error_on_eof=_C)
+ return A
+def skip_comments_and_array_ws(src,pos):
+ A=pos
+ while _A:
+ B=A;A=skip_chars(src,A,TOML_WS_AND_NEWLINE);A=skip_comment(src,A)
+ if A==B:return A
+def create_dict_rule(src,pos,out):
+ D=out;B=src;A=pos;A+=1;A=skip_chars(B,A,TOML_WS);A,C=parse_key(B,A)
+ if D.flags.is_(C,Flags.EXPLICIT_NEST)or D.flags.is_(C,Flags.FROZEN):raise suffixed_err(B,A,f"Cannot declare {C} twice")
+ D.flags.set(C,Flags.EXPLICIT_NEST,recursive=_C)
+ try:D.data.get_or_create_nest(C)
+ except KeyError:raise suffixed_err(B,A,_G) from _B
+ if not B.startswith(']',A):raise suffixed_err(B,A,"Expected ']' at the end of a table declaration")
+ return A+1,C
+def create_list_rule(src,pos,out):
+ D=out;B=src;A=pos;A+=2;A=skip_chars(B,A,TOML_WS);A,C=parse_key(B,A)
+ if D.flags.is_(C,Flags.FROZEN):raise suffixed_err(B,A,f"Cannot mutate immutable namespace {C}")
+ D.flags.unset_all(C);D.flags.set(C,Flags.EXPLICIT_NEST,recursive=_C)
+ try:D.data.append_nest_to_list(C)
+ except KeyError:raise suffixed_err(B,A,_G) from _B
+ if not B.startswith(']]',A):raise suffixed_err(B,A,"Expected ']]' at the end of an array declaration")
+ return A+2,C
+def key_value_rule(src,pos,out,header,parse_float):
+ E=header;C=out;B=src;A=pos;A,D,H=parse_key_value_pair(B,A,parse_float);K,I=D[:-1],D[-1];F=E+K;L=(E+D[:A]for A in range(1,len(D)))
+ for G in L:
+ if C.flags.is_(G,Flags.EXPLICIT_NEST):raise suffixed_err(B,A,f"Cannot redefine namespace {G}")
+ C.flags.add_pending(G,Flags.EXPLICIT_NEST)
+ if C.flags.is_(F,Flags.FROZEN):raise suffixed_err(B,A,f"Cannot mutate immutable namespace {F}")
+ try:J=C.data.get_or_create_nest(F)
+ except KeyError:raise suffixed_err(B,A,_G) from _B
+ if I in J:raise suffixed_err(B,A,_G)
+ if isinstance(H,(dict,list)):C.flags.set(E+D,Flags.FROZEN,recursive=_A)
+ J[I]=H;return A
+def parse_key_value_pair(src,pos,parse_float):
+ B=src;A=pos;A,D=parse_key(B,A)
+ try:C=B[A]
+ except IndexError:C=_B
+ if C!='=':raise suffixed_err(B,A,"Expected '=' after a key in a key/value pair")
+ A+=1;A=skip_chars(B,A,TOML_WS);A,E=parse_value(B,A,parse_float);return A,D,E
+def parse_key(src,pos):
+ B=src;A=pos;A,C=parse_key_part(B,A);D=C,;A=skip_chars(B,A,TOML_WS)
+ while _A:
+ try:E=B[A]
+ except IndexError:E=_B
+ if E!='.':return A,D
+ A+=1;A=skip_chars(B,A,TOML_WS);A,C=parse_key_part(B,A);D+=C,;A=skip_chars(B,A,TOML_WS)
+def parse_key_part(src,pos):
+ B=src;A=pos
+ try:C=B[A]
+ except IndexError:C=_B
+ if C in BARE_KEY_CHARS:D=A;A=skip_chars(B,A,BARE_KEY_CHARS);return A,B[D:A]
+ if C=="'":return parse_literal_str(B,A)
+ if C=='"':return parse_one_line_basic_str(B,A)
+ raise suffixed_err(B,A,'Invalid initial character for a key part')
+def parse_one_line_basic_str(src,pos):pos+=1;return parse_basic_str(src,pos,multiline=_C)
+def parse_array(src,pos,parse_float):
+ B=src;A=pos;A+=1;C=[];A=skip_comments_and_array_ws(B,A)
+ if B.startswith(']',A):return A+1,C
+ while _A:
+ A,E=parse_value(B,A,parse_float);C.append(E);A=skip_comments_and_array_ws(B,A);D=B[A:A+1]
+ if D==']':return A+1,C
+ if D!=',':raise suffixed_err(B,A,'Unclosed array')
+ A+=1;A=skip_comments_and_array_ws(B,A)
+ if B.startswith(']',A):return A+1,C
+def parse_inline_table(src,pos,parse_float):
+ B=src;A=pos;A+=1;D=NestedDict();F=Flags();A=skip_chars(B,A,TOML_WS)
+ if B.startswith('}',A):return A+1,D.dict
+ while _A:
+ A,C,G=parse_key_value_pair(B,A,parse_float);J,E=C[:-1],C[-1]
+ if F.is_(C,Flags.FROZEN):raise suffixed_err(B,A,f"Cannot mutate immutable namespace {C}")
+ try:H=D.get_or_create_nest(J,access_lists=_C)
+ except KeyError:raise suffixed_err(B,A,_G) from _B
+ if E in H:raise suffixed_err(B,A,f"Duplicate inline table key {E!r}")
+ H[E]=G;A=skip_chars(B,A,TOML_WS);I=B[A:A+1]
+ if I=='}':return A+1,D.dict
+ if I!=',':raise suffixed_err(B,A,'Unclosed inline table')
+ if isinstance(G,(dict,list)):F.set(C,Flags.FROZEN,recursive=_A)
+ A+=1;A=skip_chars(B,A,TOML_WS)
+def parse_basic_str_escape(src,pos,*,multiline=_C):
+ E="Unescaped '\\' in a string";D='\\\n';B=src;A=pos;C=B[A:A+2];A+=2
+ if multiline and C in{'\\ ','\\\t',D}:
+ if C!=D:
+ A=skip_chars(B,A,TOML_WS)
+ try:F=B[A]
+ except IndexError:return A,''
+ if F!=_D:raise suffixed_err(B,A,E)
+ A+=1
+ A=skip_chars(B,A,TOML_WS_AND_NEWLINE);return A,''
+ if C=='\\u':return parse_hex_char(B,A,4)
+ if C=='\\U':return parse_hex_char(B,A,8)
+ try:return A,BASIC_STR_ESCAPE_REPLACEMENTS[C]
+ except KeyError:raise suffixed_err(B,A,E) from _B
+def parse_basic_str_escape_multiline(src,pos):return parse_basic_str_escape(src,pos,multiline=_A)
+def parse_hex_char(src,pos,hex_len):
+ C=hex_len;B=src;A=pos;D=B[A:A+C]
+ if len(D)!=C or not HEXDIGIT_CHARS.issuperset(D):raise suffixed_err(B,A,'Invalid hex value')
+ A+=C;E=int(D,16)
+ if not is_unicode_scalar_value(E):raise suffixed_err(B,A,'Escaped character is not a Unicode scalar value')
+ return A,chr(E)
+def parse_literal_str(src,pos):A=pos;A+=1;B=A;A=skip_until(src,A,"'",error_on=ILLEGAL_LITERAL_STR_CHARS,error_on_eof=_A);return A+1,src[B:A]
+def parse_multiline_str(src,pos,*,literal):
+ B=src;A=pos;A+=3
+ if B.startswith(_D,A):A+=1
+ if literal:C="'";E=skip_until(B,A,"'''",error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS,error_on_eof=_A);D=B[A:E];A=E+3
+ else:C='"';A,D=parse_basic_str(B,A,multiline=_A)
+ if not B.startswith(C,A):return A,D
+ A+=1
+ if not B.startswith(C,A):return A,D+C
+ A+=1;return A,D+C*2
+def parse_basic_str(src,pos,*,multiline):
+ F=multiline;B=src;A=pos
+ if F:G=ILLEGAL_MULTILINE_BASIC_STR_CHARS;H=parse_basic_str_escape_multiline
+ else:G=ILLEGAL_BASIC_STR_CHARS;H=parse_basic_str_escape
+ C='';D=A
+ while _A:
+ try:E=B[A]
+ except IndexError:raise suffixed_err(B,A,'Unterminated string') from _B
+ if E=='"':
+ if not F:return A+1,C+B[D:A]
+ if B.startswith('"""',A):return A+3,C+B[D:A]
+ A+=1;continue
+ if E=='\\':C+=B[D:A];A,I=H(B,A);C+=I;D=A;continue
+ if E in G:raise suffixed_err(B,A,f"Illegal character {E!r}")
+ A+=1
+def parse_value(src,pos,parse_float):
+ D=parse_float;B=src;A=pos
+ try:C=B[A]
+ except IndexError:C=_B
+ if C=='"':
+ if B.startswith('"""',A):return parse_multiline_str(B,A,literal=_C)
+ return parse_one_line_basic_str(B,A)
+ if C=="'":
+ if B.startswith("'''",A):return parse_multiline_str(B,A,literal=_A)
+ return parse_literal_str(B,A)
+ if C=='t':
+ if B.startswith('true',A):return A+4,_A
+ if C=='f':
+ if B.startswith('false',A):return A+5,_C
+ if C=='[':return parse_array(B,A,D)
+ if C=='{':return parse_inline_table(B,A,D)
+ E=RE_DATETIME.match(B,A)
+ if E:
+ try:J=match_to_datetime(E)
+ except ValueError as K:raise suffixed_err(B,A,'Invalid date or datetime') from K
+ return E.end(),J
+ F=RE_LOCALTIME.match(B,A)
+ if F:return F.end(),match_to_localtime(F)
+ G=RE_NUMBER.match(B,A)
+ if G:return G.end(),match_to_number(G,D)
+ H=B[A:A+3]
+ if H in{'inf','nan'}:return A+3,D(H)
+ I=B[A:A+4]
+ if I in{'-inf','+inf','-nan','+nan'}:return A+4,D(I)
+ raise suffixed_err(B,A,'Invalid value')
+def suffixed_err(src,pos,msg):
+ def A(src,pos):
+ B=src;A=pos
+ if A>=len(B):return'end of document'
+ C=B.count(_D,0,A)+1
+ if C==1:D=A+1
+ else:D=A-B.rindex(_D,0,A)
+ return f"line {C}, column {D}"
+ return TOMLDecodeError(f"{msg} (at {A(src,pos)})")
+def is_unicode_scalar_value(codepoint):A=codepoint;return 0<=A<=55295 or 57344<=A<=1114111
+def make_safe_parse_float(parse_float):
+ A=parse_float
+ if A is float:return float
+ def B(float_str):
+ B=A(float_str)
+ if isinstance(B,(dict,list)):raise ValueError('parse_float must not return dicts or lists')
+ return B
+ return B
\ No newline at end of file
diff --git a/dynaconf/vendor/tomllib/_re.py b/dynaconf/vendor/tomllib/_re.py
new file mode 100644
index 0000000..da26ba2
--- /dev/null
+++ b/dynaconf/vendor/tomllib/_re.py
@@ -0,0 +1,32 @@
+from __future__ import annotations
+from datetime import date,datetime,time,timedelta,timezone,tzinfo
+from functools import lru_cache
+import re
+from typing import Any
+from ._types import ParseFloat
+_TIME_RE_STR='([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\\.([0-9]{1,6})[0-9]*)?'
+RE_NUMBER=re.compile('\n0\n(?:\n x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex\n |\n b[01](?:_?[01])* # bin\n |\n o[0-7](?:_?[0-7])* # oct\n)\n|\n[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part\n(?P<floatpart>\n (?:\\.[0-9](?:_?[0-9])*)? # optional fractional part\n (?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part\n)\n',flags=re.VERBOSE)
+RE_LOCALTIME=re.compile(_TIME_RE_STR)
+RE_DATETIME=re.compile(f"""
+([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
+(?:
+ [Tt ]
+ {_TIME_RE_STR}
+ (?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
+)?
+""",flags=re.VERBOSE)
+def match_to_datetime(match):
+ H,I,J,B,K,L,C,M,D,N,O=match.groups();E,F,G=int(H),int(I),int(J)
+ if B is None:return date(E,F,G)
+ P,Q,R=int(B),int(K),int(L);S=int(C.ljust(6,'0'))if C else 0
+ if D:A=cached_tz(N,O,D)
+ elif M:A=timezone.utc
+ else:A=None
+ return datetime(E,F,G,P,Q,R,S,tzinfo=A)
+@lru_cache(maxsize=None)
+def cached_tz(hour_str,minute_str,sign_str):A=1 if sign_str=='+'else-1;return timezone(timedelta(hours=A*int(hour_str),minutes=A*int(minute_str)))
+def match_to_localtime(match):B,C,D,A=match.groups();E=int(A.ljust(6,'0'))if A else 0;return time(int(B),int(C),int(D),E)
+def match_to_number(match,parse_float):
+ A=match
+ if A.group('floatpart'):return parse_float(A.group())
+ return int(A.group(),0)
\ No newline at end of file
diff --git a/dynaconf/vendor/tomllib/_types.py b/dynaconf/vendor/tomllib/_types.py
new file mode 100644
index 0000000..1fe9083
--- /dev/null
+++ b/dynaconf/vendor/tomllib/_types.py
@@ -0,0 +1,4 @@
+from typing import Any,Callable,Tuple
+ParseFloat=Callable[[str],Any]
+Key=Tuple[str,...]
+Pos=int
\ No newline at end of file
diff --git a/dynaconf/vendor/tomllib/_writer.py b/dynaconf/vendor/tomllib/_writer.py
new file mode 100644
index 0000000..887bd21
--- /dev/null
+++ b/dynaconf/vendor/tomllib/_writer.py
@@ -0,0 +1,89 @@
+from __future__ import annotations
+_C=True
+_B=False
+_A='\n'
+from collections.abc import Generator,Mapping
+from datetime import date,datetime,time
+from decimal import Decimal
+import string
+from types import MappingProxyType
+from typing import Any,BinaryIO,NamedTuple
+ASCII_CTRL=frozenset((chr(A)for A in range(32)))|frozenset(chr(127))
+ILLEGAL_BASIC_STR_CHARS=frozenset('"\\')|ASCII_CTRL-frozenset('\t')
+BARE_KEY_CHARS=frozenset(string.ascii_letters+string.digits+'-_')
+ARRAY_TYPES=list,tuple
+ARRAY_INDENT=' '*4
+MAX_LINE_LENGTH=100
+COMPACT_ESCAPES=MappingProxyType({'\x08':'\\b',_A:'\\n','\x0c':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'})
+def dump(__obj,__fp,*,multiline_strings=_B):
+ A=Context(multiline_strings,{})
+ for B in gen_table_chunks(__obj,A,name=''):__fp.write(B.encode())
+def dumps(__obj,*,multiline_strings=_B):A=Context(multiline_strings,{});return ''.join(gen_table_chunks(__obj,A,name=''))
+class Context(NamedTuple):allow_multiline:bool;inline_table_cache:dict[int,str]
+def gen_table_chunks(table,ctx,*,name,inside_aot=_B):
+ H=inside_aot;G=ctx;C=name;D=_B;E=[];F=[]
+ for (B,A) in table.items():
+ if isinstance(A,dict):F.append((B,A,_B))
+ elif is_aot(A)and not all((is_suitable_inline_table(B,G)for B in A)):F.extend(((B,C,_C)for C in A))
+ else:E.append((B,A))
+ if H or C and(E or not F):D=_C;yield f"[[{C}]]\n"if H else f"[{C}]\n"
+ if E:
+ D=_C
+ for (B,A) in E:yield f"{format_key_part(B)} = {format_literal(A,G)}\n"
+ for (B,A,J) in F:
+ if D:yield _A
+ else:D=_C
+ I=format_key_part(B);K=f"{C}.{I}"if C else I;yield from gen_table_chunks(A,G,name=K,inside_aot=J)
+def format_literal(obj,ctx,*,nest_level=0):
+ B=ctx;A=obj
+ if isinstance(A,bool):return'true'if A else'false'
+ if isinstance(A,(int,float,date,datetime)):return str(A)
+ if isinstance(A,Decimal):return format_decimal(A)
+ if isinstance(A,time):
+ if A.tzinfo:raise ValueError('TOML does not support offset times')
+ return str(A)
+ if isinstance(A,str):return format_string(A,allow_multiline=B.allow_multiline)
+ if isinstance(A,ARRAY_TYPES):return format_inline_array(A,B,nest_level)
+ if isinstance(A,dict):return format_inline_table(A,B)
+ raise TypeError(f"Object of type {type(A)} is not TOML serializable")
+def format_decimal(obj):
+ C='-inf';B='inf';A=obj
+ if A.is_nan():return'nan'
+ if A==Decimal(B):return B
+ if A==Decimal(C):return C
+ return str(A)
+def format_inline_table(obj,ctx):
+ B=obj;A=ctx;C=id(B)
+ if C in A.inline_table_cache:return A.inline_table_cache[C]
+ if not B:D='{}'
+ else:D='{ '+', '.join((f"{format_key_part(C)} = {format_literal(D,A)}"for(C,D)in B.items()))+' }'
+ A.inline_table_cache[C]=D;return D
+def format_inline_array(obj,ctx,nest_level):
+ A=nest_level
+ if not obj:return'[]'
+ B=ARRAY_INDENT*(1+A);C=ARRAY_INDENT*A;return'[\n'+',\n'.join((B+format_literal(C,ctx,nest_level=A+1)for C in obj))+f",\n{C}]"
+def format_key_part(part):
+ A=part
+ if A and BARE_KEY_CHARS.issuperset(A):return A
+ return format_string(A,allow_multiline=_B)
+def format_string(s,*,allow_multiline):
+ D=allow_multiline and _A in s
+ if D:A='"""\n';s=s.replace('\r\n',_A)
+ else:A='"'
+ B=E=0
+ while _C:
+ try:C=s[B]
+ except IndexError:
+ A+=s[E:B]
+ if D:return A+'"""'
+ return A+'"'
+ if C in ILLEGAL_BASIC_STR_CHARS:
+ A+=s[E:B]
+ if C in COMPACT_ESCAPES:
+ if D and C==_A:A+=_A
+ else:A+=COMPACT_ESCAPES[C]
+ else:A+='\\u'+hex(ord(C))[2:].rjust(4,'0')
+ E=B+1
+ B+=1
+def is_aot(obj):A=obj;return bool(isinstance(A,ARRAY_TYPES)and A and all((isinstance(B,dict)for B in A)))
+def is_suitable_inline_table(obj,ctx):A=f"{ARRAY_INDENT}{format_inline_table(obj,ctx)},";return len(A)<=MAX_LINE_LENGTH and _A not in A
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index e91517f..3947dfa 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,16 @@
[bdist_wheel]
universal = 1
+[flake8]
+ignore = F403
+ W504
+ W503
+ F841
+ E401
+ F401
+ E402
+max-line-length = 79
+
[mypy]
ignore_missing_imports = True
diff --git a/setup.py b/setup.py
index 959ee73..2e6533e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
-import io
+from __future__ import annotations
+
import os
-import sys
from setuptools import find_packages
from setuptools import setup
@@ -9,7 +9,7 @@ from setuptools import setup
def read(*names, **kwargs):
"""Read a file."""
content = ""
- with io.open(
+ with open(
os.path.join(os.path.dirname(__file__), *names),
encoding=kwargs.get("encoding", "utf8"),
) as open_file:
@@ -17,11 +17,34 @@ def read(*names, **kwargs):
return content
+test_requirements = [
+ "pytest",
+ "pytest-cov",
+ "pytest-xdist",
+ "pytest-mock",
+ "flake8",
+ "pep8-naming",
+ "flake8-debugger",
+ "flake8-print",
+ "flake8-todo",
+ "radon",
+ "flask>=0.12",
+ "django",
+ "python-dotenv",
+ "toml",
+ "codecov",
+ "redis",
+ "hvac",
+ "configobj",
+]
+
+
setup(
name="dynaconf",
version=read("dynaconf", "VERSION"),
- url="https://github.com/rochacbruno/dynaconf",
+ url="https://github.com/dynaconf/dynaconf",
license="MIT",
+ license_files=["LICENSE", "vendor_licenses/*"],
author="Bruno Rocha",
author_email="rochacbruno@gmail.com",
description="The dynamic configurator for your Python Project",
@@ -31,8 +54,8 @@ setup(
exclude=[
"tests",
"tests.*",
- "example",
- "example.*",
+ "tests_functional",
+ "tests_functional.*",
"docs",
"legacy_docs",
"legacy_docs.*",
@@ -48,22 +71,7 @@ setup(
include_package_data=True,
zip_safe=False,
platforms="any",
- install_requires=["typing; python_version<'3.5'"],
- tests_require=[
- "pytest",
- "pytest-cov",
- "pytest-xdist",
- "flake8",
- "pep8-naming",
- "flake8-debugger",
- "flake8-print",
- "flake8-todo",
- "radon",
- "flask>=0.12",
- "python-dotenv",
- "toml",
- "codecov",
- ],
+ tests_require=test_requirements,
extras_require={
"redis": ["redis"],
"vault": ["hvac"],
@@ -72,8 +80,9 @@ setup(
"ini": ["configobj"],
"configobj": ["configobj"],
"all": ["redis", "ruamel.yaml", "configobj", "hvac"],
+ "test": test_requirements,
},
- python_requires=">=3.7",
+ python_requires=">=3.8",
entry_points={"console_scripts": ["dynaconf=dynaconf.cli:main"]},
setup_requires=["setuptools>=38.6.0"],
classifiers=[
@@ -87,9 +96,10 @@ setup(
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
"Topic :: Utilities",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
diff --git a/tests/test_base.py b/tests/test_base.py
new file mode 100644
index 0000000..18363ba
--- /dev/null
+++ b/tests/test_base.py
@@ -0,0 +1,1076 @@
+from __future__ import annotations
+
+import os
+
+import pytest
+
+from dynaconf import Dynaconf
+from dynaconf import LazySettings
+from dynaconf.loaders import toml_loader
+from dynaconf.loaders import yaml_loader
+from dynaconf.strategies.filtering import PrefixFilter
+from dynaconf.utils.parse_conf import true_values
+from dynaconf.vendor.box.box_list import BoxList
+
+
+def test_deleted_raise(settings):
+ """asserts variable can be deleted"""
+ assert "TODELETE" in settings
+ assert settings.TODELETE is True
+ del settings.TODELETE
+ with pytest.raises(AttributeError):
+ assert settings.TODELETE is True
+ assert settings.exists("TODELETE") is False
+ assert settings.get("TODELETE") is None
+
+
+def test_delete_and_set_again(settings):
+ """asserts variable can be deleted and set again"""
+
+ # set
+ settings.TODELETE2 = True
+ assert "TODELETE2" in settings
+ assert settings.TODELETE2 is True
+
+ # delete
+ del settings.TODELETE2
+ assert settings.exists("TODELETE2") is False
+ assert settings.get("TODELETE2") is None
+
+ # set again
+ settings.TODELETE2 = "new value"
+ assert settings.TODELETE2 == "new value"
+
+
+def test_accepts_only_upper():
+ """Only upper case names are allowed if lowercase_read=False
+ lower case are converted"""
+
+ settings = LazySettings(debug=True, lowercase_read=False)
+
+ assert settings.DEBUG is True
+ assert settings.get("debug") is True
+ assert settings.get("DEBUG") is True
+ assert settings.get("Debug") is True
+ assert settings.exists("debug")
+ with pytest.raises(AttributeError):
+ # access in lower case is not allowed
+ assert settings.debug is True
+
+
+def test_populate_obj(settings):
+ class Obj:
+ pass
+
+ obj = Obj()
+
+ settings.populate_obj(obj)
+
+ assert obj.DEBUG is True
+ assert obj.VALUE == 42.1
+
+
+def test_populate_obj_with_keys(settings):
+ class Obj:
+ pass
+
+ obj = Obj()
+
+ settings.populate_obj(obj, ["VALUE"])
+
+ assert obj.VALUE == 42.1
+
+ with pytest.raises(AttributeError):
+ assert obj.DEBUG is True
+
+
+def test_populate_obj_with_ignore(settings):
+ class Obj:
+ pass
+
+ obj = Obj()
+
+ settings.populate_obj(obj, ignore=["VALUE"])
+
+ assert obj.DEBUG is True
+
+ with pytest.raises(AttributeError):
+ assert obj.VALUE == 42.1
+
+
+def test_call_works_as_get(settings):
+ """settings.get('name') is the same as settings('name')"""
+
+ assert settings("debug") == settings.get("DEBUG")
+ assert settings("non_exist", default="hello") == "hello"
+ assert settings.get("non_exist", default="hello") == "hello"
+ assert settings.__call__("debug") == settings.DEBUG
+ assert settings._wrapped.__call__("debug") == settings.DEBUG
+
+
+def test_keys_are_equal(settings):
+ assert set(list(settings.keys())) == set(list(settings.store.keys()))
+
+
+def test_values_are_equal(settings):
+ for item in settings.values():
+ assert item in settings.store.values()
+
+
+def test_get_env(settings):
+ settings.environ["FRUIT"] = "BANANA"
+ assert settings.exists_in_environ("fruit") is True
+ assert settings.environ["FRUIT"] == settings.get_environ("fruit")
+ assert os.environ["FRUIT"] == settings.environ.get("FRUIT")
+
+ settings.environ["SALARY"] = "180.235"
+ assert settings.get_environ("salary", cast="@float") == 180.235
+
+ settings.environ["ENVINT"] = "@int 24"
+ assert settings.get_environ("envint", cast=True) == 24
+
+
+def test_float(settings):
+ settings.set("money", "500.42")
+ assert settings.exists("MONEY")
+ assert settings.MONEY == "500.42"
+ assert settings.MONEY != 500.42
+ assert settings.store["MONEY"] == "500.42"
+ assert "MONEY" not in settings._deleted
+ assert "money" not in settings._deleted
+ assert isinstance(settings.as_float("money"), float)
+ assert settings.as_float("MONEY") == 500.42
+
+
+def test_int(settings):
+ settings.set("age", "500")
+ assert settings.exists("AGE")
+ assert settings.AGE == "500"
+ assert settings.AGE != 500
+ assert settings.store["AGE"] == "500"
+ assert "AGE" not in settings._deleted
+ assert "age" not in settings._deleted
+ assert isinstance(settings.as_int("age"), int)
+ assert settings.as_int("age") == 500
+
+
+def test_bool(settings):
+ for true_value in true_values:
+ # ('t', 'true', 'enabled', '1', 'on', 'yes')
+ settings.set("feature", true_value)
+ assert settings.exists("FEATURE")
+ assert settings.FEATURE == true_value
+ assert settings.FEATURE is not True
+ assert settings.store["FEATURE"] == true_value
+ assert "FEATURE" not in settings._deleted
+ assert "feature" not in settings._deleted
+ assert isinstance(settings.as_bool("feature"), bool)
+ assert settings.as_bool("FEATURE") is True
+
+ # anything else is a false value
+ false_values = ["f", "false", "False", "disabled", "0", "off", "no"]
+ for false_value in false_values:
+ settings.set("feature", false_value)
+ assert settings.exists("FEATURE")
+ assert settings.FEATURE == false_value
+ assert settings.FEATURE is not False
+ assert settings.store["FEATURE"] == false_value
+ assert "FEATURE" not in settings._deleted
+ assert "feature" not in settings._deleted
+ assert isinstance(settings.as_bool("feature"), bool)
+ assert settings.as_bool("FEATURE") is False
+
+
+def test_as_json(settings):
+ settings.set("fruits", '["banana", "apple", "kiwi"]')
+ assert settings.exists("FRUITS")
+ assert settings.FRUITS == '["banana", "apple", "kiwi"]'
+ assert settings.FRUITS != ["banana", "apple", "kiwi"]
+ assert settings.store["FRUITS"] == '["banana", "apple", "kiwi"]'
+ assert "FRUITS" not in settings._deleted
+ assert "fruits" not in settings._deleted
+ assert isinstance(settings.as_json("fruits"), list)
+ assert settings.as_json("fruits") == ["banana", "apple", "kiwi"]
+
+ settings.set("person", '{"name": "Bruno"}')
+ assert settings.exists("PERSON")
+ assert settings.PERSON == '{"name": "Bruno"}'
+ assert settings.PERSON != {"name": "Bruno"}
+ assert settings.store["PERSON"] == '{"name": "Bruno"}'
+ assert "PERSON" not in settings._deleted
+ assert "person" not in settings._deleted
+ assert isinstance(settings.as_json("person"), dict)
+ assert settings.as_json("person") == {"name": "Bruno"}
+
+
+def test_env_should_be_string(settings):
+ with pytest.raises(ValueError):
+ settings.setenv(123456)
+
+
+def test_env_should_not_have_underline(settings):
+ with pytest.raises(ValueError):
+ settings.setenv("COOL_env")
+
+
+def test_path_for(settings):
+ assert settings.path_for(os.path.sep, "tmp", "bla") == os.path.join(
+ os.path.sep, "tmp", "bla"
+ )
+ assert settings.path_for("foo", "bar", "blaz") == os.path.join(
+ settings._root_path, "foo", "bar", "blaz"
+ )
+
+
+def test_get_item(settings):
+ assert settings["DOTENV_INT"] == 1
+ assert settings["PORT"] == 5000
+ with pytest.raises(KeyError):
+ settings["DONOTEXISTTHISKEY"]
+
+
+def test_set_item(settings):
+ settings["FOO"] = "bar"
+ assert settings.FOO == "bar"
+ assert "FOO" in settings._defaults
+ assert settings("FOO") == "bar"
+ assert settings.get("FOO") == "bar"
+
+
+def test_set(settings):
+ # NOTE: it is recommended to call set(x, 1) or ['x'] = 1
+ # instead of settings.BAZ = 'bar'
+ settings.set("BAZ", "bar")
+ assert settings.BAZ == "bar"
+ assert "BAZ" in settings._defaults
+ assert settings("BAZ") == "bar"
+ assert settings.get("BAZ") == "bar"
+
+
+def test_global_set_merge(settings):
+ settings.set("MERGE_ENABLED_FOR_DYNACONF", True)
+ settings.set(
+ "MERGE_KEY", {"items": [{"name": "item 1"}, {"name": "item 2"}]}
+ )
+ settings.set(
+ "MERGE_KEY", {"items": [{"name": "item 3"}, {"name": "item 4"}]}
+ )
+ assert settings.MERGE_KEY == {
+ "items": [
+ {"name": "item 1"},
+ {"name": "item 2"},
+ {"name": "item 3"},
+ {"name": "item 4"},
+ ]
+ }
+
+
+def test_global_merge_shortcut(settings):
+ settings.set("MERGE_ENABLED_FOR_DYNACONF", True)
+ settings.set("MERGE_KEY", ["item1"])
+ settings.set("MERGE_KEY", ["item1"])
+ assert settings.MERGE_KEY == ["item1"]
+
+
+def test_local_set_merge_dict():
+ settings = Dynaconf()
+ settings.set("DATABASE", {"host": "localhost", "port": 666})
+ # calling twice does not change anything
+ settings.set("DATABASE", {"host": "localhost", "port": 666})
+ assert settings.DATABASE == {"host": "localhost", "port": 666}
+
+ settings.set(
+ "DATABASE", {"host": "new", "user": "admin", "dynaconf_merge": True}
+ )
+ assert settings.DATABASE == {"host": "new", "port": 666, "user": "admin"}
+ assert settings.DATABASE.HOST == "new"
+ assert settings.DATABASE.user == "admin"
+
+ settings.set("DATABASE.attrs", {"a": ["b", "c"]})
+ settings.set("DATABASE.attrs", {"dynaconf_merge": {"foo": "bar"}})
+ assert settings.DATABASE.attrs == {"a": ["b", "c"], "foo": "bar"}
+
+ settings.set("DATABASE.attrs", {"yeah": "baby", "dynaconf_merge": True})
+ assert settings.DATABASE.attrs == {
+ "a": ["b", "c"],
+ "foo": "bar",
+ "yeah": "baby",
+ }
+
+ settings.set(
+ "DATABASE.attrs",
+ {"a": ["d", "e", "dynaconf_merge"], "dynaconf_merge": True},
+ )
+
+ assert settings.DATABASE.attrs == {
+ "a": ["b", "c", "d", "e"],
+ "foo": "bar",
+ "yeah": "baby",
+ }
+
+ settings.set("DATABASE.attrs.a", ["d", "e", "f", "dynaconf_merge_unique"])
+ assert settings.DATABASE.attrs.a == ["b", "c", "d", "e", "f"]
+
+
+def test_local_set_merge_list(settings):
+ settings.set("PLUGINS", ["core"])
+ settings.set("PLUGINS", ["core"])
+ assert settings.PLUGINS == ["core"]
+
+ settings.set("PLUGINS", ["debug_toolbar", "dynaconf_merge"])
+ assert settings.PLUGINS == ["core", "debug_toolbar"]
+
+
+def test_local_set_merge_list_unique(settings):
+ settings.set("SCRIPTS", ["install.sh", "deploy.sh"])
+ settings.set("SCRIPTS", ["install.sh", "deploy.sh"])
+ assert settings.SCRIPTS == ["install.sh", "deploy.sh"]
+
+ settings.set(
+ "SCRIPTS", ["dev.sh", "test.sh", "deploy.sh", "dynaconf_merge_unique"]
+ )
+ assert settings.SCRIPTS == ["install.sh", "dev.sh", "test.sh", "deploy.sh"]
+
+
+def test_set_explicit_merge_token(tmpdir):
+ data = {
+ "a_list": [1, 2],
+ "b_list": [1],
+ "a_dict": {"name": "Bruno"},
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data, merge=False)
+ settings = LazySettings(settings_file="settings.toml")
+ assert settings.A_LIST == [1, 2]
+ assert settings.B_LIST == [1]
+ assert settings.A_DICT == {"name": "Bruno"}
+ assert settings.A_DICT.name == "Bruno"
+
+ settings.set("a_list", [3], merge=True)
+ assert settings.A_LIST == [1, 2, 3]
+
+ settings.set("b_list", "@merge [2]")
+ assert settings.B_LIST == [1, 2]
+
+ settings.set("b_list", "@merge [3, 4]")
+ assert settings.B_LIST == [1, 2, 3, 4]
+
+ settings.set("b_list", "@merge 5")
+ assert settings.B_LIST == [1, 2, 3, 4, 5]
+
+ settings.set("b_list", "@merge 6.6")
+ assert settings.B_LIST == [1, 2, 3, 4, 5, 6.6]
+
+ settings.set("b_list", "@merge false")
+ assert settings.B_LIST == [1, 2, 3, 4, 5, 6.6, False]
+
+ settings.set("b_list", "@merge foo,bar")
+ assert settings.B_LIST == [1, 2, 3, 4, 5, 6.6, False, "foo", "bar"]
+
+ settings.set("b_list", "@merge zaz")
+ assert settings.B_LIST == [1, 2, 3, 4, 5, 6.6, False, "foo", "bar", "zaz"]
+
+ settings.set("a_dict", "@merge {city='Guarulhos'}")
+ assert settings.A_DICT.name == "Bruno"
+ assert settings.A_DICT.city == "Guarulhos"
+
+ settings.set("a_dict", "@merge country=Brasil")
+ assert settings.A_DICT.name == "Bruno"
+ assert settings.A_DICT.city == "Guarulhos"
+ assert settings.A_DICT.country == "Brasil"
+
+ settings.set("new_key", "@merge foo=bar")
+ assert settings.NEW_KEY == {"foo": "bar"}
+
+
+def test_set_new_merge_issue_241_1(tmpdir):
+ data = {
+ "default": {
+ "name": "Bruno",
+ "colors": ["red", "green"],
+ "data": {
+ "links": {"twitter": "rochacbruno", "site": "brunorocha.org"}
+ },
+ }
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data, merge=False)
+ settings = LazySettings(environments=True, settings_file="settings.toml")
+ assert settings.NAME == "Bruno"
+ assert settings.COLORS == ["red", "green"]
+ assert settings.DATA.links == {
+ "twitter": "rochacbruno",
+ "site": "brunorocha.org",
+ }
+
+
+def test_set_new_merge_issue_241_2(tmpdir):
+ data = {
+ "default": {
+ "name": "Bruno",
+ "colors": ["red", "green"],
+ "data": {
+ "links": {"twitter": "rochacbruno", "site": "brunorocha.org"}
+ },
+ }
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data, merge=False)
+
+ data = {
+ "dynaconf_merge": True,
+ "default": {
+ "colors": ["blue"],
+ "data": {"links": {"github": "rochacbruno.github.io"}},
+ },
+ }
+ toml_loader.write(
+ str(tmpdir.join("settings.local.toml")), data, merge=False
+ )
+
+ settings = LazySettings(environments=True, settings_file="settings.toml")
+ assert settings.NAME == "Bruno"
+ assert settings.COLORS == ["red", "green", "blue"]
+ assert settings.DATA.links == {
+ "twitter": "rochacbruno",
+ "site": "brunorocha.org",
+ "github": "rochacbruno.github.io",
+ }
+
+
+def test_set_new_merge_issue_241_3(tmpdir):
+ data = {
+ "name": "Bruno",
+ "colors": ["red", "green"],
+ "data": {
+ "links": {"twitter": "rochacbruno", "site": "brunorocha.org"}
+ },
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data, merge=False)
+
+ data = {
+ "name": "Tommy Shelby",
+ "colors": {"dynaconf_merge": ["yellow", "pink"]},
+ "data": {"links": {"site": "pb.com"}},
+ }
+ toml_loader.write(
+ str(tmpdir.join("settings.local.toml")), data, merge=False
+ )
+
+ settings = LazySettings(settings_file="settings.toml")
+ assert settings.NAME == "Tommy Shelby"
+ assert settings.COLORS == ["red", "green", "yellow", "pink"]
+ assert settings.DATA.links == {"site": "pb.com"}
+
+
+def test_set_new_merge_issue_241_4(tmpdir):
+ data = {
+ "name": "Bruno",
+ "colors": ["red", "green"],
+ "data": {
+ "links": {"twitter": "rochacbruno", "site": "brunorocha.org"}
+ },
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data, merge=False)
+
+ data = {"data__links__telegram": "t.me/rochacbruno"}
+ toml_loader.write(
+ str(tmpdir.join("settings.local.toml")), data, merge=False
+ )
+
+ settings = LazySettings(settings_file="settings.toml")
+ assert settings.NAME == "Bruno"
+ assert settings.COLORS == ["red", "green"]
+ assert settings.DATA.links == {
+ "twitter": "rochacbruno",
+ "site": "brunorocha.org",
+ "telegram": "t.me/rochacbruno",
+ }
+
+
+def test_set_new_merge_issue_241_5(tmpdir):
+ data = {
+ "default": {
+ "name": "Bruno",
+ "colors": ["red", "green"],
+ "data": {
+ "links": {"twitter": "rochacbruno", "site": "brunorocha.org"}
+ },
+ }
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data, merge=False)
+
+ data = {"default": {"colors": "@merge ['blue']"}}
+ toml_loader.write(
+ str(tmpdir.join("settings.local.toml")), data, merge=False
+ )
+
+ settings = LazySettings(environments=True, settings_file="settings.toml")
+ assert settings.NAME == "Bruno"
+ assert settings.COLORS == ["red", "green", "blue"]
+ assert settings.DATA.links == {
+ "twitter": "rochacbruno",
+ "site": "brunorocha.org",
+ }
+
+
+def test_exists(settings):
+ settings.set("BOOK", "TAOCP")
+ assert settings.exists("BOOK") is True
+
+
+def test_dotted_traversal_access(settings):
+ settings.set(
+ "PARAMS",
+ {
+ "PASSWORD": "secret",
+ "SSL": {"CONTEXT": "SECURE"},
+ "DOTTED.KEY": True,
+ },
+ dotted_lookup=False,
+ )
+ assert settings.get("PARAMS") == {
+ "PASSWORD": "secret",
+ "SSL": {"CONTEXT": "SECURE"},
+ "DOTTED.KEY": True,
+ }
+
+ assert settings("PARAMS.PASSWORD") == "secret"
+ assert settings("PARAMS.SSL.CONTEXT") == "SECURE"
+ assert settings("PARAMS.TOKEN.IMAGINARY", 1234) == 1234
+ assert settings("IMAGINARY_KEY.FOO") is None
+ assert settings("IMAGINARY_KEY") is None
+
+ assert settings["PARAMS.PASSWORD"] == "secret"
+ assert settings["PARAMS.SSL.CONTEXT"] == "SECURE"
+ assert settings.PARAMS.SSL.CONTEXT == "SECURE"
+
+ assert settings.exists("PARAMS") is True
+ assert settings.exists("PARAMS.PASSWORD") is True
+ assert settings.exists("PARAMS.SSL") is True
+ assert settings.exists("PARAMS.SSL.FAKE") is False
+ assert settings.exists("PARAMS.SSL.CONTEXT") is True
+
+ # Dotted traversal should not work for dictionary-like key access.
+ with pytest.raises(KeyError):
+ settings["PARAMS.DOESNOTEXIST"]
+
+ # Disable dot-traversal on a per-call basis.
+ assert settings("PARAMS.PASSWORD", dotted_lookup=False) is None
+
+ assert settings("PARAMS.DOTTED.KEY") is None
+ assert settings("PARAMS").get("DOTTED.KEY") is True
+
+ settings.set("DOTTED.KEY", True, dotted_lookup=False)
+ assert settings("DOTTED.KEY", dotted_lookup=False) is True
+
+ settings.set("NESTED_1", {"nested_2": {"nested_3": {"nested_4": True}}})
+
+ assert settings.NESTED_1.nested_2.nested_3.nested_4 is True
+ assert settings["NESTED_1.nested_2.nested_3.nested_4"] is True
+ assert settings("NESTED_1.nested_2.nested_3.nested_4") is True
+ # First key is always transformed to upper()
+ assert settings("nested_1.nested_2.nested_3.nested_4") is True
+
+ # using cast
+ settings.set("me.name", '@json ["bruno", "rocha"]')
+ settings.set("me.number", "42")
+ assert settings.get("me.name", cast=True, default=["bruno", "rocha"]) == [
+ "bruno",
+ "rocha",
+ ]
+ assert settings.get("me.number", cast="@int", default=42) == 42
+
+ # nested separator test
+ assert settings.get("ME__NUMBER") == "42"
+
+
+def test_dotted_set(settings):
+ settings.set("MERGE_ENABLED_FOR_DYNACONF", False)
+
+ settings.set("nested_1.nested_2.nested_3.nested_4", "secret")
+
+ assert settings.NESTED_1.NESTED_2.NESTED_3.NESTED_4 == "secret"
+ assert settings.NESTED_1.NESTED_2.NESTED_3.to_dict() == {
+ "nested_4": "secret"
+ }
+ assert settings.NESTED_1.NESTED_2.to_dict() == {
+ "nested_3": {"nested_4": "secret"}
+ }
+
+ assert settings.get("nested_1").to_dict() == {
+ "nested_2": {"nested_3": {"nested_4": "secret"}}
+ }
+
+ with pytest.raises(KeyError):
+ settings.NESTED_1.NESTED_2_0
+
+ settings.set("nested_1.nested_2_0", "Hello")
+ assert settings.NESTED_1.NESTED_2_0 == "Hello"
+
+ settings.set("nested_1.nested_2.nested_3.nested_4", "Updated Secret")
+ assert settings.NESTED_1.NESTED_2.NESTED_3.NESTED_4 == "Updated Secret"
+ assert settings.NESTED_1.NESTED_2.NESTED_3.to_dict() == {
+ "nested_4": "Updated Secret"
+ }
+ assert settings.NESTED_1.NESTED_2.to_dict() == {
+ "nested_3": {"nested_4": "Updated Secret"}
+ }
+
+ assert settings.get("nested_1").to_dict() == {
+ "nested_2": {"nested_3": {"nested_4": "Updated Secret"}},
+ "nested_2_0": "Hello",
+ }
+
+
+def test_dotted_set_with_merge(settings):
+ settings.set("MERGE_ENABLED_FOR_DYNACONF", False)
+
+ start_data = {
+ "default": {
+ "NAME": "testdb",
+ "ENGINE": "db.foo.bar",
+ "PORT": 6666,
+ "PARAMS": ["a", "b", "c"],
+ "ATTRS": {"a": 1, "b": 2},
+ }
+ }
+ settings.set("DATABASES", start_data)
+
+ assert settings.DATABASES == start_data
+
+ # Change DB name
+ settings.set("DATABASES.default.NAME", "bladb")
+ assert settings.DATABASES != start_data
+ assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+ settings.DATABASES.default.NAME == "bladb"
+
+ # Replace items on a list
+ assert settings.DATABASES.default.PARAMS == ["a", "b", "c"]
+ settings.set("DATABASES.default.PARAMS", ["d", "e"])
+ assert settings.DATABASES != start_data
+ assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+ assert settings.DATABASES.default.PARAMS == ["d", "e"]
+
+ # Add new items to the list
+ settings.set("DATABASES.default.PARAMS", '@merge ["e", "f", "g"]')
+ assert settings.DATABASES != start_data
+ assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+ assert settings.DATABASES.default.PARAMS == ["d", "e", "e", "f", "g"]
+
+ # Replace a dict
+ assert settings.DATABASES.default.ATTRS == {"a": 1, "b": 2}
+ settings.set("DATABASES.default.ATTRS", {"c": 3})
+ assert settings.DATABASES != start_data
+ assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+ assert settings.DATABASES.default.ATTRS == {"c": 3}
+
+ # Add new item to the dict
+ settings.set("DATABASES.default.ATTRS", '@merge {"b": 2, "d": 4}')
+ assert settings.DATABASES != start_data
+ assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+ assert settings.DATABASES.default.ATTRS == {"b": 2, "c": 3, "d": 4}
+
+ # Replace the entire list
+ settings.set("DATABASES.default.PARAMS", ["x", "y", "z"], tomlfy=True)
+ assert settings.DATABASES != start_data
+ assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+ assert settings.DATABASES.default.PARAMS == ["x", "y", "z"]
+
+ # Replace the entire dict
+ settings.set("DATABASES.default.ATTRS", "{x=26}", tomlfy=True)
+ assert settings.DATABASES != start_data
+ assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+ assert settings.DATABASES.default.ATTRS == {"x": 26}
+
+
+def test_from_env_method(clean_env, tmpdir):
+ data = {
+ "default": {"a_default": "From default env"},
+ "development": {
+ "value": "From development env",
+ "only_in_development": True,
+ },
+ "other": {"value": "From other env", "only_in_other": True},
+ }
+ toml_path = str(tmpdir.join("base_settings.toml"))
+ toml_loader.write(toml_path, data, merge=False)
+ settings = LazySettings(settings_file=toml_path, environments=True)
+ settings.set("ARBITRARY_KEY", "arbitrary value")
+
+ assert settings.VALUE == "From development env"
+ assert settings.A_DEFAULT == "From default env"
+ assert settings.ONLY_IN_DEVELOPMENT is True
+ assert settings.ARBITRARY_KEY == "arbitrary value"
+ assert settings.get("ONLY_IN_OTHER") is None
+
+ # clone the settings object pointing to a new env
+ other_settings = settings.from_env("other")
+ assert other_settings.VALUE == "From other env"
+ assert other_settings.A_DEFAULT == "From default env"
+ assert other_settings.ONLY_IN_OTHER is True
+ assert other_settings.get("ARBITRARY_KEY") is None
+ assert other_settings.get("ONLY_IN_DEVELOPMENT") is None
+ with pytest.raises(AttributeError):
+ # values set programmatically are not cloned
+ other_settings.ARBITRARY_KEY
+ with pytest.raises(AttributeError):
+ # values set only in a specific env not cloned
+ other_settings.ONLY_IN_DEVELOPMENT
+ # assert it is cached not created twice
+ assert other_settings is settings.from_env("other")
+
+ # Now the same using keep=True
+ other_settings = settings.from_env("other", keep=True)
+ assert other_settings.VALUE == "From other env"
+ assert other_settings.A_DEFAULT == "From default env"
+ assert other_settings.ONLY_IN_OTHER is True
+ assert other_settings.ONLY_IN_DEVELOPMENT is True
+ assert settings.ARBITRARY_KEY == "arbitrary value"
+
+ # assert it is created not cached
+ assert other_settings is not settings.from_env("other")
+
+ # settings remains the same
+ assert settings.VALUE == "From development env"
+ assert settings.A_DEFAULT == "From default env"
+ assert settings.ONLY_IN_DEVELOPMENT is True
+ assert settings.ARBITRARY_KEY == "arbitrary value"
+ assert settings.get("ONLY_IN_OTHER") is None
+
+ # additional kwargs
+ data = {
+ "default": {"a_default": "From default env"},
+ "production": {"value": "From prod env", "only_in_prod": True},
+ "other": {"value": "From other env", "only_in_other": True},
+ }
+ toml_path = str(tmpdir.join("other_settings.toml"))
+ toml_loader.write(toml_path, data, merge=False)
+
+ new_other_settings = other_settings.from_env(
+ "production", keep=True, SETTINGS_FILE_FOR_DYNACONF=toml_path
+ )
+
+ # production values
+ assert new_other_settings.VALUE == "From prod env"
+ assert new_other_settings.ONLY_IN_PROD is True
+ # keep=True values
+ assert new_other_settings.ONLY_IN_OTHER is True
+ assert new_other_settings.ONLY_IN_DEVELOPMENT is True
+ assert settings.A_DEFAULT == "From default env"
+
+
+def test_from_env_method_with_prefix(clean_env, tmpdir):
+ data = {
+ "default": {"prefix_a_default": "From default env"},
+ "development": {
+ "prefix_value": "From development env",
+ "prefix_only_in_development": True,
+ },
+ "other": {
+ "prefix_value": "From other env",
+ "prefix_only_in_other": True,
+ "not_prefixed": "no prefix",
+ },
+ }
+ toml_path = str(tmpdir.join("base_settings.toml"))
+ toml_loader.write(toml_path, data, merge=False)
+ settings = LazySettings(
+ settings_file=toml_path,
+ environments=True,
+ filter_strategy=PrefixFilter("prefix"),
+ )
+ settings.set("ARBITRARY_KEY", "arbitrary value")
+
+ assert settings.VALUE == "From development env"
+ assert settings.A_DEFAULT == "From default env"
+ assert settings.ONLY_IN_DEVELOPMENT is True
+ assert settings.ARBITRARY_KEY == "arbitrary value"
+ assert settings.get("ONLY_IN_OTHER") is None
+
+ # clone the settings object pointing to a new env
+ other_settings = settings.from_env("other")
+ assert other_settings.VALUE == "From other env"
+ assert other_settings.A_DEFAULT == "From default env"
+ assert other_settings.ONLY_IN_OTHER is True
+ assert other_settings.get("ARBITRARY_KEY") is None
+ assert other_settings.get("ONLY_IN_DEVELOPMENT") is None
+ with pytest.raises(AttributeError):
+ other_settings.not_prefixed
+ with pytest.raises(AttributeError):
+ # values set programmatically are not cloned
+ other_settings.ARBITRARY_KEY
+ with pytest.raises(AttributeError):
+ # values set only in a specific env not cloned
+ other_settings.ONLY_IN_DEVELOPMENT
+ # assert it is cached not created twice
+ assert other_settings is settings.from_env("other")
+
+
+def test_preload(tmpdir):
+ data = {
+ "data": {"links": {"twitter": "rochacbruno", "site": "brunorocha.org"}}
+ }
+ toml_loader.write(str(tmpdir.join("preload.toml")), data, merge=False)
+
+ data = {
+ "dynaconf_merge": True,
+ "data": {"links": {"github": "rochacbruno.github.io"}},
+ }
+ toml_loader.write(
+ str(tmpdir.join("main_settings.toml")), data, merge=False
+ )
+
+ data = {
+ "dynaconf_merge": True,
+ "data": {"links": {"mastodon": "mastodon.social/@rochacbruno"}},
+ }
+ toml_loader.write(str(tmpdir.join("included.toml")), data, merge=False)
+
+ settings = LazySettings(
+ PRELOAD_FOR_DYNACONF=["preload.toml"],
+ SETTINGS_FILE_FOR_DYNACONF="main_settings.toml",
+ INCLUDES_FOR_DYNACONF=["included.toml"],
+ )
+
+ assert settings.DATA.links == {
+ "twitter": "rochacbruno",
+ "site": "brunorocha.org",
+ "github": "rochacbruno.github.io",
+ "mastodon": "mastodon.social/@rochacbruno",
+ }
+
+
+def test_config_aliases(tmpdir):
+ data = {
+ "hello": {"name": "Bruno", "passwd": 1234},
+ "awesome": {"passwd": 5678},
+ }
+ toml_loader.write(str(tmpdir.join("blarg.toml")), data, merge=False)
+
+ settings = LazySettings(
+ envvar_prefix="BRUCE",
+ core_loaders=["TOML"],
+ loaders=["dynaconf.loaders.env_loader"],
+ default_env="hello",
+ env_switcher="BRUCE_ENV",
+ prelaod=[],
+ settings_file=["blarg.toml"],
+ includes=[],
+ ENV="awesome",
+ environments=True,
+ )
+
+ assert settings.NAME == "Bruno"
+ assert settings.PASSWD == 5678
+
+ assert settings.ENVVAR_PREFIX_FOR_DYNACONF == "BRUCE"
+ assert settings.CORE_LOADERS_FOR_DYNACONF == ["TOML"]
+ assert settings.LOADERS_FOR_DYNACONF == ["dynaconf.loaders.env_loader"]
+ assert len(settings._loaders) == 1
+ assert settings.DEFAULT_ENV_FOR_DYNACONF == "hello"
+ assert settings.ENV_SWITCHER_FOR_DYNACONF == "BRUCE_ENV"
+ assert settings.PRELOAD_FOR_DYNACONF == []
+ assert settings.SETTINGS_FILE_FOR_DYNACONF == ["blarg.toml"]
+ assert settings.INCLUDES_FOR_DYNACONF == []
+ assert settings.ENV_FOR_DYNACONF == "awesome"
+ assert settings.current_env == "awesome"
+
+
+def test_envless_mode(tmpdir):
+ data = {
+ "foo": "bar",
+ "hello": "world",
+ "default": 1,
+ "databases": {"default": {"port": 8080}},
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data)
+
+ settings = LazySettings(
+ settings_file="settings.toml"
+ ) # already the default
+ assert settings.FOO == "bar"
+ assert settings.HELLO == "world"
+ assert settings.DEFAULT == 1
+ assert settings.DATABASES.default.port == 8080
+
+
+def test_envless_mode_with_prefix(tmpdir):
+ data = {
+ "prefix_foo": "bar",
+ "hello": "world",
+ "prefix_default": 1,
+ "prefix_databases": {"default": {"port": 8080}},
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data)
+
+ settings = LazySettings(
+ settings_file="settings.toml", filter_strategy=PrefixFilter("prefix")
+ ) # already the default
+ assert settings.FOO == "bar"
+ with pytest.raises(AttributeError):
+ settings.HELLO
+ assert settings.DEFAULT == 1
+ assert settings.DATABASES.default.port == 8080
+
+
+def test_lowercase_read_mode(tmpdir):
+ """
+ Starting on 3.0.0 lowercase keys are enabled by default
+ """
+ data = {
+ "foo": "bar",
+ "hello": "world",
+ "default": 1,
+ "databases": {"default": {"port": 8080}},
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data)
+
+ # settings_files misspelled.. should be `settings_file`
+ settings = LazySettings(settings_files="settings.toml")
+
+ assert settings.FOO == "bar"
+ assert settings.foo == "bar"
+ assert settings.HELLO == "world"
+ assert settings.hello == "world"
+ assert settings.DEFAULT == 1
+ assert settings.default == 1
+ assert settings.DATABASES.default.port == 8080
+ assert settings.databases.default.port == 8080
+
+ assert "foo" in settings
+ assert "FOO" in settings
+
+ # test __dir__
+ results = dir(settings)
+ assert "foo" in results
+ assert "FOO" in results
+
+ results = dir(settings.databases)
+ assert "default" in results
+ assert "DEFAULT" in results
+
+
+def test_settings_dict_like_iteration(tmpdir):
+ """Settings can be iterated just like a dict"""
+ data = {
+ "foo": "bar",
+ "hello": "world",
+ "default": 1,
+ "databases": {"default": {"port": 8080}},
+ }
+ toml_loader.write(str(tmpdir.join("settings.toml")), data)
+
+ # settings_files misspelled.. should be `settings_file`
+ settings = LazySettings(settings_files="settings.toml")
+
+ for key in settings:
+ assert key in settings._store
+
+ for key, value in settings.items():
+ assert settings._store[key] == value
+
+
+def test_prefix_is_not_str_raises():
+ with pytest.raises(TypeError):
+ toml_loader.load(LazySettings(filter_strategy=PrefixFilter(int)))
+ with pytest.raises(TypeError):
+ toml_loader.load(LazySettings(filter_strategy=PrefixFilter(True)))
+
+
+def test_clone():
+ # create a settings object
+ settings = LazySettings(FOO="bar")
+ assert settings.FOO == "bar"
+
+ # clone it
+ cloned = settings.dynaconf.clone()
+ assert cloned.FOO == "bar"
+
+ # modify the cloned settings
+ cloned.FOO = "baz"
+ assert cloned.FOO == "baz"
+
+ # assert original settings is not modified
+ assert settings.FOO == "bar"
+
+
+def test_clone_with_module_type():
+ # create a settings object
+ settings = LazySettings(FOO="bar", A_MODULE=os)
+ # adding a module type makes object unpickaable
+ # then .clone raised an error, this was fixed by copying the dict.
+ assert settings.FOO == "bar"
+
+ # clone it
+ cloned = settings.dynaconf.clone()
+ assert cloned.FOO == "bar"
+
+ # modify the cloned settings
+ cloned.FOO = "baz"
+ assert cloned.FOO == "baz"
+
+ # assert original settings is not modified
+ assert settings.FOO == "bar"
+
+ assert settings.A_MODULE == cloned.A_MODULE
+
+
+def test_wrap_existing_settings():
+ """
+ Wrap an existing settings object
+ """
+ settings = LazySettings(FOO="bar")
+ assert settings.FOO == "bar"
+
+ # wrap it
+ wrapped = LazySettings(settings._wrapped)
+ assert wrapped.FOO == "bar"
+
+ # modify the wrapped settings
+ wrapped.FOO = "baz"
+ assert wrapped.FOO == "baz"
+
+ # assert original settings is also modified as they have the same wrapped
+ assert settings.FOO == "baz"
+
+
+def test_wrap_existing_settings_clone():
+ """
+ Wrap an existing settings object
+ """
+ settings = LazySettings(FOO="bar")
+ assert settings.FOO == "bar"
+
+ # wrap it
+ wrapped = LazySettings(settings.dynaconf.clone())
+ assert wrapped.FOO == "bar"
+
+ # modify the wrapped settings
+ wrapped.FOO = "baz"
+ assert wrapped.FOO == "baz"
+
+ # assert original settings is not changes as we used a wrapped clone
+ assert settings.FOO == "bar"
+
+
+def test_list_entries_from_yaml_should_not_duplicate_when_merged(tmpdir):
+ data = {
+ "default": {
+ "SOME_KEY": "value",
+ "SOME_LIST": ["item_1", "item_2", "item_3"],
+ },
+ "other": {"SOME_KEY": "new_value", "SOME_LIST": ["item_4", "item_5"]},
+ }
+ yaml_loader.write(str(tmpdir.join("test_settings.yaml")), data)
+
+ settings = Dynaconf(
+ settings_files="test_settings.yaml",
+ environments=True,
+ merge_enabled=True,
+ )
+
+ expected_default_value = BoxList(["item_1", "item_2", "item_3"])
+ expected_other_value = BoxList(
+ ["item_1", "item_2", "item_3", "item_4", "item_5"]
+ )
+
+ assert settings.from_env("default").SOME_LIST == expected_default_value
+ assert settings.from_env("other").SOME_LIST == expected_other_value
diff --git a/tests/test_basic.py b/tests/test_basic.py
new file mode 100644
index 0000000..c102c8f
--- /dev/null
+++ b/tests/test_basic.py
@@ -0,0 +1,7 @@
+from __future__ import annotations
+
+from dynaconf import settings
+
+
+def test_has_wrapped():
+ assert settings.configured is True
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644
index 0000000..3ff506d
--- /dev/null
+++ b/tests/test_cli.py
@@ -0,0 +1,466 @@
+from __future__ import annotations
+
+import json
+import os
+from pathlib import Path
+
+import pytest
+
+from dynaconf import default_settings
+from dynaconf import LazySettings
+from dynaconf.cli import EXTS
+from dynaconf.cli import main
+from dynaconf.cli import read_file_in_root_directory
+from dynaconf.cli import WRITERS
+from dynaconf.utils.files import read_file
+from dynaconf.vendor.click.testing import CliRunner
+
+
+runner = CliRunner()
+settings = LazySettings(OPTION_FOR_TESTS=True, environments=True)
+
+
+def run(cmd, env=None, attr="output"):
+ result = runner.invoke(main, cmd, env=env, catch_exceptions=False)
+ return getattr(result, attr)
+
+
+def test_version():
+ assert read_file_in_root_directory("VERSION") in run(["--version"])
+
+
+def test_help():
+ assert "Dynaconf - Command Line Interface" in run(["--help"])
+
+
+def test_banner(clean_env):
+ assert "Learn more at:" in run(["--banner"])
+
+
+def test_init_with_instance_raises(tmpdir):
+ result = run(
+ [
+ "-i",
+ "tests.test_cli.settings",
+ "init",
+ "--env",
+ "test",
+ f"--path={str(tmpdir)}",
+ ]
+ )
+ assert "-i/--instance option is not allowed for `init` command" in result
+
+
+def test_init_with_env_warns(tmpdir):
+ result = run(["init", "--env", "test", f"--path={str(tmpdir)}"])
+ assert "The --env/-e option is deprecated" in result
+
+
+@pytest.mark.parametrize("fileformat", EXTS)
+def test_init_with_path(fileformat, tmpdir):
+ # run twice to force load of existing files
+ if fileformat == "env":
+ path = tmpdir.join(".env")
+ secs_path = None
+ else:
+ path = tmpdir.join(f"settings.{fileformat}")
+ secs_path = tmpdir.join(f"/.secrets.{fileformat}")
+
+ for _ in (1, 2):
+ run(
+ [
+ "init",
+ f"--format={fileformat}",
+ "-v",
+ "name=bruno",
+ "-s",
+ "token=secret for",
+ f"--path={str(tmpdir)}",
+ "-y",
+ ]
+ )
+
+ sets = Path(str(path))
+ assert sets.exists() is True
+ assert "bruno" in read_file(
+ str(sets), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+
+ if secs_path:
+ secs = Path(str(secs_path))
+ assert secs.exists() is True
+ assert "secret for" in read_file(
+ str(secs), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+
+ if fileformat != "env":
+ gign = Path(str(tmpdir.join(".gitignore")))
+ assert gign.exists() is True
+ assert ".secrets.*" in read_file(
+ str(gign), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+
+
+def test_list(testdir):
+ """Test list command shows only user defined vars"""
+ result = run(
+ ["list"],
+ env={
+ "ROOT_PATH_FOR_DYNACONF": testdir,
+ "INSTANCE_FOR_DYNACONF": "tests.config.settings",
+ },
+ )
+ assert "TEST_KEY<str> 'test_value'" in result
+
+
+def test_get(testdir):
+ """Tests get command"""
+ result = run(
+ ["get", "TEST_KEY"],
+ env={
+ "ROOT_PATH_FOR_DYNACONF": testdir,
+ "INSTANCE_FOR_DYNACONF": "tests.config.settings",
+ },
+ )
+ assert result == "test_value"
+
+
+def test_get_json_dict(testdir):
+ """Tests get command printing json"""
+ env = env = {
+ "ROOT_PATH_FOR_DYNACONF": testdir,
+ "DYNACONF_DATA__KEY": "value",
+ "DYNACONF_DATA__OTHERKEY": "other value",
+ "INSTANCE_FOR_DYNACONF": "tests.config.settings",
+ }
+ result = run(["get", "data"], env=env)
+ assert result == '{"KEY": "value", "OTHERKEY": "other value"}'
+
+
+def test_get_lower(testdir):
+ """Tests get command"""
+ result = run(
+ ["get", "test_key"],
+ env={
+ "ROOT_PATH_FOR_DYNACONF": testdir,
+ "INSTANCE_FOR_DYNACONF": "tests.config.settings",
+ },
+ )
+ assert result == "test_value"
+
+
+def test_get_unparsed(testdir):
+ """Tests get command"""
+ result = run(
+ ["get", "COMMENTJSON_ENABLED_FOR_DYNACONF", "-u"],
+ env={
+ "ROOT_PATH_FOR_DYNACONF": testdir,
+ "INSTANCE_FOR_DYNACONF": "tests.config.settings",
+ },
+ )
+ assert result == "@bool False"
+
+
+def test_get_with_default(testdir):
+ """Tests get command"""
+ result = run(
+ ["get", "this_obviously_doesnt_exist_yet", "-d", "Hello123"],
+ env={
+ "ROOT_PATH_FOR_DYNACONF": testdir,
+ "INSTANCE_FOR_DYNACONF": "tests.config.settings",
+ },
+ )
+ assert result == "Hello123"
+
+
+def test_get_other_env(tmpdir):
+ """Tests get command"""
+ settings_file = tmpdir.join("settings.json")
+ settings_file.write(
+ '{"prod": {"name": "admin"}, "development": {"name": "dev"}}'
+ )
+ instance_file = tmpdir.join("myconfig.py")
+ instance_file.write(
+ "settings = __import__('dynaconf').Dynaconf("
+ f"settings_file=r'{str(settings_file)}',"
+ "environments=True"
+ ")"
+ )
+
+ result = run(
+ ["get", "name"],
+ env={
+ "INSTANCE_FOR_DYNACONF": "myconfig.settings",
+ },
+ )
+ assert result == "dev"
+
+ result = run(
+ ["get", "name", "-e", "prod"],
+ env={
+ "INSTANCE_FOR_DYNACONF": "myconfig.settings",
+ },
+ )
+ assert result == "admin"
+
+
+def test_help_dont_require_instance(testdir):
+ result = os.system("dynaconf list --help")
+ assert result == 0
+
+
+def test_list_export_json(testdir):
+ result = run(
+ ["-i", "tests.config.settings", "list", "-o", "sets.json"],
+ env={"ROOT_PATH_FOR_DYNACONF": testdir},
+ )
+ assert "TEST_KEY<str> 'test_value'" in result
+ assert json.loads(read_file("sets.json"))["TEST_KEY"] == "test_value"
+
+
+def test_list_with_all(testdir):
+ """Test list command with --all includes interval vars"""
+ result = run(
+ ["-i", "tests.config.settings", "list", "-a"],
+ env={"ROOT_PATH_FOR_DYNACONF": testdir},
+ )
+
+ assert "TEST_KEY<str> 'test_value'" in result
+
+
+@pytest.mark.parametrize("loader", WRITERS)
+def test_list_with_loader(loader):
+ result = run(["-i", "tests.config.settings", "list", "-l", loader])
+ assert "Working in main environment" in result
+
+
+@pytest.mark.parametrize("env", ["default", "development"])
+def test_list_with_env(testdir, env):
+ result = run(
+ ["-i", "tests.config.settingsenv", "list", "-e", env],
+ env={"ROOT_PATH_FOR_DYNACONF": testdir},
+ )
+ assert f"Working in {env} environment" in result
+
+
+def test_list_with_instance():
+ result = run(["-i", "tests.test_cli.settings", "list"])
+ assert "OPTION_FOR_TESTS<bool> True" in result
+
+
+def test_list_with_instance_from_env():
+ result = run(
+ ["list"], {"INSTANCE_FOR_DYNACONF": "tests.test_cli.settings"}
+ )
+ assert "OPTION_FOR_TESTS<bool> True" in result
+
+
+def test_instance_attribute_error():
+ result = run(["-i", "tests.test_cli.idontexist", "list"])
+ assert "has no attribute 'idontexist'" in result
+
+
+def test_instance_import_error():
+ result = run(["-i", "idontexist.settings", "list"])
+ assert "Error: No module named 'idontexist'" in result
+
+
+def test_instance_pypath_error():
+ result = run(["-i", "idontexist", "list"])
+ assert "Error: invalid path to settings instance: idontexist" in result
+
+
+def test_list_with_key():
+ result = run(["-i", "tests.config.settings", "list", "-k", "TEST_KEY"])
+ assert "TEST_KEY<str> 'test_value'" in result
+
+
+def test_list_with_invalid_key():
+ result = run(["-i", "tests.config.settings", "list", "-k", "TEST_KEY.foo"])
+ assert "Key not found" in result
+
+
+def test_list_with_key_export_json(tmpdir):
+ result = run(
+ [
+ "-i",
+ "tests.config.settings",
+ "list",
+ "-k",
+ "TEST_KEY",
+ "-o",
+ "sets.json",
+ ]
+ )
+
+ assert "TEST_KEY<str> 'test_value'" in result
+
+ assert "TEST_KEY" in read_file("sets.json")
+ assert json.loads(read_file("sets.json"))["TEST_KEY"] == "test_value"
+ with pytest.raises(KeyError):
+ json.loads(read_file("sets.json"))["ANOTHER_KEY"]
+
+
+def test_list_with_missing_key():
+ result = run(["-i", "tests.config.settings", "list", "-k", "NOTEXISTS"])
+ assert "Key not found" in result
+
+
+@pytest.mark.parametrize("writer", EXTS)
+@pytest.mark.parametrize("env", ["default", "development"])
+@pytest.mark.parametrize("onlydir", (True, False))
+def test_write(writer, env, onlydir, tmpdir):
+ if onlydir is True:
+ tmpfile = tmpdir
+ else:
+ tmpfile = tmpdir.join(f"settings.{writer}")
+
+ settingspath = tmpdir.join(f"settings.{writer}")
+ secretfile = tmpdir.join(f".secrets.{writer}")
+ env_file = tmpdir.join(".env")
+
+ result = run(
+ [
+ "write",
+ writer,
+ "-v",
+ "TESTVALUE=1",
+ "-s",
+ "SECRETVALUE=2",
+ "-e",
+ env,
+ "-y",
+ "-p",
+ str(tmpfile),
+ ]
+ )
+ if writer != "env":
+ assert f"Data successful written to {settingspath}" in result
+ assert "TESTVALUE" in read_file(
+ str(settingspath), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+ assert "SECRETVALUE" in read_file(
+ str(secretfile), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+ else:
+ assert f"Data successful written to {env_file}" in result
+ assert "TESTVALUE" in read_file(
+ str(env_file), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+ assert "SECRETVALUE" in read_file(
+ str(env_file), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+
+
+@pytest.mark.parametrize("path", (".env", "./.env"))
+def test_write_dotenv(path, tmpdir):
+ env_file = tmpdir.join(path)
+
+ result = run(
+ [
+ "write",
+ "env",
+ "-v",
+ "TESTVALUE=1",
+ "-s",
+ "SECRETVALUE=2",
+ "-y",
+ "-p",
+ str(env_file),
+ ]
+ )
+
+ assert f"Data successful written to {env_file}" in result
+ assert "TESTVALUE" in read_file(
+ str(env_file), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+ assert "SECRETVALUE" in read_file(
+ str(env_file), encoding=default_settings.ENCODING_FOR_DYNACONF
+ )
+
+
+VALIDATION = """
+[default]
+version = {must_exist=true}
+name = {must_exist=true}
+password = {must_exist=false}
+
+# invalid rule, must always be a dict
+a = 1
+
+ [default.age]
+ must_exist = true
+ lte = 30
+ gte = 10
+
+[production]
+project = {eq="hello_world"}
+host = {is_not_in=['test.com']}
+"""
+
+TOML_VALID = """
+[default]
+version = "1.0.0"
+name = "Dynaconf"
+age = 15
+
+[production]
+project = "hello_world"
+password = 'exists only in prod'
+"""
+
+TOML_INVALID = """
+[default]
+version = "1.0.0"
+name = "Dynaconf"
+age = 35
+
+[production]
+project = "This is not hello_world"
+password = 'exists only in prod'
+host = "test.com"
+"""
+
+
+def test_validate(tmpdir):
+ validation_file = tmpdir.join("dynaconf_validators.toml")
+ validation_file.write(VALIDATION)
+
+ toml_valid = tmpdir.mkdir("valid").join("settings.toml")
+ toml_valid.write(TOML_VALID)
+
+ toml_invalid = tmpdir.mkdir("invalid").join("settings.toml")
+ toml_invalid.write(TOML_INVALID)
+
+ result = run(
+ [
+ "-i",
+ "tests.config.settingsenv",
+ "validate",
+ "-p",
+ str(validation_file),
+ ],
+ {"SETTINGS_FILE_FOR_DYNACONF": str(toml_valid)},
+ )
+ assert "Validation success!" in result
+
+ result = run(
+ [
+ "-i",
+ "tests.test_cli.settings",
+ "validate",
+ "-p",
+ str(Path(str(validation_file)).parent),
+ ],
+ {"SETTINGS_FILE_FOR_DYNACONF": str(toml_invalid)},
+ )
+ assert "age must lte 30 but it is 35 in env default" in result
+ assert (
+ "project must eq hello_world but it is This is not hello_world "
+ "in env production" in result
+ )
+ assert (
+ "host must is_not_in ['test.com'] but it is test.com in env "
+ "production" in result
+ )
+ assert "Validation success!" not in result
diff --git a/tests/test_compat.py b/tests/test_compat.py
new file mode 100644
index 0000000..5205580
--- /dev/null
+++ b/tests/test_compat.py
@@ -0,0 +1,56 @@
+from __future__ import annotations
+
+import os
+
+import pytest
+
+from dynaconf import LazySettings
+
+
+@pytest.mark.parametrize(
+ "deprecated,value,new",
+ [
+ ("DYNACONF_NAMESPACE", "FOO", "ENV_FOR_DYNACONF"),
+ ("NAMESPACE_FOR_DYNACONF", "FOO", "ENV_FOR_DYNACONF"),
+ ("DYNACONF_SETTINGS_MODULE", "dfoo.py", "SETTINGS_FILE_FOR_DYNACONF"),
+ ("SETTINGS_MODULE", "dfoo.py", "SETTINGS_FILE_FOR_DYNACONF"),
+ ("PROJECT_ROOT", "./", "ROOT_PATH_FOR_DYNACONF"),
+ ("DYNACONF_SILENT_ERRORS", True, "SILENT_ERRORS_FOR_DYNACONF"),
+ ("DYNACONF_ALWAYS_FRESH_VARS", ["BAR"], "FRESH_VARS_FOR_DYNACONF"),
+ ("GLOBAL_ENV_FOR_DYNACONF", "MYAPP", "ENVVAR_PREFIX_FOR_DYNACONF"),
+ ("BASE_NAMESPACE_FOR_DYNACONF", "THIS", "DEFAULT_ENV_FOR_DYNACONF"),
+ ],
+)
+def test_dynaconf_namespace_renamed(tmpdir, recwarn, deprecated, value, new):
+ settings = LazySettings(**{deprecated: value})
+
+ assert len(recwarn) == 1
+ assert issubclass(recwarn[0].category, DeprecationWarning)
+ assert deprecated in str(recwarn[0].message)
+ assert new in str(recwarn[0].message)
+ assert settings.get(new) == value
+
+
+def test_envvar_prefix_for_dynaconf(tmpdir, recwarn):
+ os.environ["AWESOMEAPP_FOO"] = "1"
+ os.environ["AWESOMEAPP_BAR"] = "false"
+ os.environ["AWESOMEAPP_LIST"] = '["item1", "item2"]'
+ os.environ["AWESOMEAPP_FLOAT"] = "42.2"
+
+ settings = LazySettings(ENVVAR_PREFIX_FOR_DYNACONF="AWESOMEAPP")
+
+ assert settings.FOO == 1
+ assert settings.BAR is False
+ assert settings.LIST == ["item1", "item2"]
+ assert settings.FLOAT == 42.2
+
+ settings2 = LazySettings(GLOBAL_ENV_FOR_DYNACONF="AWESOMEAPP")
+
+ assert len(recwarn) == 1
+ assert issubclass(recwarn[0].category, DeprecationWarning)
+ assert "GLOBAL_ENV_FOR_DYNACONF" in str(recwarn[0].message)
+
+ assert settings2.FOO == 1
+ assert settings2.BAR is False
+ assert settings2.LIST == ["item1", "item2"]
+ assert settings2.FLOAT == 42.2
diff --git a/tests/test_django.py b/tests/test_django.py
new file mode 100644
index 0000000..55c6d57
--- /dev/null
+++ b/tests/test_django.py
@@ -0,0 +1,44 @@
+from __future__ import annotations
+
+import os
+import sys
+
+import dynaconf
+
+
+def test_djdt_382(tmpdir):
+ settings_file = tmpdir.join("settings.py")
+ settings_file.write("\n".join(["SECRET_KEY = 'dasfadfds2'"]))
+ tmpdir.join("__init__.py").write("")
+ os.environ["DJANGO_SETTINGS_MODULE"] = "settings"
+ sys.path.append(str(tmpdir))
+ __import__("settings")
+ settings = dynaconf.DjangoDynaconf("settings", environments=True)
+ settings.configure(settings_module="settings")
+ assert settings.SECRET_KEY == "dasfadfds2"
+ assert settings.is_overridden("FOO") is False
+
+
+def test_override_settings_596(tmpdir):
+ settings_file = tmpdir.join("other_settings.py")
+ settings_file.write("\n".join(["SECRET_KEY = 'abcdef'"]))
+ tmpdir.join("__init__.py").write("")
+ os.environ["DJANGO_SETTINGS_MODULE"] = "other_settings"
+ sys.path.append(str(tmpdir))
+ __import__("other_settings")
+ settings = dynaconf.DjangoDynaconf("other_settings", environments=True)
+ settings.configure(settings_module="other_settings")
+ assert settings.SECRET_KEY == "abcdef"
+
+ # mimic what django.test.utils.override_settings does
+ class UserSettingsHolder(dynaconf.LazySettings):
+ _django_override = True
+
+ override = UserSettingsHolder(settings._wrapped)
+ override.SECRET_KEY = "foobar"
+
+ # overridden settings is changed
+ assert override.SECRET_KEY == "foobar"
+
+ # original not changed
+ assert settings.SECRET_KEY == "abcdef"
diff --git a/tests/test_dynabox.py b/tests/test_dynabox.py
new file mode 100644
index 0000000..b78f4c3
--- /dev/null
+++ b/tests/test_dynabox.py
@@ -0,0 +1,108 @@
+from __future__ import annotations
+
+from collections import namedtuple
+
+import pytest
+
+from dynaconf.utils.boxing import DynaBox
+from dynaconf.vendor.box import Box
+from dynaconf.vendor.box import BoxKeyError
+from dynaconf.vendor.box import BoxList
+
+
+DBDATA = namedtuple("DbData", ["server", "port"])
+
+
+box = DynaBox(
+ {
+ "server": {
+ "HOST": "server.com",
+ "port": 8080,
+ "PARAMS": {
+ "username": "admin",
+ "PASSWORD": "secret",
+ "token": {"TYPE": 1, "value": 2},
+ },
+ },
+ "database": DBDATA(server="db.com", port=3306),
+ },
+)
+
+
+def test_named_tuple_is_not_transformed():
+ """Issue: https://github.com/dynaconf/dynaconf/issues/595"""
+ assert isinstance(box.database, DBDATA)
+ assert isinstance(box.database, tuple)
+
+
+def test_datatypes():
+ assert isinstance(box.server, dict)
+ assert isinstance(box.server, DynaBox)
+ assert isinstance(box.server.host, str)
+ assert isinstance(box.server.PORT, int)
+
+
+def test_access_lowercase():
+ assert box.server.host == "server.com"
+ assert box.server.port == 8080
+ assert box.server.params.username == "admin"
+ assert box.server.params.password == "secret"
+ assert box.server.params.token.type == 1
+ assert box.server.params.token.value == 2
+
+
+def test_access_uppercase():
+ assert box.SERVER.HOST == "server.com"
+ assert box.SERVER.PORT == 8080
+ assert box.SERVER.PARAMS.USERNAME == "admin"
+ assert box.SERVER.PARAMS.PASSWORD == "secret"
+ assert box.SERVER.PARAMS.TOKEN.TYPE == 1
+ assert box.SERVER.PARAMS.TOKEN.VALUE == 2
+
+
+def test_access_items():
+ assert box["SERVER"]["HOST"] == "server.com"
+ assert box["SERVER"]["PORT"] == 8080
+ assert box["SERVER"]["PARAMS"]["USERNAME"] == "admin"
+ assert box["SERVER"]["PARAMS"]["PASSWORD"] == "secret"
+ assert box["SERVER"]["PARAMS"]["TOKEN"]["TYPE"] == 1
+ assert box["SERVER"]["PARAMS"]["TOKEN"]["VALUE"] == 2
+
+
+def test_access_items_lower():
+ assert box["server"]["HOST"] == "server.com"
+ assert box["server"]["PORT"] == 8080
+ assert box["server"]["params"]["USERNAME"] == "admin"
+ assert box["server"]["params"]["PASSWORD"] == "secret"
+ assert box["server"]["params"]["TOKEN"]["TYPE"] == 1
+ assert box["server"]["params"]["TOKEN"]["VALUE"] == 2
+
+
+def test_get():
+ assert box.get("server").get("host") == "server.com"
+ assert box.get("server").get("port") == 8080
+ assert box.get("server").get("params").username == "admin"
+ assert box.get("server").get("params").password == "secret"
+ assert box.get("server").get("params").token.type == 1
+ assert box.get("server").get("params").token.value == 2
+ assert box.get("server").get("blabla") is None
+ assert box.get("server").get("blabla", "foo") == "foo"
+
+
+def test_copy_no_cause_inf_recursion():
+ box.__copy__()
+ box.copy()
+
+
+def test_accessing_dynabox_inside_boxlist_inside_dynabox():
+ data = DynaBox({"nested": [{"deeper": "nest"}]})
+ assert data.nested[0].deeper == "nest"
+ assert data.NESTED[0].deeper == "nest"
+ assert data.NESTED[0].DEEPER == "nest"
+
+ data = DynaBox({"nested": BoxList([DynaBox({"deeper": "nest"})])})
+ assert data.nested[0].deeper == "nest"
+ assert data.NESTED[0].deeper == "nest"
+ assert isinstance(data.NESTED, BoxList)
+ assert isinstance(data.NESTED[0], DynaBox)
+ assert data.NESTED[0].DEEPER == "nest"
diff --git a/tests/test_endtoend.py b/tests/test_endtoend.py
new file mode 100644
index 0000000..725b020
--- /dev/null
+++ b/tests/test_endtoend.py
@@ -0,0 +1,67 @@
+from __future__ import annotations
+
+import os
+
+
+def test_end_to_end(settings):
+ """
+ settings is fixture configured in conftest.py
+ """
+ assert settings.HOSTNAME == "host.com"
+
+ assert settings.PORT == 5000
+ assert isinstance(settings.PORT, int)
+
+ assert settings.VALUE == 42.1
+ assert isinstance(settings.VALUE, float)
+
+ assert settings.DEBUG is True
+ assert isinstance(settings.DEBUG, bool)
+
+ assert settings.ALIST == ["item1", "item2", "item3"]
+ assert isinstance(settings.ALIST, list)
+ assert len(settings.ALIST) == 3
+
+ assert settings.ADICT == {"key": "value"}
+ assert isinstance(settings.ADICT, dict)
+ assert "key" in settings.ADICT
+
+ assert settings.get("FOO", default="bar") == "bar"
+
+ assert settings.HOSTNAME == "host.com"
+
+ if settings.exists_in_environ("TRAVIS"):
+ assert settings.ENV_BOOLEAN is True
+ assert settings.ENV_INT == 42
+ assert settings.ENV_FLOAT == 42.2
+ assert settings.ENV_LIST == ["dyna", "conf"]
+ assert settings.ENV_PURE_INT == 42
+ assert settings.ENV_STR_INT == "42"
+ assert settings.as_int("ENV_PURE_INT") == 42
+ assert settings.get("ENV_PURE_INT", cast="@int") == 42
+ assert isinstance(settings.ENV_DICT, dict)
+
+ os.environ["ENVVAR_PREFIX_FOR_DYNACONF"] = "OTHER"
+ with settings.using_env("OTHER"):
+ assert settings.TESTING is True
+ assert settings.ENABLED is True
+ assert settings.DISABLED is False
+ os.environ["ENVVAR_PREFIX_FOR_DYNACONF"] = "DYNACONF"
+
+
+def test_boxed_data(settings):
+ assert settings.BOXED_DATA.host == "server.com"
+ assert settings.BOXED_DATA.port == 8080
+ assert settings.BOXED_DATA.params.username == "admin"
+ assert settings.BOXED_DATA.params.password == "secret"
+ assert settings.BOXED_DATA.params.token.type == 1
+ assert settings.BOXED_DATA.params.token.value == 2
+
+
+def test_boxed_data_call(settings):
+ assert settings("boxed_data").host == "server.com"
+ assert settings("boxed_data").port == 8080
+ assert settings("boxed_data").params.username == "admin"
+ assert settings("boxed_data").params.password == "secret"
+ assert settings("boxed_data").params.token.type == 1
+ assert settings("boxed_data").params.token.value == 2
diff --git a/tests/test_env_loader.py b/tests/test_env_loader.py
new file mode 100644
index 0000000..2e85a00
--- /dev/null
+++ b/tests/test_env_loader.py
@@ -0,0 +1,507 @@
+from __future__ import annotations
+
+import os
+import sys
+from collections import OrderedDict
+from os import environ
+
+import pytest
+
+from dynaconf import settings # noqa
+from dynaconf.loaders.env_loader import load
+from dynaconf.loaders.env_loader import load_from_env
+from dynaconf.loaders.env_loader import write
+
+# GLOBAL ENV VARS
+environ["DYNACONF_HOSTNAME"] = "host.com"
+environ["DYNACONF_PORT"] = "@int 5000"
+environ["DYNACONF_ALIST"] = '@json ["item1", "item2", "item3", 123]'
+environ["DYNACONF_ADICT"] = '@json {"key": "value", "int": 42}'
+environ["DYNACONF_DEBUG"] = "@bool true"
+environ["DYNACONF_MUSTBEFRESH"] = "first"
+environ["DYNACONF_MUSTBEALWAYSFRESH"] = "first"
+environ["DYNACONF_SHOULDBEFRESHINCONTEXT"] = "first"
+environ["DYNACONF_VALUE"] = "@float 42.1"
+
+# environ['FRESH_VARS_FOR_DYNACONF'] = '@json ["MUSTBEALWAYSFRESH"]'
+# settings.configure(IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF=True)
+settings.configure(
+ FRESH_VARS_FOR_DYNACONF=["MUSTBEALWAYSFRESH"],
+ ROOT_PATH_FOR_DYNACONF=os.path.dirname(os.path.abspath(__file__)),
+)
+
+SETTINGS_DATA = OrderedDict()
+# Numbers
+SETTINGS_DATA["DYNACONF_INTEGER"] = 42
+SETTINGS_DATA["DYNACONF_FLOAT"] = 3.14
+# Text
+# 'DYNACONF_STRING': Hello,
+SETTINGS_DATA["DYNACONF_STRING2"] = "Hello"
+SETTINGS_DATA["DYNACONF_STRING2_LONG"] = "Hello World!"
+SETTINGS_DATA["DYNACONF_BOOL"] = True
+SETTINGS_DATA["DYNACONF_BOOL2"] = False
+# Use extra quotes to force a string from other type
+SETTINGS_DATA["DYNACONF_STRING21"] = '"42"'
+SETTINGS_DATA["DYNACONF_STRING22"] = "'true'"
+# Arrays must be homogeneous in toml syntax
+SETTINGS_DATA["DYNACONF_ARRAY"] = [1, 2, 3]
+SETTINGS_DATA["DYNACONF_ARRAY2"] = [1.1, 2.2, 3.3]
+SETTINGS_DATA["DYNACONF_ARRAY3"] = ["a", "b", "c"]
+# Dictionaries
+SETTINGS_DATA["DYNACONF_DICT"] = {"val": 123}
+
+SETTINGS_DATA_GROUND_TRUTH = """DYNACONF_TESTING=true
+DYNACONF_INTEGER=42
+DYNACONF_FLOAT=3.14
+DYNACONF_STRING2=Hello
+DYNACONF_STRING2_LONG="Hello World!"
+DYNACONF_BOOL=True
+DYNACONF_BOOL2=False
+DYNACONF_STRING21="42"
+DYNACONF_STRING22="true"
+DYNACONF_ARRAY="[1, 2, 3]"
+DYNACONF_ARRAY2="[1.1, 2.2, 3.3]"
+DYNACONF_ARRAY3="[\'a\', \'b\', \'c\']"
+DYNACONF_DICT="{\'val\': 123}"
+"""
+
+
+def test_write(tmpdir):
+ settings_path = tmpdir.join(".env")
+
+ write(settings_path, SETTINGS_DATA)
+
+ ground_truth = SETTINGS_DATA_GROUND_TRUTH.split("\n")
+
+ with open(str(settings_path)) as fp:
+ lines = fp.readlines()
+ for idx, line in enumerate(lines):
+ line = line.strip()
+ if line.split("=")[0] == "DYNACONF_TESTING":
+ continue # this key is written by the test itself; skip.
+ assert line == ground_truth[idx].strip()
+
+
+def test_env_loader():
+ assert settings.HOSTNAME == "host.com"
+ assert settings.PORT == 5000
+ assert settings.ALIST == ["item1", "item2", "item3", 123]
+ assert settings.ADICT == {"key": "value", "int": 42}
+
+
+def test_single_key():
+ environ["DYNACONF_HOSTNAME"] = "changedhost.com"
+ load(settings, key="HOSTNAME")
+ # hostname is reloaded
+ assert settings.HOSTNAME == "changedhost.com"
+
+
+def test_dotenv_loader():
+ assert settings.DOTENV_INT == 1
+ assert settings.DOTENV_STR == "hello"
+ assert settings.DOTENV_FLOAT == 4.2
+ assert settings.DOTENV_BOOL is False
+ assert settings.DOTENV_JSON == ["1", "2"]
+ assert settings.DOTENV_NOTE is None
+
+
+def test_get_fresh():
+ assert settings.MUSTBEFRESH == "first"
+ environ["DYNACONF_MUSTBEFRESH"] = "second"
+ with pytest.raises(AssertionError):
+ # fresh should now be second
+ assert settings.exists("MUSTBEFRESH")
+ assert settings.get_fresh("MUSTBEFRESH") == "first"
+ assert settings.get_fresh("MUSTBEFRESH") == "second"
+
+ environ["DYNACONF_THISMUSTEXIST"] = "@int 1"
+ # must tnot exist yet (not loaded)
+ assert settings.exists("THISMUSTEXIST") is False
+ # must exist because fresh will call loaders
+ assert settings.exists("THISMUSTEXIST", fresh=True) is True
+ # loaders run only once
+ assert settings.get("THISMUSTEXIST") == 1
+
+ environ["DYNACONF_THISMUSTEXIST"] = "@int 23"
+ del environ["DYNACONF_THISMUSTEXIST"]
+ # this should error because envvar got cleaned
+ # but it is not, so cleaners should be fixed
+ assert settings.get_fresh("THISMUSTEXIST") is None
+ with pytest.raises(AttributeError):
+ settings.THISMUSTEXIST
+ with pytest.raises(KeyError):
+ settings["THISMUSTEXIST"]
+
+ environ["DYNACONF_THISMUSTEXIST"] = "@int 23"
+ load(settings)
+ assert settings.get("THISMUSTEXIST") == 23
+
+
+def test_always_fresh():
+ # assert environ['FRESH_VARS_FOR_DYNACONF'] == '@json ["MUSTBEALWAYSFRESH"]' # noqa
+ assert settings.FRESH_VARS_FOR_DYNACONF == ["MUSTBEALWAYSFRESH"]
+ assert settings.MUSTBEALWAYSFRESH == "first"
+ environ["DYNACONF_MUSTBEALWAYSFRESH"] = "second"
+ assert settings.MUSTBEALWAYSFRESH == "second"
+ environ["DYNACONF_MUSTBEALWAYSFRESH"] = "third"
+ assert settings.MUSTBEALWAYSFRESH == "third"
+
+
+def test_fresh_context():
+ assert settings.SHOULDBEFRESHINCONTEXT == "first"
+ environ["DYNACONF_SHOULDBEFRESHINCONTEXT"] = "second"
+ assert settings.SHOULDBEFRESHINCONTEXT == "first"
+ with settings.fresh():
+ assert settings.get("DOTENV_INT") == 1
+ assert settings.SHOULDBEFRESHINCONTEXT == "second"
+
+
+def test_cleaner():
+ settings.clean()
+ with pytest.raises(AttributeError):
+ assert settings.HOSTNAME == "host.com"
+
+
+def test_empty_string_prefix():
+ environ["_VALUE"] = "underscored"
+ load_from_env(
+ identifier="env_global", key=None, prefix="", obj=settings, silent=True
+ )
+ assert settings.VALUE == "underscored"
+
+
+def test_no_prefix():
+ environ["VALUE"] = "no_prefix"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix=False,
+ obj=settings,
+ silent=True,
+ )
+ assert settings.VALUE == "no_prefix"
+
+
+def test_none_as_string_prefix():
+ environ["NONE_VALUE"] = "none as prefix"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="none",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.VALUE == "none as prefix"
+
+
+def test_backwards_compat_using_env_argument():
+ environ["BLARG_VALUE"] = "BLARG as prefix"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ env="BLARG", # renamed to `prefix` on 3.0.0
+ obj=settings,
+ silent=True,
+ )
+ assert settings.VALUE == "BLARG as prefix"
+
+
+def test_load_signed_integer():
+ environ["799_SIGNED_NEG_INT"] = "-1"
+ environ["799_SIGNED_POS_INT"] = "+1"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="799",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.SIGNED_NEG_INT == -1
+ assert settings.SIGNED_POS_INT == 1
+
+
+def test_env_is_not_str_raises():
+ with pytest.raises(TypeError):
+ load_from_env(settings, prefix=int)
+ with pytest.raises(TypeError):
+ load_from_env(settings, prefix=True)
+
+
+def test_can_load_in_to_dict():
+ os.environ["LOADTODICT"] = "true"
+ sets = {}
+ load_from_env(sets, prefix=False, key="LOADTODICT")
+ assert sets["LOADTODICT"] is True
+
+
+def clean_environ(prefix):
+ keys = [k for k in environ if k.startswith(prefix)]
+ for key in keys:
+ environ.pop(key)
+
+
+@pytest.mark.skipif(
+ sys.platform.startswith("win"),
+ reason="Windows env vars are case insensitive",
+)
+def test_load_dunder(clean_env):
+ """Test load and merge with dunder settings"""
+ clean_environ("DYNACONF_DATABASES")
+ settings.set(
+ "DATABASES",
+ {
+ "default": {
+ "NAME": "db",
+ "ENGINE": "module.foo.engine",
+ "ARGS": {"timeout": 30},
+ "PORTS": [123, 456],
+ }
+ },
+ )
+ # change engine
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__ENGINE"] = "other.module"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES.default.ENGINE == "other.module"
+
+ # change timeout directly
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__ARGS__timeout"] = "99"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES.default.ARGS.timeout == 99
+
+ # add to ARGS
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__ARGS"] = "@merge {retries=10}"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES.default.ARGS.retries == 10
+ assert settings.DATABASES.default.ARGS.timeout == 99
+
+ # Ensure dictionary keeps its format
+ assert settings.DATABASES == {
+ "default": {
+ "NAME": "db",
+ "ENGINE": "other.module",
+ "ARGS": {"timeout": 99, "retries": 10},
+ "PORTS": [123, 456],
+ }
+ }
+ assert "default" in settings["DATABASES"].keys()
+ assert "DEFAULT" not in settings["DATABASES"].keys()
+ assert "NAME" in settings["DATABASES"]["default"].keys()
+ assert "name" not in settings["DATABASES"]["default"].keys()
+
+ # Clean args
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__ARGS"] = "{timeout=8}"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES.default.ARGS == {"timeout": 8}
+
+ # Make args empty
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__ARGS"] = "{}"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES.default.ARGS == {}
+
+ # Remove ARGS key
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__ARGS"] = "@del"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert "ARGS" not in settings.DATABASES.default.keys()
+
+ # add to existing PORTS
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__PORTS"] = "@merge [789, 101112]"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert "ARGS" not in settings.DATABASES.default.keys()
+ assert settings.DATABASES.default.PORTS == [123, 456, 789, 101112]
+
+ # reset PORTS
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__PORTS"] = "[789, 101112]"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert "ARGS" not in settings.DATABASES.default.keys()
+ assert settings.DATABASES.default.PORTS == [789, 101112]
+
+ # delete PORTS
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default__PORTS"] = "@del"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert "ARGS" not in settings.DATABASES.default.keys()
+ assert "PORTS" not in settings.DATABASES.default.keys()
+
+ # reset default key
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default"] = "{}"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES.default == {}
+
+ # remove default
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__default"] = "@del"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES == {}
+
+ # set value to databases
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES__foo"] = "bar"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES == {"foo": "bar"}
+
+ # reset databases
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES"] = "{hello='world'}"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES == {"hello": "world"}
+
+ # also reset databases
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES"] = "{yes='no'}"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert settings.DATABASES == {"yes": "no"}
+
+ # remove databases
+ clean_environ("DYNACONF_DATABASES")
+ environ["DYNACONF_DATABASES"] = "@del"
+ load_from_env(
+ identifier="env_global",
+ key=None,
+ prefix="dynaconf",
+ obj=settings,
+ silent=True,
+ )
+ assert "DATABASES" not in settings
+
+
+def test_filtering_unknown_variables():
+ # Predefine some known variable.
+ settings.MYCONFIG = "bar"
+ # Enable environment filtering.
+ settings.IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF = True
+
+ # Pollute the environment.
+ environ["IGNOREME"] = "foo"
+
+ load_from_env(
+ obj=settings,
+ prefix=False,
+ key=None,
+ silent=True,
+ identifier="env_global",
+ env=False,
+ )
+
+ # Verify the filter works.
+ assert not settings.get("IGNOREME")
+ # Smoke test.
+ assert settings.get("MYCONFIG") == "bar"
+
+
+def test_filtering_unknown_variables_with_prefix():
+ # Predefine some known variable.
+ settings.MYCONFIG = "bar"
+ # Enable environment filtering.
+ settings.IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF = True
+
+ # Pollute the environment.
+ environ["APP_IGNOREME"] = "foo"
+ # Also change legitimate variable.
+ environ["APP_MYCONFIG"] = "ham"
+
+ load_from_env(
+ obj=settings,
+ prefix="APP",
+ key=None,
+ silent=True,
+ identifier="env_global",
+ env=False,
+ )
+
+ # Verify the filter works.
+ assert not settings.get("IGNOREME")
+ # Smoke test.
+ assert settings.get("MYCONFIG") == "ham"
diff --git a/tests/test_envvar_prefix.py b/tests/test_envvar_prefix.py
new file mode 100644
index 0000000..35011b4
--- /dev/null
+++ b/tests/test_envvar_prefix.py
@@ -0,0 +1,46 @@
+from __future__ import annotations
+
+import os
+
+import pytest
+
+from dynaconf import LazySettings
+
+TOML = """
+[global]
+var = "my value"
+
+[false]
+thisvar = "should not be set"
+"""
+
+
+def test_envvar_prefix_lazysettings(tmpdir):
+ os.environ["DYNACONF_PREFIXED_VAR"] = "this is prefixed"
+ tmpfile = tmpdir.mkdir("sub").join("test_no_envvar_prefix.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(
+ environments=True,
+ envvar_prefix=False,
+ settings_file=str(tmpfile),
+ )
+
+ assert settings.VAR == "my value"
+ assert settings.DYNACONF_PREFIXED_VAR == "this is prefixed"
+
+
+def test_envvar_prefix_false_from_envvar(tmpdir):
+ os.environ["DYNACONF_PREFIXED_VAR"] = "this is prefixed"
+ os.environ["ENVVAR_PREFIX_FOR_DYNACONF"] = "false"
+ tmpfile = tmpdir.mkdir("sub").join("test_no_envvar_prefix.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(environments=True, settings_file=str(tmpfile))
+
+ assert settings.VAR == "my value"
+ assert settings.DYNACONF_PREFIXED_VAR == "this is prefixed"
+
+ with pytest.raises(AttributeError):
+ assert settings.THISVAR == "should not be set"
+ del os.environ["ENVVAR_PREFIX_FOR_DYNACONF"]
diff --git a/tests/test_feature_flag.py b/tests/test_feature_flag.py
new file mode 100644
index 0000000..5cbdeb5
--- /dev/null
+++ b/tests/test_feature_flag.py
@@ -0,0 +1,36 @@
+from __future__ import annotations
+
+import os
+
+from dynaconf import LazySettings
+
+
+TOML = """
+[default]
+DATA = true
+
+[premiumuser]
+DASHBOARD="True"
+
+[simpleuser]
+DASHBOARD=false
+"""
+
+
+def test_feature_flag(tmpdir):
+
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(environments=True, settings_file=str(tmpfile))
+
+ assert settings.flag("dashboard", "premiumuser") is True
+ assert settings.flag("dashboard", "simpleuser") is False
+ assert settings.flag("dashboard") is False
+
+ # ensure data is fresh
+ os.environ["DYNACONF_DASHBOARD"] = "@bool on"
+ assert settings.flag("dashboard") is True
+
+ os.environ["DYNACONF_DASHBOARD"] = "False"
+ assert settings.flag("dashboard") is False
diff --git a/tests/test_flask.py b/tests/test_flask.py
new file mode 100644
index 0000000..fbd40d1
--- /dev/null
+++ b/tests/test_flask.py
@@ -0,0 +1,144 @@
+from __future__ import annotations
+
+from collections import namedtuple
+
+import pytest
+from flask import Flask
+
+from dynaconf.contrib import FlaskDynaconf
+from tests_functional.flask_with_dotenv.app import app as flask_app
+
+
+DBDATA = namedtuple("DbData", ["server", "port"])
+
+
+def test_named_tuple_config():
+ app = Flask(__name__)
+ app.config["DBDATA"] = DBDATA(server="localhost", port=5432)
+ FlaskDynaconf(app)
+ assert app.config["DBDATA"].server == "localhost"
+ assert app.config["DBDATA"].port == 5432
+ assert isinstance(app.config["DBDATA"], DBDATA)
+
+
+def test_named_tuple_config_using_initapp():
+ app = Flask(__name__)
+ FlaskDynaconf(app)
+ app.config["DBDATA"] = DBDATA(server="localhost", port=5432)
+ assert app.config["DBDATA"].server == "localhost"
+ assert app.config["DBDATA"].port == 5432
+ assert isinstance(app.config["DBDATA"], DBDATA)
+
+
+def test_dynamic_load_exts(settings):
+ """Assert that a config based extensions are loaded"""
+ app = Flask(__name__)
+ app.config["EXTENSIONS"] = [
+ "tests_functional.dummy_flask_extension.dummy:init_app"
+ ]
+ FlaskDynaconf(app, dynaconf_instance=settings)
+ app.config.load_extensions()
+ assert app.config.EXTENSIONS == [
+ "tests_functional.dummy_flask_extension.dummy:init_app"
+ ]
+ assert app.is_dummy_loaded is True
+
+
+def test_dynamic_load_entry_point(settings):
+ """Assert that a config based extensions support entry point syntax"""
+ app = Flask(__name__)
+ app.config["EXTENSIONS"] = [
+ "tests_functional.dummy_flask_extension:dummy_instance.init_app"
+ ]
+ FlaskDynaconf(app, dynaconf_instance=settings)
+ app.config.load_extensions()
+ assert app.config.EXTENSIONS == [
+ "tests_functional.dummy_flask_extension:dummy_instance.init_app"
+ ]
+ assert app.extensions["dummy"].__class__.__name__ == "DummyExtensionType"
+
+
+def test_dynamic_load_exts_list(settings):
+ """Assert that a config based extensions are loaded"""
+ app = Flask(__name__)
+ app.config["EXTENSIONS"] = [
+ "tests_functional.dummy_flask_extension.dummy:init_app"
+ ]
+ FlaskDynaconf(app, dynaconf_instance=settings, extensions_list=True)
+ assert app.config.EXTENSIONS == [
+ "tests_functional.dummy_flask_extension.dummy:init_app"
+ ]
+ assert app.is_dummy_loaded is True
+
+
+def test_dynamic_load_exts_no_list(settings):
+ """Assert that a config based extensions are loaded"""
+ app = Flask(__name__)
+ FlaskDynaconf(app, dynaconf_instance=settings, extensions_list=True)
+
+
+def test_flask_dynaconf(settings):
+ """
+ Test Flask app wrapped with FlaskDynaconf
+ """
+ app = Flask(__name__)
+ app.config["MY_VAR"] = "foo"
+ FlaskDynaconf(app, dynaconf_instance=settings)
+ app.config["MY_VAR2"] = "bar"
+
+ assert app.config.HOSTNAME == "host.com"
+ assert app.config.MY_VAR == "foo"
+
+ assert app.config["HOSTNAME"] == "host.com"
+ assert app.config["MY_VAR"] == "foo"
+
+ assert app.config.get("HOSTNAME") == "host.com"
+ assert app.config.get("MY_VAR") == "foo"
+
+ assert app.config("HOSTNAME") == "host.com"
+ assert app.config("MY_VAR") == "foo"
+
+ assert "HOSTNAME" in app.config
+ assert "MY_VAR" in app.config
+
+ # ref: #521
+ assert "NONEXISTENETVAR" not in app.config
+ assert ("NONEXISTENETVAR" in app.config) is False
+
+ assert "MY_VAR" in app.config
+ assert "MY_VAR2" in app.config
+ assert "MY_VAR" in app.config.keys()
+ assert "MY_VAR2" in app.config.keys()
+ assert ("MY_VAR", "foo") in app.config.items()
+ assert ("MY_VAR2", "bar") in app.config.items()
+ assert "foo" in app.config.values()
+ assert "bar" in app.config.values()
+ assert "MY_VAR" in list(app.config)
+ assert "MY_VAR2" in list(app.config)
+ assert app.config.setdefault("MY_VAR", "default") == "foo"
+ assert app.config.setdefault("MY_VAR2", "default") == "bar"
+ assert app.config.setdefault("DEFAULT_VAR", "default") == "default"
+ assert app.config["DEFAULT_VAR"] == "default"
+
+ with pytest.raises(KeyError):
+ app.config["NONEXISTENETVAR"]
+
+ with pytest.raises(AttributeError):
+ app.config.nonexistentattribute
+
+
+def test_flask_with_dot_env():
+ envvars = {
+ "HELLO": "hello flask",
+ "INTVAR": 42,
+ "FLOATVAR": 4.2,
+ "BOOLVAR": True,
+ "JSONVAR": ["flask", "rocks"],
+ }
+ for key, value in envvars.items():
+ assert flask_app.config[key] == value
+
+
+def test_flask_dotenv_cli():
+ with flask_app.test_client() as client:
+ assert client.get("/test").data == b"hello flask"
diff --git a/tests/test_ini_loader.py b/tests/test_ini_loader.py
new file mode 100644
index 0000000..9beda06
--- /dev/null
+++ b/tests/test_ini_loader.py
@@ -0,0 +1,208 @@
+from __future__ import annotations
+
+import pytest
+
+from dynaconf import LazySettings
+from dynaconf.loaders.ini_loader import load
+from dynaconf.strategies.filtering import PrefixFilter
+
+settings = LazySettings(environments=True, ENV_FOR_DYNACONF="PRODUCTION")
+
+
+INI = """
+a = 'a,b'
+[default]
+password = '@int 99999'
+host = "server.com"
+port = '@int 8080'
+alist = item1, item2, '@int 23'
+
+ [[service]]
+ url = "service.com"
+ port = '@int 80'
+
+ [[[auth]]]
+ password = "qwerty"
+ test = '@int 1234'
+
+[development]
+password = '@int 88888'
+host = "devserver.com"
+
+[production]
+password = '@int 11111'
+host = "prodserver.com"
+
+[global]
+global_value = 'global'
+"""
+
+INI2 = """
+[global]
+secret = "@float 42"
+password = '@int 123456'
+host = "otherini.com"
+"""
+
+INIS = [INI, INI2]
+
+
+def test_load_from_ini():
+ """Assert loads from INI string"""
+ load(settings, filename=INI)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+ assert settings.ALIST == ["item1", "item2", 23]
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=INI, env="DEVELOPMENT")
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=INI)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+
+
+def test_load_from_multiple_ini():
+ """Assert loads from INI string"""
+ load(settings, filename=INIS)
+ assert settings.HOST == "otherini.com"
+ assert settings.PASSWORD == 123456
+ assert settings.SECRET == 42.0
+ assert settings.PORT == 8080
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=INIS, env="DEVELOPMENT")
+ assert settings.PORT == 8080
+ assert settings.HOST == "otherini.com"
+ load(settings, filename=INIS)
+ assert settings.HOST == "otherini.com"
+ assert settings.PASSWORD == 123456
+ load(settings, filename=INI, env="DEVELOPMENT")
+ assert settings.PORT == 8080
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=INI)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PASSWORD == 11111
+
+
+def test_no_filename_is_none():
+ """Assert if passed no filename return is None"""
+ assert load(settings) is None
+
+
+def test_key_error_on_invalid_env():
+ """Assert error raised if env is not found in INI"""
+ with pytest.raises(KeyError):
+ load(settings, filename=INI, env="FOOBAR", silent=False)
+
+
+def test_no_key_error_on_invalid_env():
+ """Assert error raised if env is not found in INI"""
+ load(settings, filename=INI, env="FOOBAR", silent=True)
+
+
+def test_load_single_key():
+ """Test loading a single key"""
+ ini = """
+ a = "a,b"
+ [foo]
+ bar = "blaz"
+ ZAZ = "naz"
+ lowerkey = 'hello'
+ UPPERKEY = 'world'
+ """
+ load(settings, filename=ini, env="FOO", key="bar")
+ assert settings.BAR == "blaz"
+ assert settings.exists("BAR") is True
+ assert settings.exists("ZAZ") is False
+ load(settings, filename=ini, env="FOO", key="ZAZ")
+ assert settings.ZAZ == "naz"
+ load(settings, filename=ini, env="FOO", key="LOWERKEY")
+ assert settings.LOWERKEY == "hello"
+ load(settings, filename=ini, env="FOO", key="upperkey")
+ assert settings.UPPERKEY == "world"
+
+
+def test_empty_value():
+ load(settings, filename="")
+
+
+def test_multiple_filenames():
+ load(settings, filename="a.ini,b.ini,c.conf,d.properties")
+
+
+def test_cleaner():
+ load(settings, filename=INI)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+ assert settings.ALIST == ["item1", "item2", 23]
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=INI, env="DEVELOPMENT")
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=INI)
+ assert settings.HOST == "prodserver.com"
+
+ settings.clean()
+ with pytest.raises(AttributeError):
+ assert settings.HOST == "prodserver.com"
+
+
+def test_using_env(tmpdir):
+ load(settings, filename=INI)
+ assert settings.HOST == "prodserver.com"
+
+ tmpfile = tmpdir.mkdir("sub").join("test_using_env.ini")
+ tmpfile.write(INI)
+ with settings.using_env("DEVELOPMENT", filename=str(tmpfile)):
+ assert settings.HOST == "devserver.com"
+ assert settings.HOST == "prodserver.com"
+
+
+def test_load_dunder():
+ """Test load with dunder settings"""
+ ini = """
+ a = "a,b"
+ [foo]
+ colors__white__code = '#FFFFFF'
+ COLORS__white__name = 'white'
+ """
+ load(settings, filename=ini, env="FOO")
+ assert settings.COLORS.white.code == "#FFFFFF"
+ assert settings.COLORS.white.name == "white"
+
+
+def test_envless():
+ settings = LazySettings()
+ ini = """
+ a = "a,b"
+ colors__white__code = '#FFFFFF'
+ COLORS__white__name = 'white'
+ """
+ load(settings, filename=ini)
+ assert settings.a == "a,b"
+ assert settings.COLORS.white.code == "#FFFFFF"
+ assert settings.COLORS.white.name == "white"
+
+
+def test_prefix():
+ settings = LazySettings(filter_strategy=PrefixFilter("prefix"))
+ ini = """
+ prefix_a = "a,b"
+ prefix_colors__white__code = '#FFFFFF'
+ COLORS__white__name = 'white'
+ """
+ load(settings, filename=ini)
+ assert settings.a == "a,b"
+ assert settings.COLORS.white.code == "#FFFFFF"
+ with pytest.raises(AttributeError):
+ settings.COLORS.white.name
diff --git a/tests/test_json_loader.py b/tests/test_json_loader.py
new file mode 100644
index 0000000..53f0f6d
--- /dev/null
+++ b/tests/test_json_loader.py
@@ -0,0 +1,231 @@
+from __future__ import annotations
+
+import json
+
+import pytest
+
+from dynaconf import LazySettings
+from dynaconf.loaders.json_loader import DynaconfEncoder
+from dynaconf.loaders.json_loader import load
+from dynaconf.strategies.filtering import PrefixFilter
+
+
+settings = LazySettings(environments=True, ENV_FOR_DYNACONF="PRODUCTION")
+
+
+JSON = """
+{
+ "a": "a,b",
+ "default": {
+ "password": "@int 99999",
+ "host": "server.com",
+ "port": "@int 8080",
+ "alist": ["item1", "item2", 23],
+ "service": {
+ "url": "service.com",
+ "port": 80,
+ "auth": {
+ "password": "qwerty",
+ "test": 1234
+ }
+ }
+ },
+ "development": {
+ "password": "@int 88888",
+ "host": "devserver.com"
+ },
+ "production": {
+ "password": "@int 11111",
+ "host": "prodserver.com"
+ },
+ "global": {
+ "global_value": "global"
+ }
+}
+"""
+
+# the @float is not needed in JSON but kept to ensure it works
+JSON2 = """
+{
+ "global": {
+ "secret": "@float 42",
+ "password": 123456,
+ "host": "otherjson.com"
+ }
+}
+"""
+
+JSONS = [JSON, JSON2]
+
+
+def test_load_from_json():
+ """Assert loads from JSON string"""
+ load(settings, filename=JSON)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+ assert settings.ALIST == ["item1", "item2", 23]
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=JSON, env="DEVELOPMENT")
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=JSON)
+ assert settings.HOST == "prodserver.com"
+
+
+def test_load_from_multiple_json():
+ """Assert loads from JSON string"""
+ load(settings, filename=JSONS)
+ assert settings.HOST == "otherjson.com"
+ assert settings.PASSWORD == 123456
+ assert settings.SECRET == 42.0
+ assert settings.PORT == 8080
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=JSONS, env="DEVELOPMENT")
+ assert settings.PORT == 8080
+ assert settings.HOST == "otherjson.com"
+ load(settings, filename=JSONS)
+ assert settings.HOST == "otherjson.com"
+ assert settings.PASSWORD == 123456
+ load(settings, filename=JSON, env="DEVELOPMENT")
+ assert settings.PORT == 8080
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=JSON)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PASSWORD == 11111
+
+
+def test_no_filename_is_none():
+ """Assert if passed no filename return is None"""
+ assert load(settings) is None
+
+
+def test_key_error_on_invalid_env():
+ """Assert error raised if env is not found in JSON"""
+ with pytest.raises(KeyError):
+ load(settings, filename=JSON, env="FOOBAR", silent=False)
+
+
+def test_no_key_error_on_invalid_env():
+ """Assert error raised if env is not found in JSON"""
+ load(settings, filename=JSON, env="FOOBAR", silent=True)
+
+
+def test_load_single_key():
+ """Test loading a single key"""
+ _JSON = """
+ {
+ "foo": {
+ "bar": "blaz",
+ "zaz": "naz"
+ }
+ }
+ """
+ load(settings, filename=_JSON, env="FOO", key="bar")
+ assert settings.BAR == "blaz"
+ assert settings.exists("BAR") is True
+ assert settings.exists("ZAZ") is False
+
+
+def test_empty_value():
+ load(settings, filename="")
+
+
+def test_multiple_filenames():
+ load(settings, filename="a.json,b.json,c.json,d.json")
+
+
+def test_cleaner():
+ load(settings, filename=JSON)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+ assert settings.ALIST == ["item1", "item2", 23]
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=JSON, env="DEVELOPMENT")
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=JSON)
+ assert settings.HOST == "prodserver.com"
+
+ settings.clean()
+ with pytest.raises(AttributeError):
+ assert settings.HOST == "prodserver.com"
+
+
+def test_using_env(tmpdir):
+ load(settings, filename=JSON)
+ assert settings.HOST == "prodserver.com"
+
+ tmpfile = tmpdir.mkdir("sub").join("test_using_env.json")
+ tmpfile.write(JSON)
+ with settings.using_env("DEVELOPMENT", filename=str(tmpfile)):
+ assert settings.HOST == "devserver.com"
+ assert settings.HOST == "prodserver.com"
+
+
+def test_load_dunder():
+ """Test loading with dunder settings"""
+ _JSON = """
+ {
+ "foo": {
+ "colors__yellow__code": "#FFCC00",
+ "COLORS__yellow__name": "Yellow"
+ }
+ }
+ """
+ load(settings, filename=_JSON, env="FOO")
+ assert settings.COLORS.yellow.code == "#FFCC00"
+ assert settings.COLORS.yellow.name == "Yellow"
+
+
+def test_dynaconf_encoder():
+ class Dummy:
+ def _dynaconf_encode(self):
+ return "Dummy"
+
+ class DummyNotSerializable:
+ _dynaconf_encode = 42
+
+ data = {"dummy": Dummy()}
+ data_error = {"dummy": DummyNotSerializable()}
+
+ assert json.dumps(data, cls=DynaconfEncoder) == '{"dummy": "Dummy"}'
+
+ with pytest.raises(TypeError):
+ json.dumps(data_error, cls=DynaconfEncoder)
+
+
+def test_envless():
+ settings = LazySettings()
+ _json = """
+ {
+ "colors__yellow__code": "#FFCC00",
+ "COLORS__yellow__name": "Yellow"
+ }
+ """
+ load(settings, filename=_json)
+ assert settings.COLORS.yellow.code == "#FFCC00"
+ assert settings.COLORS.yellow.name == "Yellow"
+
+
+def test_prefix():
+ settings = LazySettings(filter_strategy=PrefixFilter("prefix"))
+ _json = """
+ {
+ "prefix_colors__yellow__code": "#FFCC00",
+ "COLORS__yellow__name": "Yellow"
+ }
+ """
+ load(settings, filename=_json)
+ assert settings.COLORS.yellow.code == "#FFCC00"
+ with pytest.raises(AttributeError):
+ settings.COLORS.yellow.name
diff --git a/tests/test_nested_loading.py b/tests/test_nested_loading.py
new file mode 100644
index 0000000..543b3dc
--- /dev/null
+++ b/tests/test_nested_loading.py
@@ -0,0 +1,386 @@
+from __future__ import annotations
+
+import pytest
+
+from dynaconf.base import LazySettings
+
+
+TOML = """
+[default]
+dynaconf_include = ["plugin1.toml", "plugin2.toml", "plugin2.toml"]
+DEBUG = false
+SERVER = "base.example.com"
+PORT = 6666
+
+[development]
+DEBUG = false
+SERVER = "dev.example.com"
+
+[production]
+DEBUG = false
+SERVER = "prod.example.com"
+"""
+
+MIXED = """
+[default]
+dynaconf_include = ["plugin1.toml", "plugin2.{0}"]
+DEBUG = false
+SERVER = "base.example.com"
+PORT = 6666
+
+[development]
+DEBUG = false
+SERVER = "dev.example.com"
+
+[production]
+DEBUG = false
+SERVER = "prod.example.com"
+"""
+
+MIXED_MERGE = """
+[default]
+dynaconf_include = [
+ "plugin1.toml",
+ "plugin2.json",
+ "plugin2.yaml",
+ "plugin2.ini",
+ "plugin2.py"
+]
+DEBUG = false
+SERVER = "base.example.com"
+PORT = 6666
+
+[development]
+DEBUG = false
+SERVER = "dev.example.com"
+
+[production]
+DEBUG = false
+SERVER = "prod.example.com"
+
+
+[custom.nested_1]
+base = 1
+
+[custom.nested_1.nested_2]
+base = 2
+
+[custom.nested_1.nested_2.nested_3]
+base = 3
+
+[custom.nested_1.nested_2.nested_3.nested_4]
+base = 4
+"""
+
+TOML_PLUGIN = """
+[default]
+SERVER = "toml.example.com"
+PLUGIN_NAME = "testing"
+
+[development]
+SERVER = "toml.example.com"
+PLUGIN = "extra development var"
+
+[production]
+SERVER = "toml.example.com"
+PLUGIN = "extra production var"
+
+[custom.nested_1.nested_2.nested_3.nested_4]
+toml = 5
+"""
+
+TOML_PLUGIN_2 = """
+[default]
+SERVER = "plugin2.example.com"
+PLUGIN_2_SPECIAL = true
+PORT = 4040
+
+[custom.nested_1.nested_2.nested_3.nested_4]
+toml = 5
+"""
+
+TOML_PLUGIN_TEXT = """
+[default]
+database_uri = "toml.example.com"
+port = 8080
+
+[custom.nested_1.nested_2.nested_3.nested_4]
+toml = 5
+"""
+
+JSON_PLUGIN_TEXT = """
+{
+ "default": {
+ "database_uri": "json.example.com",
+ "port": 8080
+ },
+ "custom": {
+ "nested_1": {
+ "nested_2": {
+ "nested_3": {
+ "nested_4": {
+ "json": 5
+ }
+ }
+ }
+ }
+ }
+}
+"""
+
+YAML_PLUGIN_TEXT = """
+default:
+ database_uri: "yaml.example.com"
+ port: 8080
+
+custom:
+ nested_1:
+ nested_2:
+ nested_3:
+ nested_4:
+ yaml: 5
+"""
+
+INI_PLUGIN_TEXT = """
+[default]
+database_uri="ini.example.com"
+port="@int 8080"
+
+[custom]
+ [[nested_1]]
+ [[[nested_2]]]
+ [[[[nested_3]]]]
+ [[[[[nested_4]]]]]
+ ini="@int 5"
+"""
+
+PY_PLUGIN_TEXT = """
+DATABASE_URI = "py.example.com"
+PORT = 8080
+NESTED_1 = {
+ "nested_2": {
+ "nested_3": {
+ "nested_4": {
+ "py": 5
+ }
+ }
+ }
+}
+"""
+
+PLUGIN_TEXT = {
+ "toml": TOML_PLUGIN_TEXT,
+ "yaml": YAML_PLUGIN_TEXT,
+ "json": JSON_PLUGIN_TEXT,
+ "ini": INI_PLUGIN_TEXT,
+ "py": PY_PLUGIN_TEXT,
+}
+
+
+def test_invalid_include_path(tmpdir):
+ """Ensure non existing paths are not loaded."""
+
+ settings_file = tmpdir.join("settings.toml")
+ settings_file.write(TOML)
+
+ settings = LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="DEFAULT",
+ silent=False,
+ LOADERS_FOR_DYNACONF=False,
+ SETTINGS_FILE_FOR_DYNACONF=str(settings_file),
+ )
+
+ # Ensure overrides not happened
+ assert settings.SERVER == "base.example.com"
+ assert settings.DEBUG is False
+
+
+def test_load_nested_toml(tmpdir):
+ """Load a TOML file that includes other TOML files."""
+
+ settings_file = tmpdir.join("settings.toml")
+ settings_file.write(TOML)
+
+ toml_plugin_file = tmpdir.join("plugin1.toml")
+ toml_plugin_file.write(TOML_PLUGIN)
+
+ toml_plugin_file = tmpdir.join("plugin2.toml")
+ toml_plugin_file.write(TOML_PLUGIN_2)
+
+ settings = LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="DEFAULT",
+ silent=False,
+ LOADERS_FOR_DYNACONF=False,
+ ROOT_PATH_FOR_DYNACONF=str(tmpdir),
+ SETTINGS_FILE_FOR_DYNACONF=str(settings_file),
+ )
+
+ # Ensure overrides that happen via TOML plugin config load.
+ assert settings.SERVER == "plugin2.example.com"
+ assert settings.DEBUG is False
+ assert settings.PLUGIN_NAME == "testing"
+
+ # From the second TOML plugin
+ assert settings.PORT == 4040
+ assert settings.PLUGIN_2_SPECIAL is True
+
+
+@pytest.mark.parametrize("ext", ["toml", "json", "yaml", "ini", "py"])
+def test_load_nested_different_types(ext, tmpdir):
+ """Load a TOML file that includes other various settings file types."""
+
+ settings_file = tmpdir.join("settings.toml")
+ settings_file.write(MIXED.format(ext))
+
+ toml_plugin_file = tmpdir.join("plugin1.toml")
+ toml_plugin_file.write(TOML_PLUGIN)
+
+ json_plugin_file = tmpdir.join(f"plugin2.{ext}")
+ json_plugin_file.write(PLUGIN_TEXT[ext])
+
+ settings = LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="DEFAULT",
+ silent=False,
+ LOADERS_FOR_DYNACONF=False,
+ ROOT_PATH_FOR_DYNACONF=str(tmpdir),
+ SETTINGS_FILE_FOR_DYNACONF=str(settings_file),
+ )
+
+ assert settings.DEBUG is False
+ assert settings.DATABASE_URI == f"{ext}.example.com"
+ assert settings.PORT == 8080
+ assert settings.SERVER == "toml.example.com"
+ assert settings.PLUGIN_NAME == "testing"
+
+
+def test_load_nested_different_types_with_merge(tmpdir):
+ """Check merge works for includes."""
+
+ settings_file = tmpdir.join("settings.toml")
+ settings_file.write(MIXED_MERGE)
+
+ toml_plugin_file = tmpdir.join("plugin1.toml")
+ toml_plugin_file.write(TOML_PLUGIN)
+
+ for ext in ["toml", "json", "yaml", "ini", "py"]:
+ json_plugin_file = tmpdir.join(f"plugin2.{ext}")
+ json_plugin_file.write(PLUGIN_TEXT[ext])
+
+ settings = LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="custom",
+ silent=False,
+ LOADERS_FOR_DYNACONF=False,
+ ROOT_PATH_FOR_DYNACONF=str(tmpdir),
+ SETTINGS_FILE_FOR_DYNACONF=str(settings_file),
+ MERGE_ENABLED_FOR_DYNACONF=True,
+ )
+
+ assert settings.DEBUG is False
+ assert settings.DATABASE_URI == f"{ext}.example.com"
+ assert settings.PORT == 8080
+ assert settings.SERVER == "toml.example.com"
+ assert settings.PLUGIN_NAME == "testing"
+
+ # merge asserts
+ assert settings.NESTED_1.base == 1
+ assert settings.NESTED_1.nested_2.base == 2
+ assert settings.NESTED_1.nested_2.nested_3.base == 3
+ assert settings.NESTED_1.nested_2.nested_3.nested_4.base == 4
+ for ext in ["toml", "json", "yaml", "ini", "py"]:
+ assert settings.NESTED_1.nested_2.nested_3.nested_4[ext] == 5
+
+
+def test_programmatically_file_load(tmpdir):
+ """Check file can be included programmatically"""
+ settings_file = tmpdir.join("settings.toml")
+ settings_file.write(
+ """
+ [default]
+ default_var = 'default'
+ """
+ )
+
+ settings = LazySettings(
+ environments=True, SETTINGS_FILE_FOR_DYNACONF=str(settings_file)
+ )
+ assert settings.DEFAULT_VAR == "default"
+
+ toml_plugin_file = tmpdir.join("plugin1.toml")
+ toml_plugin_file.write(
+ """
+ [development]
+ plugin_value = 'plugin'
+ """
+ )
+
+ settings.load_file(path=str(toml_plugin_file))
+ assert settings.PLUGIN_VALUE == "plugin"
+
+ settings.setenv("production")
+ assert settings.DEFAULT_VAR == "default"
+ # programmatically loaded file is not persisted
+ # once `env` is changed, or a `reload` it will be missed
+ # to persist it needs to go to `INCLUDES_FOR_DYNACONF` variable
+ with pytest.raises(AttributeError):
+ assert settings.PLUGIN_VALUE == "plugin"
+
+
+def test_include_via_python_module_name(tmpdir):
+ """Check if an include can be a Python module name"""
+ settings_file = tmpdir.join("settings.toml")
+ settings_file.write(
+ """
+ [default]
+ default_var = 'default'
+ """
+ )
+
+ dummy_folder = tmpdir.mkdir("dummy")
+ dummy_folder.join("dummy_module.py").write('FOO = "164110"')
+ dummy_folder.join("__init__.py").write('print("initing dummy...")')
+
+ settings = LazySettings(
+ environments=True,
+ SETTINGS_FILE_FOR_DYNACONF=str(settings_file),
+ INCLUDES_FOR_DYNACONF=["dummy.dummy_module"],
+ )
+
+ assert settings.DEFAULT_VAR == "default"
+ assert settings.FOO == "164110"
+
+
+def test_include_via_python_module_name_and_others(tmpdir):
+ """Check if an include can be a Python module name plus others"""
+ settings_file = tmpdir.join("settings.toml")
+ settings_file.write(
+ """
+ [default]
+ default_var = 'default'
+ """
+ )
+
+ dummy_folder = tmpdir.mkdir("dummy")
+ dummy_folder.join("dummy_module.py").write('FOO = "164110"')
+ dummy_folder.join("__init__.py").write('print("initing dummy...")')
+
+ yaml_file = tmpdir.join("otherfile.yaml")
+ yaml_file.write(
+ """
+ default:
+ yaml_value: 748632
+ """
+ )
+
+ settings = LazySettings(
+ environments=True,
+ SETTINGS_FILE_FOR_DYNACONF=str(settings_file),
+ INCLUDES_FOR_DYNACONF=["dummy.dummy_module", "otherfile.yaml"],
+ )
+
+ assert settings.DEFAULT_VAR == "default"
+ assert settings.FOO == "164110"
+ assert settings.YAML_VALUE == 748632
diff --git a/tests/test_py_loader.py b/tests/test_py_loader.py
new file mode 100644
index 0000000..625d87c
--- /dev/null
+++ b/tests/test_py_loader.py
@@ -0,0 +1,189 @@
+from __future__ import annotations
+
+import io
+import os
+
+import pytest
+
+from dynaconf import default_settings
+from dynaconf import LazySettings
+from dynaconf.loaders.py_loader import load
+from dynaconf.loaders.py_loader import try_to_load_from_py_module_name
+from dynaconf.utils import DynaconfDict
+
+
+def test_py_loader_from_file(tmpdir):
+
+ settings = DynaconfDict()
+ dummy_path = tmpdir.join("dummy_module.py")
+ with open(
+ str(dummy_path), "w", encoding=default_settings.ENCODING_FOR_DYNACONF
+ ) as f:
+ f.write('FOO = "bar"')
+
+ load(settings, "dummy_module.py")
+ os.remove("dummy_module.py")
+ load(settings, "dummy_module.py") # will be ignored not found
+
+ assert settings.get("FOO") == "bar"
+
+
+def test_py_loader_from_module(tmpdir):
+
+ settings = DynaconfDict()
+ dummy_folder = tmpdir.mkdir("dummy")
+
+ dummy_folder.join("dummy_module.py").write('FOO = "bar"')
+ dummy_folder.join("__init__.py").write('print("initing dummy...")')
+
+ load(settings, "dummy.dummy_module")
+
+ assert settings.exists("FOO")
+
+
+def test_try_to_load_from_py_module_name(tmpdir):
+ settings = DynaconfDict()
+ dummy_folder = tmpdir.mkdir("dummy")
+
+ dummy_folder.join("dummy_module.py").write('FOO = "bar"')
+ dummy_folder.join("__init__.py").write('print("initing dummy...")')
+
+ try_to_load_from_py_module_name(settings, "dummy.dummy_module")
+
+ assert settings.exists("FOO")
+
+
+def test_negative_try_to_load_from_py_module_name(tmpdir):
+ settings = DynaconfDict()
+ with pytest.raises(ImportError):
+ try_to_load_from_py_module_name(settings, "foo.bar.dummy")
+
+
+def test_silently_try_to_load_from_py_module_name(tmpdir):
+ settings = DynaconfDict()
+ try_to_load_from_py_module_name(settings, "foo.bar.dummy", silent=True)
+
+ assert settings.exists("FOO") is False
+
+
+def test_py_loader_from_file_dunder(clean_env, tmpdir):
+ """Test load with dunder settings"""
+
+ settings = LazySettings(
+ DATABASES={
+ "default": {
+ "NAME": "db",
+ "ENGINE": "module.foo.engine",
+ "ARGS": {"timeout": 30},
+ "PORTS": [123, 456],
+ }
+ }
+ )
+ dummy_path = tmpdir.join("dummy_module.py")
+ with open(
+ str(dummy_path), "w", encoding=default_settings.ENCODING_FOR_DYNACONF
+ ) as f:
+ f.write('F = "bar"')
+ f.write("\n")
+ f.write('COLORS__white__code = "#FFFFFF"')
+ f.write("\n")
+ f.write('DATABASES__default__ENGINE = "other.module"')
+
+ load(settings, "dummy_module.py")
+ os.remove("dummy_module.py")
+ load(settings, "dummy_module.py") # will be ignored not found
+
+ assert settings.get("F") == "bar"
+ assert settings.COLORS == {"white": {"code": "#FFFFFF"}}
+ assert settings.DATABASES.default.NAME == "db"
+ assert settings.DATABASES.default.ENGINE == "other.module"
+
+
+def test_post_load_hooks(clean_env, tmpdir):
+ """Test post load hooks works
+
+ This test uses 3 settings files
+
+ PRELOAD = "plugin_folder/plugin.py"
+ SETTINGS_FILE = "settings.py"
+ HOOKFILES = ["plugin_folder/dynaconf_hooks.py", "dynaconf_hooks.py"]
+
+ The hook file has a function called "post" which is called after
+ loading the settings, that function accepts the argument `settings`
+ which is a copy of the settings object, and returns a dictionary
+ of settings to be merged.
+ """
+
+ # Arrange
+ plugin_folder = tmpdir.mkdir("plugin_folder")
+ plugin_folder.join("__init__.py").write('print("initing plugin...")')
+
+ plugin_path = plugin_folder.join("plugin.py")
+ plugin_hook = plugin_folder.join("dynaconf_hooks.py")
+ settings_path = tmpdir.join("settings.py")
+ settings_hook = tmpdir.join("dynaconf_hooks.py")
+
+ to_write = {
+ str(plugin_path): ["PLUGIN_NAME = 'DummyPlugin'"],
+ str(settings_path): [
+ "INSTALLED_APPS = ['admin']",
+ "COLORS = ['red', 'green']",
+ "DATABASES = {'default': {'NAME': 'db'}}",
+ "BANDS = ['Rush', 'Yes']",
+ ],
+ str(plugin_hook): [
+ "post = lambda settings: "
+ "{"
+ "'PLUGIN_NAME': settings.PLUGIN_NAME.lower(),"
+ "'COLORS': '@merge blue',"
+ "'DATABASES__default': '@merge PORT=5151',"
+ "'DATABASES__default__VERSION': 42,"
+ "'DATABASES__default__FORCED_INT': '@int 12',",
+ "'BANDS': ['Anathema', 'dynaconf_merge']" "}",
+ ],
+ str(settings_hook): [
+ "post = lambda settings: "
+ "{"
+ "'INSTALLED_APPS': [settings.PLUGIN_NAME],"
+ "'dynaconf_merge': True,"
+ "}"
+ ],
+ }
+
+ for path, lines in to_write.items():
+ with open(
+ str(path), "w", encoding=default_settings.ENCODING_FOR_DYNACONF
+ ) as f:
+ for line in lines:
+ f.write(line)
+ f.write("\n")
+
+ # Act
+ settings = LazySettings(
+ preload=["plugin_folder.plugin"], settings_file="settings.py"
+ )
+
+ # Assert
+ assert settings.PLUGIN_NAME == "dummyplugin"
+ assert settings.INSTALLED_APPS == ["admin", "dummyplugin"]
+ assert settings.COLORS == ["red", "green", "blue"]
+ assert settings.DATABASES.default.NAME == "db"
+ assert settings.DATABASES.default.PORT == 5151
+ assert settings.DATABASES.default.VERSION == 42
+ assert settings.DATABASES.default.FORCED_INT == 12
+ assert settings.BANDS == ["Rush", "Yes", "Anathema"]
+ assert settings._loaded_hooks[str(plugin_hook)] == {
+ "post": {
+ "PLUGIN_NAME": "dummyplugin",
+ "COLORS": "@merge blue",
+ "DATABASES__default": "@merge PORT=5151",
+ "DATABASES__default__VERSION": 42,
+ "DATABASES__default__FORCED_INT": "@int 12",
+ "BANDS": ["Anathema", "dynaconf_merge"],
+ }
+ }
+ assert settings._loaded_hooks[str(settings_hook)] == {
+ "post": {
+ "INSTALLED_APPS": ["dummyplugin"],
+ }
+ }
diff --git a/tests/test_redis.py b/tests/test_redis.py
new file mode 100644
index 0000000..3c6b02b
--- /dev/null
+++ b/tests/test_redis.py
@@ -0,0 +1,108 @@
+from __future__ import annotations
+
+import os
+
+import pytest
+
+from dynaconf import LazySettings
+from dynaconf.loaders.redis_loader import delete
+from dynaconf.loaders.redis_loader import load
+from dynaconf.loaders.redis_loader import write
+
+
+def custom_checker(ip_address, port):
+ # This function should be check if the redis server is online and ready
+ # write(settings, {"SECRET": "redis_works"})
+ # return load(settings, key="SECRET")
+ return True
+
+
+DYNACONF_TEST_REDIS_URL = os.environ.get("DYNACONF_TEST_REDIS_URL", None)
+if DYNACONF_TEST_REDIS_URL:
+
+ @pytest.fixture(scope="module")
+ def docker_redis():
+ return DYNACONF_TEST_REDIS_URL
+
+else:
+
+ @pytest.fixture(scope="module")
+ def docker_redis(docker_services):
+ docker_services.start("redis")
+ public_port = docker_services.wait_for_service(
+ "redis", 6379, check_server=custom_checker
+ )
+ url = f"http://{docker_services.docker_ip}:{public_port}"
+ return url
+
+
+@pytest.mark.integration
+def test_redis_not_configured():
+ with pytest.raises(RuntimeError) as excinfo:
+ settings = LazySettings(environments=True)
+ write(settings, {"OTHER_SECRET": "redis_works"})
+ assert "export REDIS_ENABLED_FOR_DYNACONF=true" in str(excinfo.value)
+
+
+@pytest.mark.integration
+def test_write_redis_without_data(docker_redis):
+ os.environ["REDIS_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["REDIS_HOST_FOR_DYNACONF"] = "localhost"
+ os.environ["REDIS_PORT_FOR_DYNACONF"] = "6379"
+ settings = LazySettings(environments=True)
+ with pytest.raises(AttributeError) as excinfo:
+ write(settings)
+ assert "Data must be provided" in str(excinfo.value)
+
+
+@pytest.mark.integration
+def test_write_to_redis(docker_redis):
+ os.environ["REDIS_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["REDIS_HOST_FOR_DYNACONF"] = "localhost"
+ os.environ["REDIS_PORT_FOR_DYNACONF"] = "6379"
+ settings = LazySettings(environments=True)
+
+ write(settings, {"SECRET": "redis_works_with_docker"})
+ load(settings, key="SECRET")
+ assert settings.get("SECRET") == "redis_works_with_docker"
+
+
+@pytest.mark.integration
+def test_load_from_redis_with_key(docker_redis):
+ os.environ["REDIS_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["REDIS_HOST_FOR_DYNACONF"] = "localhost"
+ os.environ["REDIS_PORT_FOR_DYNACONF"] = "6379"
+ settings = LazySettings(environments=True)
+ load(settings, key="SECRET")
+ assert settings.get("SECRET") == "redis_works_with_docker"
+
+
+@pytest.mark.integration
+def test_write_and_load_from_redis_without_key(docker_redis):
+ os.environ["REDIS_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["REDIS_HOST_FOR_DYNACONF"] = "localhost"
+ os.environ["REDIS_PORT_FOR_DYNACONF"] = "6379"
+ settings = LazySettings(environments=True)
+ write(settings, {"SECRET": "redis_works_perfectly"})
+ load(settings)
+ assert settings.get("SECRET") == "redis_works_perfectly"
+
+
+@pytest.mark.integration
+def test_delete_from_redis(docker_redis):
+ os.environ["REDIS_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["REDIS_HOST_FOR_DYNACONF"] = "localhost"
+ os.environ["REDIS_PORT_FOR_DYNACONF"] = "6379"
+ settings = LazySettings(environments=True)
+ write(settings, {"OTHER_SECRET": "redis_works"})
+ load(settings)
+ assert settings.get("OTHER_SECRET") == "redis_works"
+ delete(settings, key="OTHER_SECRET")
+ assert load(settings, key="OTHER_SECRET") is None
+
+
+@pytest.mark.integration
+def test_delete_all_from_redis(docker_redis):
+ settings = LazySettings(environments=True)
+ delete(settings)
+ assert load(settings, key="OTHER_SECRET") is None
diff --git a/tests/test_toml_loader.py b/tests/test_toml_loader.py
new file mode 100644
index 0000000..82cde80
--- /dev/null
+++ b/tests/test_toml_loader.py
@@ -0,0 +1,256 @@
+from __future__ import annotations
+
+import pytest
+
+from dynaconf import LazySettings
+from dynaconf.loaders.toml_loader import encode_nulls
+from dynaconf.loaders.toml_loader import load
+from dynaconf.strategies.filtering import PrefixFilter
+
+settings = LazySettings(environments=True, ENV_FOR_DYNACONF="PRODUCTION")
+
+
+TOML = """
+a = "a,b"
+[default]
+password = "@int 99999"
+host = "server.com"
+port = "@int 8080"
+alist = [
+ "item1",
+ "item2",
+ "@int 23"
+]
+
+ [default.service]
+ url = "service.com"
+ port = 80.0
+
+ [default.service.auth]
+ password = "qwerty"
+ test = 1234.0
+
+[development]
+password = "@int 88888"
+host = "devserver.com"
+
+[production]
+password = "@int 11111"
+HOST = "prodserver.com"
+
+[GLOBAL]
+global_value = "global"
+"""
+
+TOML2 = """
+[global]
+secret = "@float 42"
+password = 123456.0
+host = "othertoml.com"
+"""
+
+INVALID_TOML_TO_BE_REMOVED_ON_4_0_0 = """
+[global]
+secret = "@float 42"
+password = 123456.0
+host = "othertoml.com"
+emojis = "😀😀😀😀"
+encoded_variable="This has accents like � and � � � � just to test encoding �"
+# The above is not allowed by TOML, but it is allowed by Dynaconf < 4.0.0
+"""
+
+
+TOMLS = [TOML, TOML2]
+
+
+def test_load_from_toml_with_invalid_unicode(tmpdir):
+ # THIS TEST MUST FAIL AND BE REMOVED ON 4.0.0
+ load(settings, filename=INVALID_TOML_TO_BE_REMOVED_ON_4_0_0)
+ assert settings.ENCODED_VARIABLE == (
+ "This has accents like � and � � � � just to test encoding �"
+ )
+
+ tmpfile = tmpdir.join("settings.toml")
+ with open(tmpfile.strpath, "w", encoding="utf-8") as f:
+ f.write(INVALID_TOML_TO_BE_REMOVED_ON_4_0_0)
+
+ _settings = LazySettings(
+ settings_files=[tmpfile.strpath], environments=True
+ )
+ assert _settings.ENCODED_VARIABLE == (
+ "This has accents like � and � � � � just to test encoding �"
+ )
+ assert _settings.EMOJIS == "😀😀😀😀"
+
+
+def test_load_from_toml():
+ """Assert loads from TOML string"""
+ load(settings, filename=TOML)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+ assert settings.ALIST == ["item1", "item2", 23]
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=TOML, env="DEVELOPMENT")
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=TOML)
+ assert settings.HOST == "prodserver.com"
+
+
+def test_load_from_multiple_toml():
+ """Assert loads from TOML string"""
+ load(settings, filename=TOMLS)
+ assert settings.HOST == "othertoml.com"
+ assert settings.PASSWORD == 123456
+ assert settings.SECRET == 42.0
+ assert settings.PORT == 8080
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=TOMLS, env="DEVELOPMENT")
+ assert settings.PORT == 8080
+ assert settings.HOST == "othertoml.com"
+ load(settings, filename=TOMLS)
+ assert settings.HOST == "othertoml.com"
+ assert settings.PASSWORD == 123456
+ load(settings, filename=TOML, env="DEVELOPMENT")
+ assert settings.PORT == 8080
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=TOML)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PASSWORD == 11111
+
+
+def test_no_filename_is_none():
+ """Assert if passed no filename return is None"""
+ assert load(settings) is None
+
+
+def test_key_error_on_invalid_env():
+ """Assert error raised if env is not found in TOML"""
+ with pytest.raises(KeyError):
+ load(settings, filename=TOML, env="FOOBAR", silent=False)
+
+
+def test_no_key_error_on_invalid_env():
+ """Assert error raised if env is not found in TOML"""
+ load(settings, filename=TOML, env="FOOBAR", silent=True)
+
+
+def test_load_single_key():
+ """Test loading a single key"""
+ toml = """
+ a = "a,b"
+ [foo]
+ bar = "blaz"
+ ZAZ = "naz"
+ lowerkey = 'hello'
+ UPPERKEY = 'world'
+ """
+ load(settings, filename=toml, env="FOO", key="bar")
+ assert settings.BAR == "blaz"
+ assert settings.exists("BAR") is True
+ assert settings.exists("ZAZ") is False
+ load(settings, filename=toml, env="FOO", key="ZAZ")
+ assert settings.ZAZ == "naz"
+ load(settings, filename=toml, env="FOO", key="LOWERKEY")
+ assert settings.LOWERKEY == "hello"
+ load(settings, filename=toml, env="FOO", key="upperkey")
+ assert settings.UPPERKEY == "world"
+
+
+def test_empty_value():
+ load(settings, filename="")
+
+
+def test_multiple_filenames():
+ load(settings, filename="a.toml,b.tml,c.toml,d.tml")
+
+
+def test_cleaner():
+ load(settings, filename=TOML)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+ assert settings.ALIST == ["item1", "item2", 23]
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=TOML, env="DEVELOPMENT")
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=TOML)
+ assert settings.HOST == "prodserver.com"
+
+ settings.clean()
+ with pytest.raises(AttributeError):
+ assert settings.HOST == "prodserver.com"
+
+
+def test_using_env(tmpdir):
+ load(settings, filename=TOML)
+ assert settings.HOST == "prodserver.com"
+
+ tmpfile = tmpdir.mkdir("sub").join("test_using_env.toml")
+ tmpfile.write(TOML)
+ with settings.using_env("DEVELOPMENT", filename=str(tmpfile)):
+ assert settings.HOST == "devserver.com"
+ assert settings.HOST == "prodserver.com"
+
+
+def test_load_dunder():
+ """Test load with dunder settings"""
+ toml = """
+ a = "a,b"
+ [foo]
+ colors__gray__code = '#CCCCCC'
+ COLORS__gray__name = 'Gray'
+ """
+ load(settings, filename=toml, env="FOO")
+ assert settings.COLORS.gray.code == "#CCCCCC"
+ assert settings.COLORS.gray.name == "Gray"
+
+
+def test_encode_nulls():
+ assert encode_nulls(None) == "@none "
+ assert encode_nulls([None, None]) == ["@none ", "@none "]
+ assert encode_nulls((None, None)) == ["@none ", "@none "]
+ assert encode_nulls({"nullable": None}) == {"nullable": "@none "}
+ assert encode_nulls(1) == 1
+ assert encode_nulls(1.1) == 1.1
+ assert encode_nulls(True) is True
+ assert encode_nulls(False) is False
+ assert encode_nulls("") == ""
+ assert encode_nulls("text") == "text"
+
+
+def test_envless():
+ settings = LazySettings()
+ ini = """
+ a = "a,b"
+ colors__white__code = '#FFFFFF'
+ COLORS__white__name = 'white'
+ """
+ load(settings, filename=ini)
+ assert settings.a == "a,b"
+ assert settings.COLORS.white.code == "#FFFFFF"
+ assert settings.COLORS.white.name == "white"
+
+
+def test_prefix():
+ settings = LazySettings(filter_strategy=PrefixFilter("prefix"))
+ ini = """
+ prefix_a = "a,b"
+ prefix_colors__white__code = '#FFFFFF'
+ COLORS__white__name = 'white'
+ """
+ load(settings, filename=ini)
+ assert settings.a == "a,b"
+ assert settings.COLORS.white.code == "#FFFFFF"
+ with pytest.raises(AttributeError):
+ settings.COLORS.white.name
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..f664a9b
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,484 @@
+from __future__ import annotations
+
+import io
+import json
+import os
+from collections import namedtuple
+
+import pytest
+
+from dynaconf import default_settings
+from dynaconf import Dynaconf
+from dynaconf.loaders.json_loader import DynaconfEncoder
+from dynaconf.utils import build_env_list
+from dynaconf.utils import ensure_a_list
+from dynaconf.utils import extract_json_objects
+from dynaconf.utils import isnamedtupleinstance
+from dynaconf.utils import Missing
+from dynaconf.utils import missing
+from dynaconf.utils import object_merge
+from dynaconf.utils import trimmed_split
+from dynaconf.utils import upperfy
+from dynaconf.utils.files import find_file
+from dynaconf.utils.files import get_local_filename
+from dynaconf.utils.parse_conf import evaluate_lazy_format
+from dynaconf.utils.parse_conf import Formatters
+from dynaconf.utils.parse_conf import Lazy
+from dynaconf.utils.parse_conf import parse_conf_data
+from dynaconf.utils.parse_conf import try_to_encode
+from dynaconf.utils.parse_conf import unparse_conf_data
+
+
+def test_isnamedtupleinstance():
+ """Assert that isnamedtupleinstance works as expected"""
+ Db = namedtuple("Db", ["host", "port"])
+ assert isnamedtupleinstance(Db(host="localhost", port=3306))
+ assert not isnamedtupleinstance(dict(host="localhost", port=3306.0))
+ assert not isnamedtupleinstance(("localhost", "3306"))
+
+ class Foo(tuple):
+ def _fields(self):
+ return ["a", "b"]
+
+ assert not isnamedtupleinstance(Foo())
+
+
+def test_unparse():
+ """Assert bare types are reversed cast"""
+ assert unparse_conf_data("teste") == "teste"
+ assert unparse_conf_data(123) == "@int 123"
+ assert unparse_conf_data(123.4) == "@float 123.4"
+ assert unparse_conf_data(False) == "@bool False"
+ assert unparse_conf_data(True) == "@bool True"
+ assert unparse_conf_data(["a", "b"]) == '@json ["a", "b"]'
+ assert unparse_conf_data({"name": "Bruno"}) == '@json {"name": "Bruno"}'
+ assert unparse_conf_data(None) == "@none "
+ assert unparse_conf_data(Lazy("{foo}")) == "@format {foo}"
+
+
+def test_cast_bool(settings):
+ """Covers https://github.com/dynaconf/dynaconf/issues/14"""
+ assert parse_conf_data(False, box_settings=settings) is False
+ assert settings.get("SIMPLE_BOOL", cast="@bool") is False
+
+
+def test_find_file(tmpdir):
+ """
+ Create a temporary folder structure like the following:
+ tmpXiWxa5/
+ └── child1
+ └── child2
+ └── child3
+ └── child4
+ └── .env
+ └── app.py
+
+ 1) Then try to automatically `find_dotenv` starting in `child4`
+ """
+
+ curr_dir = tmpdir
+ dirs = []
+ for f in ["child1", "child2", "child3", "child4"]:
+ curr_dir = os.path.join(str(curr_dir), f)
+ dirs.append(curr_dir)
+ os.mkdir(curr_dir)
+
+ child4 = dirs[-1]
+
+ assert find_file("file-does-not-exist") == ""
+ assert find_file("/abs-file-does-not-exist") == ""
+
+ for _dir in dirs:
+ # search for abspath return the same path
+ assert os.path.isabs(_dir)
+ assert find_file(_dir) == _dir
+
+ # now place a .env file a few levels up and make sure it's found
+ filename = os.path.join(str(child4), ".env")
+ with open(
+ filename, "w", encoding=default_settings.ENCODING_FOR_DYNACONF
+ ) as f:
+ f.write("TEST=test\n")
+
+ assert find_file(project_root=str(child4)) == filename
+
+ # skip the inner child4/.env and force the find of /tmp.../.env
+ assert find_file(
+ project_root=str(child4), skip_files=[filename]
+ ) == os.path.join(str(tmpdir), ".env")
+
+
+def test_casting_str(settings):
+ res = parse_conf_data("@str 7")
+ assert isinstance(res, str) and res == "7"
+
+ settings.set("value", 7)
+ res = parse_conf_data("@str @jinja {{ this.value }}")(settings)
+ assert isinstance(res, str) and res == "7"
+
+ res = parse_conf_data("@str @format {this.value}")(settings)
+ assert isinstance(res, str) and res == "7"
+
+
+def test_casting_int(settings):
+ res = parse_conf_data("@int 2")
+ assert isinstance(res, int) and res == 2
+
+ settings.set("value", 2)
+ res = parse_conf_data("@int @jinja {{ this.value }}")(settings)
+ assert isinstance(res, int) and res == 2
+
+ res = parse_conf_data("@int @format {this.value}")(settings)
+ assert isinstance(res, int) and res == 2
+
+
+def test_casting_float(settings):
+ res = parse_conf_data("@float 0.3")
+ assert isinstance(res, float) and abs(res - 0.3) < 1e-6
+
+ settings.set("value", 0.3)
+ res = parse_conf_data("@float @jinja {{ this.value }}")(settings)
+ assert isinstance(res, float) and abs(res - 0.3) < 1e-6
+
+ res = parse_conf_data("@float @format {this.value}")(settings)
+ assert isinstance(res, float) and abs(res - 0.3) < 1e-6
+
+
+def test_casting_bool(settings):
+ res = parse_conf_data("@bool true")
+ assert isinstance(res, bool) and res is True
+
+ settings.set("value", "true")
+ res = parse_conf_data("@bool @jinja {{ this.value }}")(settings)
+ assert isinstance(res, bool) and res is True
+
+ settings.set("value", "false")
+ res = parse_conf_data("@bool @format {this.value}")(settings)
+ assert isinstance(res, bool) and res is False
+
+
+def test_casting_json(settings):
+ res = parse_conf_data("""@json {"FOO": "bar"}""")
+ assert isinstance(res, dict)
+ assert "FOO" in res and "bar" in res.values()
+
+ # Test how single quotes cases are handled.
+ # When jinja uses `attr` to render a json string,
+ # it may convert double quotes to single quotes.
+ settings.set("value", "{'FOO': 'bar'}")
+ res = parse_conf_data("@json @jinja {{ this.value }}")(settings)
+ assert isinstance(res, dict)
+ assert "FOO" in res and "bar" in res.values()
+
+ res = parse_conf_data("@json @format {this.value}")(settings)
+ assert isinstance(res, dict)
+ assert "FOO" in res and "bar" in res.values()
+
+ # Test jinja rendering a dict
+ settings.set("value", "OPTION1")
+ settings.set("OPTION1", {"bar": 1})
+ settings.set("OPTION2", {"bar": 2})
+ res = parse_conf_data("@jinja {{ this|attr(this.value) }}")(settings)
+ assert isinstance(res, str)
+ res = parse_conf_data("@json @jinja {{ this|attr(this.value) }}")(settings)
+ assert isinstance(res, dict)
+ assert "bar" in res and res["bar"] == 1
+
+
+def test_disable_cast(monkeypatch):
+ # this casts for int
+ assert parse_conf_data("@int 42", box_settings={}) == 42
+ # now gives pure string
+ with monkeypatch.context() as m:
+ m.setenv("AUTO_CAST_FOR_DYNACONF", "off")
+ assert parse_conf_data("@int 42", box_settings={}) == "@int 42"
+
+
+def test_disable_cast_on_instance():
+ settings = Dynaconf(auto_cast=False, environments=True)
+ assert settings.auto_cast_for_dynaconf is False
+ settings.set("SIMPLE_INT", "@int 42")
+ assert settings.get("SIMPLE_INT") == "@int 42"
+
+
+def test_tomlfy(settings):
+ assert parse_conf_data("1", tomlfy=True, box_settings=settings) == 1
+ assert parse_conf_data("true", tomlfy=True, box_settings=settings) is True
+ assert (
+ parse_conf_data("'true'", tomlfy=True, box_settings=settings) == "true"
+ )
+ assert parse_conf_data('"42"', tomlfy=True, box_settings=settings) == "42"
+ assert parse_conf_data(
+ "[1, 32, 3]", tomlfy=True, box_settings=settings
+ ) == [1, 32, 3]
+ assert parse_conf_data(
+ "[1.1, 32.1, 3.3]", tomlfy=True, box_settings=settings
+ ) == [1.1, 32.1, 3.3]
+ assert parse_conf_data(
+ "['a', 'b', 'c']", tomlfy=True, box_settings=settings
+ ) == ["a", "b", "c"]
+ assert parse_conf_data(
+ "[true, false]", tomlfy=True, box_settings=settings
+ ) == [True, False]
+ assert parse_conf_data(
+ "{key='value', v=1}", tomlfy=True, box_settings=settings
+ ) == {"key": "value", "v": 1}
+
+
+@pytest.mark.parametrize("test_input", ["something=42"])
+def test_tomlfy_unparseable(test_input, settings):
+ assert (
+ parse_conf_data(test_input, tomlfy=True, box_settings=settings)
+ == test_input
+ )
+
+
+def test_missing_sentinel():
+
+ # The missing singleton should always compare truthfully to itself
+ assert missing == missing
+
+ # new instances of Missing should be equal to each other due to
+ # explicit __eq__ implementation check for isinstance.
+ assert missing == Missing()
+
+ # The sentinel should not be equal to None, True, or False
+ assert missing is not None
+ assert missing is not True
+ assert missing is not False
+
+ # But the explicit typecasting of missing to a bool should evaluate to
+ # False
+ assert bool(missing) is False
+
+ assert str(missing) == "<dynaconf.missing>"
+
+
+def test_meta_values(settings):
+ reset = parse_conf_data(
+ "@reset [1, 2]", tomlfy=True, box_settings=settings
+ )
+ assert reset.value == [1, 2]
+ assert reset._dynaconf_reset is True
+ assert "Reset([1, 2])" in repr(reset)
+
+ _del = parse_conf_data("@del", tomlfy=True, box_settings=settings)
+ assert _del.value == ""
+ assert _del._dynaconf_del is True
+ assert "Del()" in repr(_del)
+
+
+def test_merge_existing_list():
+ existing = ["bruno", "karla"]
+ object_merge(existing, existing)
+ # calling twice the same object does not duplicate
+ assert existing == ["bruno", "karla"]
+
+ new = ["erik", "bruno"]
+ object_merge(existing, new)
+ assert new == ["bruno", "karla", "erik", "bruno"]
+
+
+def test_merge_existing_list_unique():
+ existing = ["bruno", "karla"]
+ new = ["erik", "bruno"]
+ object_merge(existing, new, unique=True)
+ assert new == ["karla", "erik", "bruno"]
+
+
+def test_merge_existing_dict():
+ existing = {"host": "localhost", "port": 666}
+ new = {"user": "admin"}
+
+ # calling with same data has no effect
+ object_merge(existing, existing)
+ assert existing == {"host": "localhost", "port": 666}
+
+ object_merge(existing, new)
+ assert new == {"host": "localhost", "port": 666, "user": "admin"}
+
+
+def test_merge_dict_with_meta_values(settings):
+ existing = {"A": 1, "B": 2, "C": 3}
+ new = {
+ "B": parse_conf_data("@del", tomlfy=True, box_settings=settings),
+ "C": parse_conf_data("4", tomlfy=True, box_settings=settings),
+ }
+ object_merge(existing, new)
+ assert new == {"A": 1, "C": 4}
+
+
+def test_trimmed_split():
+ # No sep
+ assert trimmed_split("hello") == ["hello"]
+
+ # a comma sep string
+ assert trimmed_split("ab.toml,cd.yaml") == ["ab.toml", "cd.yaml"]
+ # spaces are trimmed
+ assert trimmed_split(" ab.toml , cd.yaml ") == ["ab.toml", "cd.yaml"]
+
+ # a semicollon sep string
+ assert trimmed_split("ab.toml;cd.yaml") == ["ab.toml", "cd.yaml"]
+ # semicollon are trimmed
+ assert trimmed_split(" ab.toml ; cd.yaml ") == ["ab.toml", "cd.yaml"]
+
+ # has comma and also semicollon (semicollon has precedence)
+ assert trimmed_split("ab.toml,cd.yaml;ef.ini") == [
+ "ab.toml,cd.yaml",
+ "ef.ini",
+ ]
+
+ # has comma and also semicollon (changing precedence)
+ assert trimmed_split("ab.toml,cd.yaml;ef.ini", seps=(",", ";")) == [
+ "ab.toml",
+ "cd.yaml;ef.ini",
+ ]
+
+ # using different separator
+ assert trimmed_split("ab.toml|cd.yaml", seps=("|")) == [
+ "ab.toml",
+ "cd.yaml",
+ ]
+
+
+def test_ensure_a_list():
+
+ # No data is empty list
+ assert ensure_a_list(None) == []
+
+ # Sequence types is only converted
+ assert ensure_a_list([1, 2]) == [1, 2]
+ assert ensure_a_list((1, 2)) == [1, 2]
+ assert ensure_a_list({1, 2}) == [1, 2]
+
+ # A string is trimmed_splitted
+ assert ensure_a_list("ab.toml") == ["ab.toml"]
+ assert ensure_a_list("ab.toml,cd.toml") == ["ab.toml", "cd.toml"]
+ assert ensure_a_list("ab.toml;cd.toml") == ["ab.toml", "cd.toml"]
+
+ # other types get wrapped in a list
+ assert ensure_a_list(1) == [1]
+
+
+def test_get_local_filename():
+ settings_path = os.path.join("foo", "b", "conf.toml")
+ local_path = os.path.join("foo", "b", "conf.local.toml")
+ assert get_local_filename(settings_path) == local_path
+
+
+def test_upperfy():
+ assert upperfy("foo") == "FOO"
+ assert upperfy("foo__bar") == "FOO__bar"
+ assert upperfy("foo__bar__ZAZ") == "FOO__bar__ZAZ"
+ assert (
+ upperfy("foo__bar__ZAZ__naz__TAZ_ZAZ") == "FOO__bar__ZAZ__naz__TAZ_ZAZ"
+ )
+ assert upperfy("foo_bar") == "FOO_BAR"
+ assert upperfy("foo_BAR") == "FOO_BAR"
+
+
+def test_lazy_format_class():
+ value = Lazy("{this[FOO]}/bar")
+ settings = {"FOO": "foo"}
+ assert value(settings) == "foo/bar"
+ assert str(value) == value.value
+ assert repr(value) == f"'@{value.formatter} {value.value}'"
+
+
+def test_evaluate_lazy_format_decorator(settings):
+ class Settings:
+ FOO = "foo"
+ AUTO_CAST_FOR_DYNACONF = True
+
+ @evaluate_lazy_format
+ def get(self, key, default=None):
+ if key.endswith("_FOR_DYNACONF"):
+ return getattr(self, key)
+ return parse_conf_data("@format {this.FOO}/bar", box_settings=self)
+
+ settings = Settings()
+ assert settings.get("foo") == "foo/bar"
+
+
+def test_lazy_format_on_settings(settings):
+ os.environ["ENV_THING"] = "LazyFormat"
+ settings.set("set_1", "really")
+ settings.set("lazy", "@format {env[ENV_THING]}/{this[set_1]}/{this.SET_2}")
+ settings.set("set_2", "works")
+
+ assert settings.LAZY == settings.get("lazy") == "LazyFormat/really/works"
+
+
+def test_lazy_format_class_jinja():
+ value = Lazy("{{this['FOO']}}/bar", formatter=Formatters.jinja_formatter)
+ settings = {"FOO": "foo"}
+ assert value(settings) == "foo/bar"
+
+
+def test_evaluate_lazy_format_decorator_jinja(settings):
+ class Settings:
+ FOO = "foo"
+
+ AUTO_CAST_FOR_DYNACONF = True
+
+ @evaluate_lazy_format
+ def get(self, key, default=None):
+ if key.endswith("_FOR_DYNACONF"):
+ return getattr(self, key)
+ return parse_conf_data(
+ "@jinja {{this.FOO}}/bar", box_settings=settings
+ )
+
+ settings = Settings()
+ assert settings.get("foo") == "foo/bar"
+
+
+def test_lazy_format_on_settings_jinja(settings):
+ os.environ["ENV_THING"] = "LazyFormat"
+ settings.set("set_1", "really")
+ settings.set(
+ "lazy", "@jinja {{env.ENV_THING}}/{{this['set_1']}}/{{this.SET_2}}"
+ )
+ settings.set("set_2", "works")
+
+ assert settings.LAZY == settings.get("lazy") == "LazyFormat/really/works"
+
+
+def test_lazy_format_is_json_serializable():
+ value = Lazy("{this[FOO]}/bar")
+ assert (
+ json.dumps({"val": value}, cls=DynaconfEncoder)
+ == '{"val": "@format {this[FOO]}/bar"}'
+ )
+
+
+def test_try_to_encode():
+ value = Lazy("{this[FOO]}/bar")
+ assert try_to_encode(value) == "@format {this[FOO]}/bar"
+
+
+def test_del_raises_on_unwrap(settings):
+ value = parse_conf_data("@del ", box_settings=settings)
+ with pytest.raises(ValueError):
+ value.unwrap()
+
+
+def test_extract_json():
+ assert list(extract_json_objects("foo bar")) == []
+ assert list(extract_json_objects('foo bar {"a": 1}')) == [{"a": 1}]
+ assert list(extract_json_objects("foo bar {'a': 2{")) == []
+ assert list(extract_json_objects('{{{"x": {}}}}')) == [{"x": {}}]
+
+
+def test_env_list():
+ class Obj(dict):
+ @property
+ def current_env(self):
+ return "other"
+
+ assert build_env_list(Obj(), env="OTHER") == [
+ "default",
+ "dynaconf",
+ "other",
+ "global",
+ ]
diff --git a/tests/test_validators.py b/tests/test_validators.py
new file mode 100644
index 0000000..07f3ebe
--- /dev/null
+++ b/tests/test_validators.py
@@ -0,0 +1,877 @@
+from __future__ import annotations
+
+import os
+from types import MappingProxyType
+
+import pytest
+
+from dynaconf import Dynaconf
+from dynaconf import LazySettings
+from dynaconf import ValidationError
+from dynaconf import Validator
+from dynaconf.validator import ValidatorList
+
+
+TOML = """
+[default]
+EXAMPLE = true
+MYSQL_HOST = 'localhost'
+DEV_SERVERS = ['127.0.0.1', 'localhost', 'development.com']
+VERSION = 1
+AGE = 15
+NAME = 'BRUNO'
+PORT = 8001
+BASE_IMAGE = 'bla'
+PRESIDENT = 'Lula'
+PROJECT = 'hello_world'
+SALARY = 2000
+WORKS = 'validator'
+ZERO = 0
+FALSE = false
+
+[development]
+MYSQL_HOST = 'development.com'
+VERSION = 1
+AGE = 15
+NAME = 'MIKE'
+IMAGE_1 = 'aaa'
+IMAGE_2 = 'bbb'
+IMAGE_4 = 'a'
+IMAGE_5 = 'b'
+
+[production]
+MYSQL_HOST = 'production.com'
+VERSION = 1
+AGE = 15
+NAME = 'MIKE'
+IMAGE_4 = 'a'
+IMAGE_5 = 'b'
+
+[global]
+MYSQL_PASSWD = "SuperSecret"
+TESTVALUE = "value"
+"""
+
+YAML = """
+server:
+ hostname: "localhost"
+ port: 22
+ users:
+ - "Bruno"
+ - "Lula"
+app:
+ name: "testname"
+ path: "/tmp/app_startup"
+ args:
+ arg1: "a"
+ arg2: "b"
+ arg3: "c"
+
+hasemptyvalues:
+ key1:
+ key2:
+ key3: null
+ key4: "@empty"
+"""
+
+
+@pytest.fixture
+def yaml_validators_good():
+ return [
+ Validator(
+ "server.hostname", "server.port", "server.users", must_exist=True
+ ),
+ Validator(
+ "app.name",
+ "app.path",
+ "app.args.arg1",
+ "app.args.arg2",
+ "app.args.arg3",
+ must_exist=True,
+ ),
+ ]
+
+
+@pytest.fixture
+def yaml_validators_bad():
+ return [
+ Validator("missing.value", must_exist=True),
+ Validator("app.missing", must_exist=True),
+ Validator("app.args.missing", must_exist=True),
+ ]
+
+
+@pytest.fixture
+def yaml_validators_mixed(yaml_validators_good, yaml_validators_bad):
+ mixed = []
+ mixed.extend(yaml_validators_good)
+ mixed.extend(yaml_validators_bad)
+ return mixed
+
+
+def test_validators_on_init(tmpdir):
+ TOML = """
+ [default]
+ hostname = 'devserver.com'
+ username = 'admin'
+ """
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(
+ environments=True,
+ settings_file=str(tmpfile),
+ validators=(
+ Validator("hostname", eq="devserver.com"),
+ Validator("username", ne="admin"),
+ ),
+ )
+
+ with pytest.raises(ValidationError):
+ settings.HOSTNAME
+
+
+def test_validators_register(tmpdir):
+
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="EXAMPLE",
+ SETTINGS_FILE_FOR_DYNACONF=str(tmpfile),
+ silent=True,
+ )
+ settings.validators.register(
+ Validator("VERSION", "AGE", "NAME", must_exist=True),
+ Validator("AGE", lte=30, gte=10),
+ Validator("PROJECT", eq="hello_world"),
+ Validator("PRESIDENT", env="DEVELOPMENT", ne="Trump"),
+ Validator("SALARY", lt=1000000, gt=1000),
+ Validator("DEV_SERVERS", must_exist=True, is_type_of=(list, tuple)),
+ Validator("MYSQL_HOST", env="DEVELOPMENT", is_in=settings.DEV_SERVERS),
+ Validator(
+ "MYSQL_HOST", env="PRODUCTION", is_not_in=settings.DEV_SERVERS
+ ),
+ Validator("NAME", condition=lambda x: x in ("BRUNO", "MIKE")),
+ Validator(
+ "IMAGE_1",
+ "IMAGE_2",
+ env="development",
+ must_exist=True,
+ when=Validator(
+ "BASE_IMAGE", must_exist=True, env=settings.ENV_FOR_DYNACONF
+ ),
+ ),
+ Validator(
+ "IMAGE_4",
+ "IMAGE_5",
+ env=("development", "production"),
+ must_exist=True,
+ when=Validator(
+ "BASE_IMAGE", must_exist=False, env=settings.ENV_FOR_DYNACONF
+ ),
+ ),
+ Validator(
+ "PORT",
+ must_exist=True,
+ ne=8000,
+ when=Validator("MYSQL_HOST", eq="localhost"),
+ ),
+ #
+ Validator("ZERO", is_type_of=int, eq=0),
+ Validator("FALSE", is_type_of=bool, eq=False),
+ Validator("NAME", len_min=3, len_max=125),
+ Validator("DEV_SERVERS", cont="localhost"),
+ Validator("PORT", condition=lambda value: len(str(value)) == 4),
+ Validator("PROJECT", len_ne=0),
+ )
+
+ assert settings.validators.validate() is None
+
+ settings.validators.register(
+ Validator("TESTVALUEZZ", env="development"),
+ Validator("TESTVALUE", eq="hello_world"),
+ )
+
+ with pytest.raises(ValidationError):
+ settings.validators.validate()
+
+ with pytest.raises(TypeError):
+ Validator("A", condition=1)
+
+ with pytest.raises(TypeError):
+ Validator("A", when=1)
+
+
+def test_dotted_validators(settings):
+
+ settings.set(
+ "PARAMS",
+ {"PASSWORD": "secret", "SSL": {"CONTEXT": "SECURE", "ENABLED": True}},
+ )
+
+ settings.validators.register(
+ Validator("PARAMS", must_exist=True, is_type_of=dict),
+ Validator("PARAMS.PASSWORD", must_exist=True, is_type_of=str),
+ Validator("PARAMS.SSL", must_exist=True, is_type_of=dict),
+ Validator("PARAMS.SSL.ENABLED", must_exist=True, is_type_of=bool),
+ Validator("PARAMS.NONEXISTENT", must_exist=False, is_type_of=str),
+ )
+
+ assert settings.validators.validate() is None
+
+
+@pytest.mark.parametrize(
+ "validator_instance",
+ [
+ Validator("TESTVALUE", eq="hello_world"),
+ Validator("PROJECT", condition=lambda x: False, env="development"),
+ Validator("TESTVALUEZZ", must_exist=True),
+ Validator("TESTVALUEZZ", "PROJECT", must_exist=False),
+ ],
+)
+def test_validation_error(validator_instance, tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="EXAMPLE",
+ SETTINGS_FILE_FOR_DYNACONF=str(tmpfile),
+ silent=True,
+ )
+ settings.validators.register(validator_instance)
+ with pytest.raises(ValidationError):
+ settings.validators.validate()
+
+
+def test_no_reload_on_single_env(tmpdir, mocker):
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ same_env_validator = Validator(
+ "VERSION", is_type_of=int, env="development"
+ )
+ other_env_validator = Validator("NAME", must_exist=True, env="production")
+
+ settings = LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="DEVELOPMENt",
+ SETTINGS_FILE_FOR_DYNACONF=str(tmpfile),
+ )
+ using_env = mocker.patch.object(settings, "from_env")
+
+ settings.validators.register(same_env_validator)
+ settings.validators.validate()
+ using_env.assert_not_called()
+
+ settings.validators.register(other_env_validator)
+ settings.validators.validate()
+ using_env.assert_any_call("production")
+ assert using_env.call_count == 1
+
+
+@pytest.mark.parametrize(
+ "this_validator,another_validator",
+ [
+ (
+ Validator("VERSION", "AGE", "NAME", must_exist=True),
+ Validator("VERSION", "AGE", "NAME", must_exist=True),
+ ),
+ (
+ Validator("VERSION") | Validator("AGE"),
+ Validator("VERSION") | Validator("AGE"),
+ ),
+ (
+ Validator("VERSION") & Validator("AGE"),
+ Validator("VERSION") & Validator("AGE"),
+ ),
+ ],
+)
+def test_equality(this_validator, another_validator):
+ assert this_validator == another_validator
+ assert this_validator is not another_validator
+
+
+@pytest.mark.parametrize(
+ "this_validator,another_validator",
+ [
+ (
+ Validator(
+ "IMAGE_1", when=Validator("BASE_IMAGE", must_exist=True)
+ ),
+ Validator(
+ "IMAGE_1", when=Validator("MYSQL_HOST", must_exist=True)
+ ),
+ ),
+ (Validator("VERSION"), Validator("VERSION") & Validator("AGE")),
+ (Validator("VERSION"), Validator("VERSION") | Validator("AGE")),
+ (
+ Validator("VERSION") | Validator("AGE"),
+ Validator("VERSION") & Validator("AGE"),
+ ),
+ (
+ Validator("VERSION") | Validator("AGE"),
+ Validator("NAME") | Validator("BASE_IMAGE"),
+ ),
+ ],
+)
+def test_inequality(this_validator, another_validator):
+ assert this_validator != another_validator
+
+
+def test_ignoring_duplicate_validators(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="EXAMPLE",
+ SETTINGS_FILE_FOR_DYNACONF=str(tmpfile),
+ silent=True,
+ )
+
+ validator1 = Validator("VERSION", "AGE", "NAME", must_exist=True)
+ settings.validators.register(
+ validator1, Validator("VERSION", "AGE", "NAME", must_exist=True)
+ )
+
+ assert len(settings.validators) == 1
+
+ settings.validators.register(validator1)
+
+ assert len(settings.validators) == 1
+
+
+def test_validator_equality_by_identity():
+ validator1 = Validator("FOO", must_exist=True)
+ validator2 = validator1
+ assert validator1 == validator2
+
+
+def test_validator_custom_message(tmpdir):
+ """Assert custom message is being processed by validator."""
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(
+ environments=True, SETTINGS_FILE_FOR_DYNACONF=str(tmpfile), silent=True
+ )
+
+ custom_msg = "You cannot set {name} to {value} in env {env}"
+ settings.validators.register(
+ Validator("MYSQL_HOST", eq="development.com", env="DEVELOPMENT"),
+ Validator("MYSQL_HOST", ne="development.com", env="PRODUCTION"),
+ Validator("VERSION", ne=1, messages={"operations": custom_msg}),
+ )
+
+ with pytest.raises(ValidationError) as error:
+ settings.validators.validate()
+
+ assert custom_msg.format(
+ name="VERSION", value="1", env="DEVELOPMENT"
+ ) in str(error)
+
+
+def test_validate_all(tmpdir):
+ """Assert custom message is being processed by validator."""
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(
+ environments=True, SETTINGS_FILE_FOR_DYNACONF=str(tmpfile), silent=True
+ )
+
+ custom_msg = "You cannot set {name} to {value} in env {env}"
+ settings.validators.register(
+ Validator("MYSQL_HOST", eq="development.com", env="DEVELOPMENT"),
+ Validator("MYSQL_HOST", ne="development.com", env="PRODUCTION"),
+ Validator("VERSION", ne=1, messages={"operations": custom_msg}),
+ Validator("BLABLABLA", must_exist=True),
+ )
+
+ with pytest.raises(ValidationError) as error:
+ settings.validators.validate_all()
+
+ assert (
+ custom_msg.format(name="VERSION", value="1", env="DEVELOPMENT")
+ in error.value.message
+ )
+ assert "BLABLABLA" in error.value.message
+
+ assert error.type == ValidationError
+ assert len(error.value.details) == 2
+
+
+def test_validator_subclass_messages(tmpdir):
+ """Assert message can be customized via class attributes"""
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+
+ settings = LazySettings(
+ environments=True, SETTINGS_FILE_FOR_DYNACONF=str(tmpfile), silent=True
+ )
+
+ class MyValidator(Validator):
+ default_messages = MappingProxyType(
+ {
+ "must_exist_true": "{name} should exist in {env}",
+ "must_exist_false": "{name} CANNOT BE THERE IN {env}",
+ "condition": (
+ "{name} BROKE THE {function}({value}) IN env {env}"
+ ),
+ "operations": (
+ "{name} SHOULD BE {operation} {op_value} "
+ "BUT YOU HAVE {value} IN ENV {env}, PAY ATTENTION!"
+ ),
+ }
+ )
+
+ with pytest.raises(ValidationError) as error_custom_message:
+ custom_msg = "You cannot set {name} to {value} in env {env}"
+ MyValidator(
+ "VERSION", ne=1, messages={"operations": custom_msg}
+ ).validate(settings)
+
+ assert custom_msg.format(
+ name="VERSION", value="1", env="DEVELOPMENT"
+ ) in str(error_custom_message)
+
+ with pytest.raises(ValidationError) as error_operations:
+ MyValidator("VERSION", ne=1).validate(settings)
+
+ assert (
+ "VERSION SHOULD BE ne 1 "
+ "BUT YOU HAVE 1 IN ENV DEVELOPMENT, "
+ "PAY ATTENTION!"
+ ) in str(error_operations)
+
+ with pytest.raises(ValidationError) as error_conditions:
+ MyValidator("VERSION", condition=lambda value: False).validate(
+ settings
+ )
+
+ assert ("VERSION BROKE THE <lambda>(1) IN env DEVELOPMENT") in str(
+ error_conditions
+ )
+
+ with pytest.raises(ValidationError) as error_must_exist_false:
+ MyValidator("VERSION", must_exist=False).validate(settings)
+
+ assert ("VERSION CANNOT BE THERE IN DEVELOPMENT") in str(
+ error_must_exist_false
+ )
+
+ with pytest.raises(ValidationError) as error_must_exist_true:
+ MyValidator("BLARGVARGST_DONT_EXIST", must_exist=True).validate(
+ settings
+ )
+
+ assert ("BLARGVARGST_DONT_EXIST should exist in DEVELOPMENT") in str(
+ error_must_exist_true
+ )
+
+
+def test_positive_combined_validators(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+ settings = LazySettings(
+ environments=True, SETTINGS_FILE_FOR_DYNACONF=str(tmpfile), silent=True
+ )
+ settings.validators.register(
+ Validator("VERSION", ne=1) | Validator("VERSION", ne=2),
+ Validator("VERSION", ne=4) & Validator("VERSION", ne=2),
+ )
+ settings.validators.validate()
+
+
+def test_negative_combined_or_validators(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+ settings = LazySettings(
+ environments=True, SETTINGS_FILE_FOR_DYNACONF=str(tmpfile), silent=True
+ )
+ settings.validators.register(
+ Validator("VERSION", ne=1) | Validator("VERSION", ne=1),
+ )
+ with pytest.raises(ValidationError):
+ settings.validators.validate()
+
+
+def test_negative_combined_and_validators(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+ settings = LazySettings(
+ environments=True, SETTINGS_FILE_FOR_DYNACONF=str(tmpfile), silent=True
+ )
+ settings.validators.register(
+ Validator("VERSION", ne=1) & Validator("VERSION", ne=1),
+ )
+ with pytest.raises(ValidationError):
+ settings.validators.validate()
+
+
+def test_envless_and_combined_validators(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ TOML = """
+ value = true
+ version = 1
+ name = 'Bruno'
+ """
+ tmpfile.write(TOML)
+ settings = LazySettings(
+ SETTINGS_FILE_FOR_DYNACONF=str(tmpfile), silent=True
+ )
+ settings.validators.register(
+ Validator("VERSION", ne=1) & Validator("value", ne=True),
+ )
+ with pytest.raises(ValidationError):
+ settings.validators.validate()
+
+
+def test_cast_on_validate_transforms_value(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ TOML = """
+ name = 'Bruno'
+ colors = ['red', 'green', 'blue']
+ """
+ tmpfile.write(TOML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ silent=True,
+ lowercase_read=True,
+ validators=[
+ # Order matters here
+ Validator("name", len_eq=5),
+ Validator("name", len_min=1),
+ Validator("name", len_max=5),
+ # This will cast the str to list
+ Validator("name", cast=list),
+ Validator("colors", len_eq=3),
+ Validator("colors", len_eq=3),
+ # this will cast the list to str
+ Validator("colors", len_eq=24, cast=str),
+ ],
+ )
+ assert settings.name == list("Bruno")
+ assert settings.colors == str(["red", "green", "blue"])
+
+
+def test_validator_can_provide_default(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ TOML = """
+ name = 'Bruno'
+ colors = ['red', 'green', 'blue']
+ """
+ tmpfile.write(TOML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ validators=[
+ Validator("name", required=True),
+ Validator("FOO", default="BAR"),
+ Validator("COMPUTED", default=lambda st, va: "I am computed"),
+ ],
+ )
+ assert settings.name == "Bruno"
+ assert settings.colors == ["red", "green", "blue"]
+
+ assert settings.FOO == "BAR"
+ assert settings.COMPUTED == "I am computed"
+
+
+def test_validator_init_exclude(tmpdir, yaml_validators_mixed):
+ tmpfile = tmpdir.join("settings.yaml")
+ tmpfile.write(YAML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ validators=yaml_validators_mixed,
+ validate_exclude=["missing", "app.missing", "app.args.missing"],
+ )
+ assert settings.server.hostname == "localhost"
+
+
+def test_validator_init_only(tmpdir, yaml_validators_mixed):
+ tmpfile = tmpdir.join("settings.yaml")
+ tmpfile.write(YAML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ validators=yaml_validators_mixed,
+ validate_only=["server"],
+ )
+ assert settings.server.hostname == "localhost"
+
+
+def test_validator_init_mixed(tmpdir, yaml_validators_mixed):
+ tmpfile = tmpdir.join("settings.yaml")
+ tmpfile.write(YAML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ validators=yaml_validators_mixed,
+ validate_only=["server", "app"],
+ validate_exclude=["app.missing", "app.args.missing"],
+ )
+ assert settings.server.hostname == "localhost"
+
+
+def test_validator_only_post_register(
+ tmpdir, yaml_validators_good, yaml_validators_bad
+):
+ tmpfile = tmpdir.join("settings.yaml")
+ tmpfile.write(YAML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ validators=yaml_validators_good,
+ validate_only=["server"],
+ )
+ assert settings.server.hostname == "localhost"
+ settings.validators.register(*yaml_validators_bad)
+ # call validation only on the server section
+ settings.validators.validate(only=["server"])
+
+
+def test_validator_exclude_post_register(
+ tmpdir, yaml_validators_good, yaml_validators_bad
+):
+ tmpfile = tmpdir.join("settings.yaml")
+ tmpfile.write(YAML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ validators=yaml_validators_good,
+ validate_only=["server", "app.path"],
+ )
+ assert settings.server.hostname == "localhost"
+ settings.validators.register(*yaml_validators_bad)
+ # call validation only on the server section
+ settings.validators.validate(
+ exclude=["missing", "app.missing", "app.args.missing"]
+ )
+ settings.app.path = "/tmp/app_startup"
+
+
+def test_validator_only_current_env_valid(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ environments=True,
+ ENV_FOR_DYNACONF="DEVELOPMENT",
+ )
+ settings.validators.register(
+ Validator("IMAGE_1", env="production", must_exist=True)
+ )
+ settings.validators.validate(only_current_env=True)
+
+
+def test_raises_only_current_env_invalid(tmpdir):
+ tmpfile = tmpdir.join("settings.toml")
+ tmpfile.write(TOML)
+ settings = LazySettings(
+ settings_file=str(tmpfile),
+ environments=True,
+ ENV_FOR_DYNACONF="PRODUCTION",
+ )
+ settings.validators.register(
+ Validator("IMAGE_1", env="production", must_exist=True)
+ )
+
+ with pytest.raises(ValidationError):
+ settings.validators.validate(only_current_env=True)
+
+
+def test_raises_on_invalid_selective_args(tmpdir, yaml_validators_good):
+ settings = LazySettings(validators=yaml_validators_good, validate_only=int)
+ with pytest.raises(ValueError):
+ settings.validator_instance.validate()
+
+ settings = LazySettings(
+ validators=yaml_validators_good, validate_exclude=int
+ )
+ with pytest.raises(ValueError):
+ settings.validator_instance.validate()
+
+
+def test_validator_descriptions(tmpdir):
+ validators = ValidatorList(
+ LazySettings(),
+ validators=[
+ Validator("foo", description="foo"),
+ Validator("bar", description="bar"),
+ Validator("baz", "zaz", description="baz zaz"),
+ Validator("foo", description="another message"),
+ Validator("a", description="a") & Validator("b"),
+ ],
+ )
+
+ assert validators.descriptions() == {
+ "bar": ["bar"],
+ "baz": ["baz zaz"],
+ "zaz": ["baz zaz"],
+ "foo": ["foo", "another message"],
+ "a": ["a"],
+ "b": ["a"],
+ }
+
+
+def test_validator_descriptions_flat(tmpdir):
+ validators = ValidatorList(
+ LazySettings(),
+ validators=[
+ Validator("foo", description="foo"),
+ Validator("bar", description="bar"),
+ Validator("baz", "zaz", description="baz zaz"),
+ Validator("foo", description="another message"),
+ Validator("a", description="a") & Validator("b"),
+ ],
+ )
+
+ assert validators.descriptions(flat=True) == {
+ "bar": "bar",
+ "baz": "baz zaz",
+ "zaz": "baz zaz",
+ "foo": "foo",
+ "a": "a",
+ "b": "a",
+ }
+
+
+def test_toml_should_not_change_validator_type_with_is_type_set():
+ settings = Dynaconf(
+ validators=[Validator("TEST", is_type_of=str, default="+172800")]
+ )
+
+ assert settings.test == "+172800"
+
+
+def test_toml_should_not_change_validator_type_with_is_type_not_set_int():
+ settings = Dynaconf(
+ validators=[Validator("TEST", default="+172800")]
+ # The ways to force a string is
+ # passing is_type_of=str
+ # or default="@str +172800" or default="'+172800'"
+ )
+
+ assert settings.test == +172800
+
+
+def test_toml_should_not_change_validator_type_using_at_sign():
+ settings = Dynaconf(
+ validators=[Validator("TEST", is_type_of=str, default="@str +172800")]
+ )
+
+ assert settings.test == "+172800"
+
+
+def test_default_eq_env_lvl_1():
+ """Tests when the env value equals the default value."""
+ VAR_NAME = "test"
+ ENV = "DYNATESTRUN_TEST"
+ settings = Dynaconf(
+ environments=False,
+ envvar_prefix="DYNATESTRUN",
+ validators=[
+ Validator(
+ VAR_NAME,
+ default=True,
+ is_type_of=bool,
+ ),
+ ],
+ )
+ os.environ[ENV] = "true"
+ assert settings.test is True
+ del os.environ[ENV]
+
+
+def test_default_lvl_1():
+ """Tests if the default works properly without any nested level.
+
+ Uses different values for the default and the environment variable.
+ """
+ VAR_NAME = "test"
+ ENV = "DYNATESTRUN_TEST"
+ settings = Dynaconf(
+ environments=False,
+ envvar_prefix="DYNATESTRUN",
+ validators=[
+ Validator(
+ VAR_NAME,
+ default=True,
+ is_type_of=bool,
+ ),
+ ],
+ )
+ os.environ[ENV] = "false"
+ assert settings.test is False
+ del os.environ[ENV]
+
+
+def test_default_lvl_2():
+ """Tests if the default works properly with one nested level.
+
+ Uses different values for the default and the environment variable.
+ """
+ VAR_NAME = "nested.test"
+ ENV = "DYNATESTRUN_NESTED__TEST"
+ settings = Dynaconf(
+ environments=False,
+ envvar_prefix="DYNATESTRUN",
+ validators=[
+ Validator(
+ VAR_NAME,
+ default=True,
+ is_type_of=bool,
+ ),
+ ],
+ )
+ os.environ[ENV] = "false"
+ assert settings.nested.test is False
+ del os.environ[ENV]
+
+
+def test_use_default_value_when_yaml_is_empty_and_explicitly_marked(tmpdir):
+ tmpfile = tmpdir.join("settings.yaml")
+ tmpfile.write(YAML)
+ settings = Dynaconf(
+ settings_file=str(tmpfile),
+ validators=[
+ # Explicitly say thar default must be applied to None
+ Validator(
+ "hasemptyvalues.key1",
+ default="value1",
+ apply_default_on_none=True,
+ ),
+ # The following 2 defaults must be ignored
+ Validator("hasemptyvalues.key2", default="value2"),
+ Validator("hasemptyvalues.key3", default="value3"),
+ # This one must be set because on YAML key is set to `@empty`
+ Validator("hasemptyvalues.key4", default="value4"),
+ ],
+ )
+ assert settings.hasemptyvalues.key1 == "value1"
+ assert settings.hasemptyvalues.key2 is None
+ assert settings.hasemptyvalues.key3 is None
+ assert settings.hasemptyvalues.key4 == "value4"
+
+
+def test_ensure_cast_happens_after_must_exist(tmpdir):
+ """#823"""
+ from pathlib import Path
+
+ settings = Dynaconf(
+ validators=[Validator("java_bin", must_exist=True, cast=Path)]
+ )
+ # must raise ValidationError instead of Path error
+ with pytest.raises(ValidationError):
+ settings.validators.validate()
+
+
+def test_ensure_cast_works_for_non_default_values(tmpdir):
+ """#834"""
+
+ settings = Dynaconf(validators=[Validator("offset", default=1, cast=int)])
+
+ settings.offset = "24"
+
+ settings.validators.validate()
+
+ assert isinstance(settings.offset, int), type(settings.offset)
diff --git a/tests/test_validators_conditions.py b/tests/test_validators_conditions.py
new file mode 100644
index 0000000..ff48b8d
--- /dev/null
+++ b/tests/test_validators_conditions.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+import pytest
+
+from dynaconf import validator_conditions
+
+
+positive_conditions = [
+ ("eq", 1, 1),
+ ("ne", 1, 2),
+ ("gt", 4, 3),
+ ("lt", 3, 4),
+ ("gte", 5, 5),
+ ("lte", 5, 5),
+ ("identity", None, None),
+ ("is_type_of", 42, int),
+ ("is_in", 42, [42, 34]),
+ ("is_not_in", 42, [55, 34]),
+ ("cont", "This word(s) contains in text", "in"),
+ ("len_eq", "Length Equal", 12),
+ ("len_eq", [1, 2, 3], 3),
+ ("len_ne", "Length Not equal", 0),
+ ("len_ne", [], 1),
+ ("len_min", "Minimum length", 3),
+ ("len_min", [1, 2, 3, 4, 5], 3),
+ ("len_max", "Maximum length", 15),
+ ("len_max", [1, 2, 3, 4, 5], 5),
+ ("startswith", "codeshow", "code"),
+ ("endswith", "codeshow", "show"),
+]
+
+
+@pytest.mark.parametrize("data", positive_conditions)
+def test_conditions(data):
+ assert getattr(validator_conditions, data[0])(data[1], data[2])
+
+
+negative_conditions = [
+ ("eq", 1, 2),
+ ("ne", 1, 1),
+ ("gt", 4, 4),
+ ("lt", 3, 3),
+ ("gte", 5, 6),
+ ("lte", 5, 4),
+ ("identity", None, 1),
+ ("is_type_of", 42, str),
+ ("is_in", 42, [55, 34]),
+ ("is_not_in", 42, [42, 34]),
+ ("cont", "This word(s) contains in text", "out"),
+ ("len_eq", "Length Equal", 0),
+ ("len_eq", [1, 2, 3], 4),
+ ("len_ne", "Length Not equal", 16),
+ ("len_ne", [], 0),
+ ("len_min", "Minimum length", 15),
+ ("len_min", [1, 2, 3, 4, 5], 6),
+ ("len_max", "Maximum length", 3),
+ ("len_max", [1, 2, 3, 4, 5], 4),
+ ("startswith", "codeshow", "show"),
+ ("endswith", "codeshow", "code"),
+]
+
+
+@pytest.mark.parametrize("data", negative_conditions)
+def test_negative_conditions(data):
+ assert not getattr(validator_conditions, data[0])(data[1], data[2])
diff --git a/tests/test_vault.py b/tests/test_vault.py
new file mode 100644
index 0000000..ad9698c
--- /dev/null
+++ b/tests/test_vault.py
@@ -0,0 +1,109 @@
+from __future__ import annotations
+
+import os
+from time import sleep
+
+import pytest
+
+from dynaconf import LazySettings
+from dynaconf.loaders.vault_loader import list_envs
+from dynaconf.loaders.vault_loader import load
+from dynaconf.loaders.vault_loader import write
+
+
+def custom_checker(ip_address, port):
+ # This function should be check if the redis server is online and ready
+ # write(settings, {"SECRET": "redis_works"})
+ # return load(settings, key="SECRET")
+ return True
+
+
+@pytest.fixture(scope="module")
+def docker_vault(docker_services):
+ docker_services.start("vault")
+ public_port = docker_services.wait_for_service(
+ "vault", 8200, check_server=custom_checker
+ )
+ url = f"http://{docker_services.docker_ip}:{public_port}"
+
+ sleep(3)
+ return url
+
+
+@pytest.mark.integration
+def test_load_vault_not_configured():
+ with pytest.raises(AssertionError) as excinfo:
+ settings = LazySettings(environments=True)
+ load(settings, {"OTHER_SECRET": "vault_works"})
+ assert "Vault authentication error" in str(excinfo.value)
+
+
+@pytest.mark.integration
+def test_write_vault_not_configured():
+ with pytest.raises(RuntimeError) as excinfo:
+ settings = LazySettings(environments=True)
+ write(settings, {"OTHER_SECRET": "vault_works"})
+ assert "export VAULT_ENABLED_FOR_DYNACONF" in str(excinfo.value)
+
+
+@pytest.mark.integration
+def test_write_vault_without_data(docker_vault):
+ os.environ["VAULT_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["VAULT_TOKEN_FOR_DYNACONF"] = "myroot"
+ settings = LazySettings(environments=True)
+ with pytest.raises(AttributeError) as excinfo:
+ write(settings)
+ assert "Data must be provided" in str(excinfo.value)
+
+
+@pytest.mark.integration
+def test_list_envs_in_vault(docker_vault):
+ os.environ["VAULT_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["VAULT_TOKEN_FOR_DYNACONF"] = "myroot"
+ settings = LazySettings(environments=True)
+ envs = list_envs(settings, "test_list_envs_in_vault")
+ assert envs == []
+
+
+@pytest.mark.integration
+def test_write_to_vault(docker_vault):
+ os.environ["VAULT_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["VAULT_TOKEN_FOR_DYNACONF"] = "myroot"
+ settings = LazySettings(environments=True)
+ write(settings, {"SECRET": "vault_works_with_docker"})
+ load(settings, key="SECRET")
+ assert settings.get("SECRET") == "vault_works_with_docker"
+
+
+@pytest.mark.integration
+def test_load_from_vault_with_key(docker_vault):
+ os.environ["VAULT_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["VAULT_TOKEN_FOR_DYNACONF"] = "myroot"
+ settings = LazySettings(environments=True)
+ load(settings, key="SECRET")
+ assert settings.get("SECRET") == "vault_works_with_docker"
+
+
+@pytest.mark.integration
+def test_write_and_load_from_vault_without_key(docker_vault):
+ os.environ["VAULT_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["VAULT_TOKEN_FOR_DYNACONF"] = "myroot"
+ settings = LazySettings(environments=True)
+ write(settings, {"SECRET": "vault_works_perfectly"})
+ load(settings)
+ assert settings.get("SECRET") == "vault_works_perfectly"
+
+
+@pytest.mark.integration
+def test_read_from_vault_kv2_with_different_environments(docker_vault):
+ os.environ["VAULT_ENABLED_FOR_DYNACONF"] = "1"
+ os.environ["VAULT_KV_VERSION_FOR_DYNACONF"] = "2"
+ os.environ["VAULT_TOKEN_FOR_DYNACONF"] = "myroot"
+ settings = LazySettings(environments=["dev", "prod"])
+ for env in ["default", "dev", "prod"]:
+ with settings.using_env(env):
+ write(settings, {"SECRET": f"vault_works_in_{env}"})
+ load(settings)
+ assert settings.secret == "vault_works_in_default"
+ assert settings.from_env("dev").secret == "vault_works_in_dev"
+ assert settings.from_env("prod").secret == "vault_works_in_prod"
diff --git a/tests/test_yaml_loader.py b/tests/test_yaml_loader.py
new file mode 100644
index 0000000..1582bed
--- /dev/null
+++ b/tests/test_yaml_loader.py
@@ -0,0 +1,544 @@
+from __future__ import annotations
+
+import os
+
+import pytest
+
+from dynaconf import LazySettings
+from dynaconf.loaders.yaml_loader import load
+from dynaconf.strategies.filtering import PrefixFilter
+
+
+@pytest.fixture(scope="module")
+def settings():
+ return LazySettings(
+ environments=True,
+ ENV_FOR_DYNACONF="PRODUCTION",
+ # ROOT_PATH_FOR_DYNACONF=os.path.dirname(os.path.abspath(__file__)),
+ )
+
+
+YAML = """
+# the below is just to ensure `,` will not break string YAML
+a: 'a,b'
+default:
+ password: 99999
+ host: server.com
+ port: 8080
+ alist:
+ - item1
+ - item2
+ - 23
+ service:
+ url: service.com
+ port: 80
+ auth:
+ password: qwerty
+ test: 1234
+ spaced key: 1
+ spaced nested:
+ key: 1
+development:
+ password: 88888
+ host: devserver.com
+production:
+ password: 11111
+ host: prodserver.com
+global:
+ global_value: global
+
+"""
+
+YAML2 = """
+global:
+ # @float casting not needed, used only for testing
+ secret: '@float 42'
+ password: 123456
+ host: otheryaml.com
+"""
+
+YAMLS = [YAML, YAML2]
+
+
+def test_load_from_yaml(settings):
+ """Assert loads from YAML string"""
+ load(settings, filename=YAML)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+ assert settings.ALIST == ["item1", "item2", 23]
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ assert settings.spaced_key == 1
+ assert settings.spaced_nested.key == 1
+ assert settings.spaced_nested["key"] == 1
+ assert settings["spaced key"] == 1
+ assert settings["SPACED KEY"] == 1
+ load(settings, filename=YAML, env="DEVELOPMENT")
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=YAML)
+ assert settings.HOST == "prodserver.com"
+
+
+def test_load_from_multiple_yaml(settings):
+ """Assert loads from YAML string"""
+ load(settings, filename=YAMLS)
+ assert settings.HOST == "otheryaml.com"
+ assert settings.PASSWORD == 123456
+ assert settings.SECRET == 42.0
+ assert settings.PORT == 8080
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=YAMLS, env="DEVELOPMENT")
+ assert settings.PORT == 8080
+ assert settings.HOST == "otheryaml.com"
+ load(settings, filename=YAMLS)
+ assert settings.HOST == "otheryaml.com"
+ assert settings.PASSWORD == 123456
+ load(settings, filename=YAML, env="DEVELOPMENT")
+ assert settings.PORT == 8080
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=YAML)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PASSWORD == 11111
+
+
+def test_no_filename_is_none(settings):
+ """Assert if passed no filename return is None"""
+ assert load(settings) is None
+
+
+def test_key_error_on_invalid_env(settings):
+ """Assert error raised if env is not found in YAML"""
+ with pytest.raises(KeyError):
+ load(settings, filename=YAML, env="FOOBAR", silent=False)
+
+
+def test_no_key_error_on_invalid_env(settings):
+ """Assert error raised if env is not found in YAML"""
+ load(settings, filename=YAML, env="FOOBAR", silent=True)
+
+
+def test_load_single_key(settings):
+ """Test loading a single key"""
+ yaml = """
+ foo:
+ bar: blaz
+ zaz: naz
+ """
+ load(settings, filename=yaml, env="FOO", key="bar")
+ assert settings.BAR == "blaz"
+ assert settings.exists("BAR") is True
+ assert settings.exists("ZAZ") is False
+
+
+def test_extra_yaml(settings):
+ """Test loading extra yaml file"""
+ load(settings, filename=YAML)
+ yaml = """
+ example:
+ helloexample: world
+ """
+ settings.set("YAML", yaml)
+ settings.execute_loaders(env="EXAMPLE")
+ assert settings.HELLOEXAMPLE == "world"
+
+
+def test_empty_value(settings):
+ load(settings, filename="")
+
+
+def test_multiple_filenames(settings):
+ load(settings, filename="a.yaml,b.yml,c.yaml,d.yml")
+
+
+def test_cleaner(settings):
+ load(settings, filename=YAML)
+ assert settings.HOST == "prodserver.com"
+ assert settings.PORT == 8080
+ assert settings.ALIST == ["item1", "item2", 23]
+ assert settings.SERVICE["url"] == "service.com"
+ assert settings.SERVICE.url == "service.com"
+ assert settings.SERVICE.port == 80
+ assert settings.SERVICE.auth.password == "qwerty"
+ assert settings.SERVICE.auth.test == 1234
+ load(settings, filename=YAML, env="DEVELOPMENT")
+ assert settings.HOST == "devserver.com"
+ load(settings, filename=YAML)
+ assert settings.HOST == "prodserver.com"
+
+ settings.clean()
+ with pytest.raises(AttributeError):
+ assert settings.HOST == "prodserver.com"
+
+
+def test_using_env(tmpdir, settings):
+ load(settings, filename=YAML)
+ assert settings.HOST == "prodserver.com"
+
+ tmpfile = tmpdir.mkdir("sub").join("test_using_env.yaml")
+ tmpfile.write(YAML)
+ with settings.using_env("DEVELOPMENT", filename=str(tmpfile)):
+ assert settings.HOST == "devserver.com"
+ assert settings.HOST == "prodserver.com"
+
+
+def test_load_dunder(settings):
+ """Test load with dunder settings"""
+ yaml = """
+ foo:
+ bar: blaz
+ zaz: naz
+ colors__black__code: '#000000'
+ COLORS__black__name: Black
+ """
+ load(settings, filename=yaml, env="FOO")
+ assert settings.COLORS.black.code == "#000000"
+ assert settings.COLORS.black.name == "Black"
+
+
+def test_local_files(tmpdir):
+
+ settings_file_yaml = """
+ default:
+ name: Bruno
+ colors:
+ - green
+ - blue
+ data:
+ link: brunorocha.org
+ other:
+ foo: bar
+ music:
+ current:
+ volume: 10
+ title: The Beatles - Strawberry Fields
+ """
+ tmpdir.join("settings.yaml").write(settings_file_yaml)
+
+ local_file_yaml = """
+ default:
+ name: Bruno Rocha
+ colors:
+ - red
+ - dynaconf_merge
+ data:
+ city: Guarulhos
+ dynaconf_merge: true
+ other:
+ baz: zaz
+ music__current__volume: 100
+ music__current__title: Led Zeppelin - Immigrant Song
+ """
+ tmpdir.join("settings.local.yaml").write(local_file_yaml)
+
+ conf = LazySettings(environments=True, settings_file="settings.yaml")
+ assert conf.NAME == "Bruno Rocha"
+ assert set(conf.COLORS) == {"red", "green", "blue"}
+ assert conf.DATA.link == "brunorocha.org"
+ assert conf.DATA.city == "Guarulhos"
+ assert conf.OTHER == {"baz": "zaz"}
+ assert conf.MUSIC.current.volume == 100
+ assert conf.MUSIC.current.title == "Led Zeppelin - Immigrant Song"
+
+
+def test_explicit_local_files(tmpdir):
+
+ settings_file_yaml = """
+ default:
+ name: Bruno
+ colors:
+ - green
+ - blue
+ data:
+ link: brunorocha.org
+ other:
+ foo: bar
+ music:
+ current:
+ volume: 10
+ title: The Beatles - Strawberry Fields
+ """
+ tmpdir.join("foo.yaml").write(settings_file_yaml)
+
+ local_file_yaml = """
+ default:
+ name: Bruno Rocha
+ colors:
+ - red
+ - dynaconf_merge
+ data:
+ city: Guarulhos
+ dynaconf_merge: true
+ other:
+ baz: zaz
+ music__current__volume: 100
+ music__current__title: Led Zeppelin - Immigrant Song
+ music__current__even__inner__element: true
+ """
+ tmpdir.join("foo.local.yaml").write(local_file_yaml)
+
+ conf = LazySettings(
+ environments=True,
+ SETTINGS_FILE_FOR_DYNACONF=["foo.yaml", "foo.local.yaml"],
+ )
+
+ assert conf.NAME == "Bruno Rocha"
+ assert set(conf.COLORS) == {"red", "green", "blue"}
+ assert conf.DATA.link == "brunorocha.org"
+ assert conf.DATA.city == "Guarulhos"
+ assert conf.OTHER == {"baz": "zaz"}
+ assert conf.MUSIC.current.volume == 100
+ assert conf.MUSIC.current.title == "Led Zeppelin - Immigrant Song"
+ assert conf.get("music.current.even.inner.element") is True
+
+
+def test_envless():
+ settings = LazySettings()
+ _yaml = """
+ a: a,b
+ colors__white__code: "#FFFFFF"
+ COLORS__white__name: "white"
+ """
+ load(settings, filename=_yaml)
+ assert settings.a == "a,b"
+ assert settings.COLORS.white.code == "#FFFFFF"
+ assert settings.COLORS.white.name == "white"
+
+
+def test_prefix():
+ settings = LazySettings(filter_strategy=PrefixFilter("prefix"))
+ _yaml = """
+ prefix_a: a,b
+ prefix_colors__white__code: "#FFFFFF"
+ COLORS__white__name: "white"
+ """
+ load(settings, filename=_yaml)
+ assert settings.a == "a,b"
+ assert settings.COLORS.white.code == "#FFFFFF"
+ with pytest.raises(AttributeError):
+ settings.COLORS.white.name
+
+
+def test_empty_env():
+ settings = LazySettings(environments=True)
+ _yaml = """
+ default:
+ foo: bar
+ development:
+ """
+ load(settings, filename=_yaml)
+ assert settings.FOO == "bar"
+
+
+def test_empty_env_from_file(tmpdir):
+ """Assert empty env is not crashing on load."""
+ settings_file_yaml = """
+ default:
+ foo: bar
+ development: ~
+ """
+ tmpdir.join("settings.yaml").write(settings_file_yaml)
+ settings = LazySettings(environments=True, settings_file="settings.yaml")
+ settings.reload()
+ assert settings.FOO == "bar"
+
+
+def test_merge_unique_in_a_first_level(tmpdir):
+ """Assert merge unique in a first level."""
+ settings_file_yaml = """
+ default:
+ colors: "@merge_unique green,blue"
+ non_exist: "@merge_unique item1,item2"
+ """
+ tmpdir.join("settings.yaml").write(settings_file_yaml)
+ settings = LazySettings(
+ environments=True,
+ settings_file="settings.yaml",
+ COLORS=["red", "green"],
+ )
+ settings.reload()
+ assert settings.COLORS == ["red", "green", "blue"]
+ assert settings.NON_EXIST == ["item1", "item2"]
+
+
+def test_should_not_merge_if_merge_is_not_explicit_set(tmpdir):
+ """Should not merge if merge is not explicit set."""
+ settings_file_yaml = """
+ default:
+ SOME_KEY: "value"
+ SOME_LIST:
+ - "item_1"
+ - "item_2"
+ - "item_3"
+ other:
+ SOME_KEY: "new_value"
+ SOME_LIST:
+ - "item_4"
+ - "item_5"
+ """
+ tmpdir.join("settings.yaml").write(settings_file_yaml)
+ settings = LazySettings(
+ environments=True,
+ settings_files=["settings.yaml"],
+ )
+ settings.reload()
+ assert settings.SOME_KEY == "value"
+ assert settings.SOME_LIST == ["item_1", "item_2", "item_3"]
+
+ other_settings = settings.from_env("other")
+ assert other_settings.SOME_KEY == "new_value"
+ assert other_settings.SOME_LIST == ["item_4", "item_5"]
+
+
+def test_should_not_duplicate_with_global_merge(tmpdir):
+ """Assert merge unique in a first level. Issue #653"""
+ settings_file_yaml = """
+ default:
+ SOME_KEY: "value"
+ SOME_LIST:
+ - "item_1"
+ - "item_2"
+ - "item_3"
+ other:
+ SOME_KEY: "new_value"
+ SOME_LIST:
+ - "item_4"
+ - "item_5"
+ even_other:
+ SOME_KEY: "new_value_2"
+ SOME_LIST:
+ - "item_6"
+ - "item_7"
+ """
+ tmpdir.join("settings.yaml").write(settings_file_yaml)
+ settings = LazySettings(
+ environments=True, settings_files=["settings.yaml"], merge_enabled=True
+ )
+ # settings.reload()
+ assert settings.SOME_KEY == "value"
+ assert settings.SOME_LIST == ["item_1", "item_2", "item_3"]
+
+ other_settings = settings.from_env("other")
+ assert other_settings.SOME_KEY == "new_value"
+ assert other_settings.SOME_LIST == [
+ "item_1",
+ "item_2",
+ "item_3",
+ "item_4",
+ "item_5",
+ ]
+
+
+def test_should_duplicate_when_explicit_set(tmpdir):
+ """Issue #653"""
+ settings_file_yaml = """
+ default:
+ SCRIPTS:
+ - "script1.sh"
+ - "script2.sh"
+ - "script3.sh"
+ other:
+ SCRIPTS:
+ - "script4.sh"
+ - "script1.sh"
+ - "dynaconf_merge"
+ """
+ tmpdir.join("settings.yaml").write(settings_file_yaml)
+ settings = LazySettings(
+ environments=True, settings_files=["settings.yaml"]
+ )
+ assert settings.SCRIPTS == [
+ "script1.sh",
+ "script2.sh",
+ "script3.sh",
+ ]
+
+ other_settings = settings.from_env("other")
+ assert other_settings.SCRIPTS == [
+ "script1.sh",
+ "script2.sh",
+ "script3.sh",
+ "script4.sh",
+ "script1.sh", # explicit wants to duplicate
+ ]
+
+
+def test_should_NOT_duplicate_when_explicit_set(tmpdir):
+ """Issue #653"""
+ settings_file_yaml = """
+ default:
+ SCRIPTS:
+ - "script1.sh"
+ - "script2.sh"
+ - "script3.sh"
+ other:
+ SCRIPTS:
+ - "script4.sh"
+ - "script1.sh"
+ - "dynaconf_merge_unique" # NO DUPLICATE
+ """
+ tmpdir.join("settings.yaml").write(settings_file_yaml)
+ settings = LazySettings(
+ environments=True, settings_files=["settings.yaml"]
+ )
+ assert settings.SCRIPTS == [
+ "script1.sh",
+ "script2.sh",
+ "script3.sh",
+ ]
+
+ other_settings = settings.from_env("other")
+ assert other_settings.SCRIPTS == [
+ "script2.sh",
+ "script3.sh",
+ "script4.sh",
+ "script1.sh",
+ # merge_unique does not duplicate, but overrides the order
+ ]
+
+
+def test_empty_yaml_key_overriding(tmpdir):
+ new_key_value = "new_key_value"
+ os.environ["DYNACONF_LEVEL1__KEY"] = new_key_value
+ os.environ["DYNACONF_LEVEL1__KEY2"] = new_key_value
+ os.environ["DYNACONF_LEVEL1__key3"] = new_key_value
+ os.environ["DYNACONF_LEVEL1__KEY4"] = new_key_value
+ os.environ["DYNACONF_LEVEL1__KEY5"] = new_key_value
+
+ tmpdir.join("test.yml").write(
+ """
+ level1:
+ key: key_value
+ KEY2:
+ key3:
+ keY4:
+ """
+ )
+
+ for merge_state in (True, False):
+ _settings = LazySettings(
+ settings_files=["test.yml"], merge_enabled=merge_state
+ )
+ assert _settings.level1.key == new_key_value
+ assert _settings.level1.key2 == new_key_value
+ assert _settings.level1.key3 == new_key_value
+ assert _settings.level1.get("KEY4") == new_key_value
+ assert _settings.level1.get("key4") == new_key_value
+ assert _settings.level1.get("keY4") == new_key_value
+ assert _settings.level1.get("keY6", "foo") == "foo"
+ assert _settings.level1.get("KEY6", "bar") == "bar"
+ assert _settings.level1["Key4"] == new_key_value
+ assert _settings.level1.Key4 == new_key_value
+ assert _settings.level1.KEy4 == new_key_value
+ assert _settings.level1.KEY4 == new_key_value
+ assert _settings.level1.key4 == new_key_value
+ with pytest.raises(AttributeError):
+ _settings.level1.key6
+ _settings.level1.key7
+ _settings.level1.KEY8
diff --git a/vendor_licenses/box-LICENSE.txt b/vendor_licenses/box-LICENSE.txt
new file mode 100644
index 0000000..2d55592
--- /dev/null
+++ b/vendor_licenses/box-LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017-2022 Chris Griffith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor_licenses/click-LICENSE.rst b/vendor_licenses/click-LICENSE.rst
new file mode 100644
index 0000000..d12a849
--- /dev/null
+++ b/vendor_licenses/click-LICENSE.rst
@@ -0,0 +1,28 @@
+Copyright 2014 Pallets
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor_licenses/licenses.sh b/vendor_licenses/licenses.sh
new file mode 100755
index 0000000..43ee8af
--- /dev/null
+++ b/vendor_licenses/licenses.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/bash
+# SPDX-FileCopyrightText: 2022 Maxwell G <gotmax@e.email>
+# SPDX-License-Identifier: MIT
+
+set -euo pipefail
+ruamel_yaml_version=0.16.10
+
+rm -fv *-LICENSE.*
+wget https://github.com/cdgriffith/Box/raw/master/LICENSE -O box-LICENSE.txt
+wget https://github.com/uiri/toml/raw/master/LICENSE -O toml-LICENSE.txt
+wget https://github.com/hukkin/tomli/raw/master/LICENSE -O tomli-LICENSE.txt
+wget https://github.com/pallets/click/raw/main/LICENSE.rst -O click-LICENSE.rst
+wget https://github.com/theskumar/python-dotenv/raw/main/LICENSE -O python-dotenv-LICENSE.txt
+wget "https://files.pythonhosted.org/packages/source/r/ruamel.yaml/ruamel.yaml-${ruamel_yaml_version}.tar.gz" -O- | tar -xzvO "ruamel.yaml-${ruamel_yaml_version}/LICENSE" >ruamel.yaml-LICENSE.txt
diff --git a/vendor_licenses/python-dotenv-LICENSE.txt b/vendor_licenses/python-dotenv-LICENSE.txt
new file mode 100644
index 0000000..39372fe
--- /dev/null
+++ b/vendor_licenses/python-dotenv-LICENSE.txt
@@ -0,0 +1,87 @@
+python-dotenv
+Copyright (c) 2014, Saurabh Kumar
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of python-dotenv nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+django-dotenv-rw
+Copyright (c) 2013, Ted Tieken
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of django-dotenv nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Original django-dotenv
+Copyright (c) 2013, Jacob Kaplan-Moss
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of django-dotenv nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor_licenses/ruamel.yaml-LICENSE.txt b/vendor_licenses/ruamel.yaml-LICENSE.txt
new file mode 100644
index 0000000..5b863d3
--- /dev/null
+++ b/vendor_licenses/ruamel.yaml-LICENSE.txt
@@ -0,0 +1,21 @@
+ The MIT License (MIT)
+
+ Copyright (c) 2014-2020 Anthon van der Neut, Ruamel bvba
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff --git a/vendor_licenses/toml-LICENSE.txt b/vendor_licenses/toml-LICENSE.txt
new file mode 100644
index 0000000..576d83e
--- /dev/null
+++ b/vendor_licenses/toml-LICENSE.txt
@@ -0,0 +1,27 @@
+The MIT License
+
+Copyright 2013-2019 William Pearson
+Copyright 2015-2016 Julien Enselme
+Copyright 2016 Google Inc.
+Copyright 2017 Samuel Vasko
+Copyright 2017 Nate Prewitt
+Copyright 2017 Jack Evans
+Copyright 2019 Filippo Broggini
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor_licenses/tomli-LICENSE.txt b/vendor_licenses/tomli-LICENSE.txt
new file mode 100644
index 0000000..e859590
--- /dev/null
+++ b/vendor_licenses/tomli-LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Taneli Hukkinen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor_licenses/vendor_versions.txt b/vendor_licenses/vendor_versions.txt
new file mode 100644
index 0000000..65f74aa
--- /dev/null
+++ b/vendor_licenses/vendor_versions.txt
@@ -0,0 +1,6 @@
+python-box==4.2.3
+toml==0.10.8
+tomli==2.0.1
+click==7.1.x
+python-dotenv==0.13.0
+ruamel.yaml==0.16.10
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.12.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.12.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.12.egg-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.12.egg-info/not-zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.12.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.12.egg-info/top_level.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf/vendor/tomllib/__init__.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf/vendor/tomllib/_parser.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf/vendor/tomllib/_re.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf/vendor/tomllib/_types.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf/vendor/tomllib/_writer.py
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.7.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.7.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.7.egg-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.7.egg-info/not-zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.7.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dynaconf-3.1.7.egg-info/top_level.txt
No differences were encountered in the control files