././@PaxHeader0000000000000000000000000000007100000000000010213 xustar0015 uid=4214970 15 gid=4214970 27 mtime=1773776274.923678 dynaconf-3.2.13/0000755000000000000000000000000015156326623013546 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/3.x-release-notes.md0000644000000000000000000001211115156324234017234 0ustar00pbrochadpbrochad# Dynaconf 3.0.0 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/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/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. - Added implementation for `__dir__` to allow auto complete for terminal and IDEs. - Add option to override mount point for vault server. - `LazySettings` is now aliased to `Dynaconf` - `Dynaconf` class now accepts a parameter `validators=[Validator, ...]` that will be immediately evaluated when passed. ## Breaking Changes > **NOTE 1** `from dynaconf import settings` will keep working until version `4.0.x` to allow users to migrate gradually without having breaking changes but it will raise some DeprecationWarnings. > **NOTE 2** If you are using `FLASK` or `DJANGO` plugins there will be no breaking change on the framework settings, **a.k.a your project will keep working fine!**. #### `dynaconf.settings` global settings instance is now deprecated. Users are now supposed to create their own instance of `settings` instead of using the deprecated `from dynaconf import settings`. **project/config.py** ```python from dynaconf import Dynaconf 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://www.dynaconf.com/configuration/) ex: ```python settings = Dynaconf( settings_file=["settings.toml", ".secrets.toml"], envvar_prefix="MYPROJECT" ) ``` > With the above instance your `settings` will load data from those 2 `.toml` files and also environment variables prefixed with `MYPROOJECT` e.g: `export MYPROJECT_FOO=1` #### Dynaconf is now envless by default. Historically Dynaconf worked in a multi layered environments for loading data from files, so you were supposed to have a file like: ```toml [default] key = 'value' [production] key = 'value' ``` **Now starting on 3.0.0** the environments are disabled by default, so the same file can be created as. ```toml key = 'value' ``` And you can still have the environments but only if you explicit specify it when creating your instance. To have the previous behavior supporting all environments from the first level of a file. ```python settings = Dynaconf( environments=True ) ``` or to strictly specify your environments. ```python settings = Dynaconf( environments=["default", "prodution", "testing", "xyz"], ) ``` Once it is defined all the other functionalities still works such as. ```bash export ENV_FOR_DYNACONF=production myprogram.py ``` #### Automatic load of `settings.*` files are disabled by default. Starting on 3.x Dynaconf will now only load files passed explicitly to **settings_file** option when creating the instance or specified as env var **SETTINGS_FILE_FOR_DYNACONF**. When creating the settings instance the file **paths** or **globs** should be specified unless you want only env vars and external loaders to be loaded. Single file ```py settings = Dynaconf( settings_file="settings.toml", ) ``` Or multiple files. ```py settings = Dynaconf( settings_file=["settings.toml", "path/*.yaml"], ) ``` Dynaconf will respect the exact order informed by you on the `settings_file` parameter. > **NOTE** the parameters `preload`, `includes` and `secret` continues to work in the same way as before. > **NOTE** the parameters `preload`, `includes` and `secret` continues to work in the same way as before. #### Load of dotenv `.env` is now disabled by default. Dynaconf will **NO MORE** automatically load the data from `.env` file and will do that only if `load_dotenv=True` is provided. ```py settings = Dynaconf( load_dotenv=True ) ``` BY default it keeps searching for files in the current `PROJECT_ROOT` named `.env` and the `dotenv_path` accepts a relative path such as `.env` or `path/to/.env` #### DEBUG Logging is now completely removed There is no more `logging` or `debug` messages being printed, so the variable `DEBUG_LEVEL` has no more effect. ## Coming in 3.1.x - Support for Pydantic BaseSettings for Validators. - Support for replacement of `toml` parser on envvars loader. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776227.0 dynaconf-3.2.13/CHANGELOG.md0000644000000000000000000046065415156326543015377 0ustar00pbrochadpbrochadChangelog ========= ## [3.2.13](https://github.com/pedro-psb/dynaconf/releases/tag/3.2.13) - 2026-03-17 ### Chore - fix linting errors and pre-commit config. *By Pedro Brochado*. ## [3.2.12](https://github.com/pedro-psb/dynaconf/releases/tag/3.2.12) - 2025-10-10 ### Bug Fixes - get method to return Any type.. *By Bruno Rocha*. - remove unnecessary recursive evaluation call on Settings.get. *By Pedro Brochado*. - improve performance of settings access in a loop (part 1). *By Pedro Brochado*. ## [3.2.11](https://github.com/dynaconf/dynaconf/releases/tag/3.2.11) - 2025-05-06 ### Bug Fixes - add correct supported python version to 3.2.x. *By Bruno Rocha*. - `-k` must exit code 1 when key do not exist (#1293). *By Bruno Rocha*. - use sys.argv instead of click.get_os_args (#1292). *By Bruno Rocha*. - windows CI. *By Bruno Rocha*. - make raw variables private (#1287). *By Fabricio Aguiar*. - Better way for CLI to find the Django Settings. *By Bruno Rocha*. - handle empty hooks and boolean environments.. *By Bruno Rocha*. ### Features - envless load file (#1295). *By Bruno Rocha*. - Run CLI as module with `python -m dynaconf` (#1290). *By Bruno Rocha*. ### Docs - clarification on redis hash title when using custom envvar prefixes (#1273). *By Pedro Brochado*. ## [3.2.10](https://github.com/dynaconf/dynaconf/releases/tag/3.2.10) - 2025-02-17 ### Bug Fixes - Hotfix hook collector to avoid eager evaluation. (#1255). *By Bruno Rocha*. ## [3.2.9](https://github.com/dynaconf/dynaconf/releases/tag/3.2.9) - 2025-02-16 ## [3.2.8](https://github.com/dynaconf/dynaconf/releases/tag/3.2.8) - 2025-02-16 ### Bug Fixes - Parse data type on merge with comma separated value. *By Bruno Rocha*. ### Features - Add CLI command `debug-info` (#1251). *By Bruno Rocha*. - Add support for decorated hooks on settings files (#1246). *By Bruno Rocha*. - Add VAULT_TOKEN_RENEW_FOR_DYNACONF config/code (#1094) (#1242). *By Pedro Brochado*. - populate_obj takes convert_to_dict (#1237). *By Bruno Rocha*. - add VAULT_TOKEN_RENEW. *By Bruno Rocha*. ## [3.2.7](https://github.com/dynaconf/dynaconf/releases/tag/3.2.7) - 2025-01-21 ### Bug Fixes - lazy validator's default value would evaluate early (#1198). *By Pedro Brochado*. - Fixed an error that would raise when using get_history() with lazy values (#1184) (#1185). *By Pedro Brochado*. - Fixed Redis loader when ENV prefix is `None`. - Populate object method now takes `internal` attribute to filter out internal variables. - On CLI `json.dumps` defaults to `repr` for types that cannot be serialized. - Added an identifier to validator calls of `set` method - Fix django app discovery using DJANGO_SETTINGS_MODULE variable ### Features - Added `@insert` token to call `list.insert` - Allow env loader to load from multiple prefixes - Allow multiple composable current environments - Track more data on `load_file` method - Added `--json` to dynaconf list CLI ## [3.2.6](https://github.com/dynaconf/dynaconf/releases/tag/3.2.6) - 2024-07-19 ## [3.2.5](https://github.com/pedro-psb/dynaconf/releases/tag/3.2.5) - 2024-03-18 ### Bug Fixes - `_bypass_evaluation` showing in end-user settings (#1071). *By Pedro Brochado*. - dependabot alert 21 about Django (on tests) (#1067). *By Bruno Rocha*. - include load with relative root path (#1049). *By Pedro Brochado*. ### Features - Add `@get` converter to alias existing keys (#1040). *By Bruno Rocha*. ### Docs - fix wrong info about validation trigger on insantiation (#1076). *By Pedro Brochado*. - fix incorrect combination of TOML table and inline table (#1070). *By Aaron DeVore*. - Fix mkdocs warnings for cleaner build output (#1061). *By Mitchell Edmunds*. - Add dynaconf API to docs with mkdocstrings (#1058). *By Mitchell Edmunds*. - fix argument `env` in Validation at validation.md (#1051). *By Mostafa Alayesh*. - fix click help syntax error (#1041). *By xiaohuanshu*. - replace dead link to flask subclassing page (#1031). *By Adam Kjems*. - Add explicit Dynaconf instantiation to sample code (#1022). *By Lucas Limeira*. - Fix the syntax errors in the sample program. (#1027). *By Sun Jianjiao*. ### Chore - Replace lint and formatting tools with ruff (#1074). *By Mitchell Edmunds*. - Replace/Update release script (#1078). *By Pedro Brochado*. - add "typos" tool and run it in codebase/docs (#1063). *By Mitchell Edmunds*. - fix create-release-commit.sh permission. *By Pedro Brochado*. - fix shell script in release.yml (2). *By Pedro Brochado*. - fix shell script in release.yml. *By Pedro Brochado*. - move release workflow to GitHub actions (partial) (#1043). *By Pedro Brochado*. - Fix misspelled variable name (#1032). *By HAMASHITA*. - Fix misspelled GitHub action names on main.yml (#1033). *By Pedro Brochado*. ## [3.2.4](https://github.com/dynaconf/dynaconf/releases/tag/3.2.4) - 2023-10-30 ### Bug Fixes - allow underscore in env name #1011 (#1015) ([f55c277](https://github.com/dynaconf/dynaconf/commit/f55c27761ec839df3e6c5806d19a52594f6a5c9b) by Bruno Rocha). - support for 'entrypoint-path' in FLASK_APP #946 (#1014) ([f2bb032](https://github.com/dynaconf/dynaconf/commit/f2bb032ea25a3110f3ba38891fc3307edc203a9a) by Pedro Pessoa). - dynaconf_merge=False on nested structures (#1012) ([b113e74](https://github.com/dynaconf/dynaconf/commit/b113e744617dd75aa6ce1d4d1d793a93abf65edc) by Pedro Pessoa). - non-str key raising type error #1005 (#1008) ([4ed2350](https://github.com/dynaconf/dynaconf/commit/4ed2350da4c6f4fa2b046cbef2f07a56f97ef94b) by Pedro Pessoa). - pin hvac minimum version ([9170beb](https://github.com/dynaconf/dynaconf/commit/9170beb2386989f7486fcc22e21918fe441aa1e8) by Jake Callahan). ### Docs - Fix link to configuration page (#1020) ([df3b437](https://github.com/dynaconf/dynaconf/commit/df3b43718858786d0a2feb2d7b60c9515b01a15d) by Vladislav Sharapov). - Improve Configuration and Validation sections of the docs (#989) ([99741a3](https://github.com/dynaconf/dynaconf/commit/99741a3124b4c788e6a8e9e3761e90e7f9be5cc3) by Sebastian Correa). ## [3.2.3]() - 2023-09-13 - Release version 3.2.3. [Bruno Rocha] Shortlog of commits since last release: Anderson Sousa (1): chore(lint): pep8 errors (#995) Bruno Rocha (7): Release version 3.2.1 Hooking: Add support for changing the wrapped class (#975) Hotfix bypass evaluation #984 (#985) Fix #976 from envvars parse True/False as booleans (#983) Fix #982 glob on settings_files (#987) docker compose is available on CI no need to install fix(django): fix #1000 AttributeError on admin (#1002) Pedro Pessoa (1): Inspect Feature Review (#958) pedro-psb (1): Release version 3.2.2 - Fix(django): fix #1000 AttributeError on admin (#1002) [Bruno Rocha] fix #1000 - Chore(lint): pep8 errors (#995) [Anderson Sousa] - Release version 3.2.2. [pedro-psb] Shortlog of commits since last release: Bruno Rocha (6): Release version 3.2.1 Hooking: Add support for changing the wrapped class (#975) Hotfix bypass evaluation #984 (#985) Fix #976 from envvars parse True/False as booleans (#983) Fix #982 glob on settings_files (#987) docker compose is available on CI no need to install Pedro Pessoa (1): Inspect Feature Review (#958) - Inspect Feature Review (#958) [Pedro Pessoa] inspect_settings: * change inspect_settings report-output format names and structure * implement 'history_limit' on 'utils.inspect:inspect_settings' * rename key_dotted_path to key * rename history_sort to new_first * enforce usage of kwargs, except for "settings", "key" and "env" * merge "output_format" and "custom" into "dumper" * add "report_builder" to allow output customization * add explicit 'print_report' param cli: * implement cli --limit|-n * update cli to match 'inspect_settings' changes non-breaking-change * minor "utils.inspect" internal renames * re-write docstrings in sphinx-style (:param foo) * expose 'get_history' * refactor 'test_cli.py': use more robust test isolation strategy * add data return to 'inspect_settings' for cleaner testing - Docker compose is available on CI no need to install. [Bruno Rocha] - Fix #982 glob on settings_files (#987) [Bruno Rocha] - Fix #976 from envvars parse True/False as booleans (#983) [Bruno Rocha] Only when reading from envvars True and False will be transformed to lowercase to allow toml parser. - Hotfix bypass evaluation #984 (#985) [Bruno Rocha] - Hooking: Add support for changing the wrapped class (#975) [Bruno Rocha] - add support for _wrapper_class replacement - add hooking with a Hookable implementation to be used by Django integration only (this is transparent now, must not affect any current user) - fix integer key access - Changes on inspect - History now saves the raw value instead of parsed, as parsed will be showed on the current_value anyway - Set will always create a source_metadata even if not passed or if a str is passed as laoder_identifier - Internal uses of set are now tracked - dynaconf_hooks.py is now tracked - set method now keeps `value` as the raw unparsed value and `parsed` as the evaluated value - `inspect` command no more prints `django app detected` - get_history now accepts `include_internal` to output internal identifiers - `_get_data_by_key` refactored for dotted path traversal - access to integer indexes on dotted path removed, we gonna implement later as `key._1_` - Fixed the acceptance of keys with other types - Release version 3.2.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (5): Ignore docs build without a tag Cancel any running CI job when a Push is made to an existing PR or branch (#952) Fix #959 cli get will exit code 1 in case of KeyError. (#960) add tech preview note to inspect docs (#961) Build docs Hugo Prudente (1): Doc advanced usage for cli overrides dynaconf settings fix #967 (#970) Marian Ganisin (1): Feat: Support for multidoc yaml files (#825) Pedro Pessoa (11): Docs - Update envvar.md custom token e.g. to use add_converter (#941) Feature - Inspect and CLI (#939) Fix - Template substitution with variable update (#944) Assert #658 works (#945) fix infinite recursions in special case of django app #867 (#947) Fix - Django functions with `add_converter` (#951) Fix hooks not re-running on reload #850 (#953) update vault and redis warning recommendations. fix #950 (#954) Fix - Enable merge equal False (#957) CI - Test docker-compose pyyaml issue (#964) Fix: unexpected _bypass_evaluation in BoxList (#966) pedro-psb (1): Release version 3.2.0 3.2.1 (2023-08-11) ------------------ Fix ~~~ - Unexpected _bypass_evaluation in BoxList (#966) [Pedro Pessoa] * fix _bypass_evaluation showing in BoxList Other ~~~~~ - Release version 3.2.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (5): Ignore docs build without a tag Cancel any running CI job when a Push is made to an existing PR or branch (#952) Fix #959 cli get will exit code 1 in case of KeyError. (#960) add tech preview note to inspect docs (#961) Build docs Hugo Prudente (1): Doc advanced usage for cli overrides dynaconf settings fix #967 (#970) Marian Ganisin (1): Feat: Support for multidoc yaml files (#825) Pedro Pessoa (11): Docs - Update envvar.md custom token e.g. to use add_converter (#941) Feature - Inspect and CLI (#939) Fix - Template substitution with variable update (#944) Assert #658 works (#945) fix infinite recursions in special case of django app #867 (#947) Fix - Django functions with `add_converter` (#951) Fix hooks not re-running on reload #850 (#953) update vault and redis warning recommendations. fix #950 (#954) Fix - Enable merge equal False (#957) CI - Test docker-compose pyyaml issue (#964) Fix: unexpected _bypass_evaluation in BoxList (#966) pedro-psb (1): Release version 3.2.0 - Doc advanced usage for cli overrides dynaconf settings fix #967 (#970) [Hugo Prudente] - Feat: Support for multidoc yaml files (#825) [Bruno Rocha, Marian Ganisin, Pedro Pessoa] This adds `safe_load_all` to possible yaml loaders and enables processing multidoc yaml files. This can be handy in case more configs are need but it's difficult or impossible to pass multiple files. fixes #824 --------- - CI - Test docker-compose pyyaml issue (#964) [Pedro Pessoa] * test ciinstall * try bumping pyyaml before docker-compose * try using latest docker-compose binary * add note about docker-compose on contributing.md - Build docs. [Bruno Rocha] - Release version 3.2.0. [pedro-psb] Shortlog of commits since last release: Bruno Rocha (4): Ignore docs build without a tag Cancel any running CI job when a Push is made to an existing PR or branch (#952) Fix #959 cli get will exit code 1 in case of KeyError. (#960) add tech preview note to inspect docs (#961) Pedro Pessoa (9): Docs - Update envvar.md custom token e.g. to use add_converter (#941) Feature - Inspect and CLI (#939) Fix - Template substitution with variable update (#944) Assert #658 works (#945) fix infinite recursions in special case of django app #867 (#947) Fix - Django functions with `add_converter` (#951) Fix hooks not re-running on reload #850 (#953) update vault and redis warning recommendations. fix #950 (#954) Fix - Enable merge equal False (#957) - Add tech preview note to inspect docs (#961) [Bruno Rocha] - Fix #959 cli get will exit code 1 in case of KeyError. (#960) [Bruno Rocha] - Fix - Enable merge equal False (#957) [Pedro Pessoa] - Update vault and redis warning recommendations. fix #950 (#954) [Pedro Pessoa] - Fix hooks not re-running on reload #850 (#953) [Pedro Pessoa] * fix hooks not re-running on reload #850 by clearing `settings._loaded_hooks` - Cancel any running CI job when a Push is made to an existing PR or branch (#952) [Bruno Rocha] - Fix - Django functions with `add_converter` (#951) [Pedro Pessoa] - Ignore docs build without a tag. [Bruno Rocha] - Fix infinite recursions in special case of django app #867 (#947) [Pedro Pessoa] - Assert #658 works (#945) [Pedro Pessoa] * assert #658 works * fix linting - Fix - Template substitution with variable update (#944) [Pedro Pessoa] * fix tmpl substitution when var is updated #575 #603 #660 #690 - add '_safe_get' and '_safe_items' to DynaBox - small indirect refactors * fix inspect exception names (flake8) * remove accidental file * update to the new official publisher of the vault image (hashicorp) - see deprecation notice in: https://hub.docker.com/_/vault/ * fix test_vault.sh image name (update to hashicorp/vault) * fix main workflow and test_vault_userpass.sh references to vault image - can't trust telescope live_grep - Feature - Inspect and CLI (#939) [Pedro Pessoa] * implement get_history and inspect_settings utilities * implement CLI inspect command - Docs - Update envvar.md custom token e.g. to use add_converter (#941) [Pedro Pessoa] * update envvar.md custom token e.g. to use add_converter * fix Windows test compatibility * fix test on windows (attempt n2) * fix windows test (last attempt) * lastly last attempt to fix windows test * skip windows test on the test sample - Update validation.md to fix typo (#937) [Gwyn Evans] The doc has ValidationError being in dynaconf.validators not dynaconf.validator - Fix - Order of variables break parsing (#869) (#933) [Bruno Rocha, Pedro Pessoa] * fix order of variables break parsing (#869) * fix linting --------- - Docs - Fix error in example about casting (#930) (#935) [Pedro Pessoa] * gitignore .tool-versions (asdf) * fix wrong example in docs - Replace pkg_resources with importlib.metadata (#934) [Pedro Pessoa, Thijs Miedema] * fix: remove pkg_resources in favour of importlib.metadata. fixes 851. * fix formatting --------- - Add support for Vault username/password access (#928) [Hans Harhoff Andersen, Hans Harhoff Andersen, Pedro Pessoa] * add userpass test * handle userpass handle if user/token does not have list permission * fix bug in implementation and fix test * add newlines * add new integration test for vault * remove breakpoint * Pinning ipython version for python 3.8 compatibility. --------- - Fix AttributeError with integer keys (yaml) #919 (#920) [Pedro Pessoa] * convert int keys to str. fix #919 * remove link pytest-dynaconf (broken) - Docs - `load_file` relative path resolution and `root_path` fallbacks #909 (#921) [Pedro Pessoa] * doc: add clarification on root_path, load_file and related * add complementary info at docstrings - Update FUNDING.yml. [Bruno Rocha] - Feature - Allow system environment fallback in `get()` #742 (#916) [Pedro Pessoa] * add sysenv_fallback for settings.get() (global and local options) #742 * add docs entry for `sysenv_fallback` config option * fix formmating * remove linux-specific test for sysenv_fallback - Remove codecov (#918) [Bruno Rocha] - Fix - Add support for lowercase `envvar_prefix` in DynaconfFlask (Fix #848) (#915) [Pedro Pessoa] * add support for lowercase `envvar_prefix` in Flask. fix #848 * add support for lowercase options setting in FlaskDynaconf init - Docs: add clarification on DEFAULT_ENV_FOR_DYNACONF config (#912) [Pedro Pessoa] - Fix - Array entries duplicated when using Validator with default for computed value (#910) [Pedro Pessoa] * add test for #905 (item duplication in list) - when using validator with the default field, list items would get duplicated under certain circumstances. * fix for #905 - this avoids an unwanted merge when setting the default for some value. - Docs - Add `Extensions` header to index and `pytest-dynaconf` reference (#907) [Bruno Rocha, Pedro Pessoa] * add "extensions" section to index. #699 * grammarly-assisted typos and style fixes --------- - Feature - Validate on call to update, set or load_file (#900) [Bruno Rocha --------- Co- authored-by: Bruno Rocha , Pedro Pessoa] * add tests to validate on update/set/load_file support for: - global option VALIDATE_ON_UPDATE_FOR_DYANCONF - Settings.update(... validate=bool) - Settings.set(... validate=bool) - Settings.load_file(... validate=bool) * feature: validate on call to update/set. #712 - option to validate data in methods Settings.[load|set|load_file] - global option to set default behaviour. eg: settings = Dynaconf(validate_on_update=True) settings.validators.register( add validators ) settings.load(data) # will validate settings.load(data, validate=False) # won't validate * feature: validate on call to load_file (add support) eg: settings.load_file(file, validate=True) # will validate * Replace bare tries with suppress and increase codecov (#901) * docs: include validate on update feature - add section to Configurations - include inside Validation * support for validate_all() on validate_on_update feat - add support for validate_all() as `validate="all"`. - tests: include all file loaders (except .env, coz load_file doesnt support it) * Update dynaconf/default_settings.py add FOR_DYNACONF suffix for validate_on_update envvar - Docs - Add faq and fix `includes` info (#899) [Bruno Rocha, Pedro Pessoa] * fix include/preload docs and allow str|list. fix #898 * add faq about #792 and #712 --------- - Provides new add_converter function for #858 (#904) [Will Gordon] * Add tests for reverse_lazy for #858 * Add converter helper function * Refactor adding custom converters - Replace bare tries with suppress and increase codecov (#901) [Bruno Rocha] - Fix incorrect reverse_lazy workaround in docs to be correct (#897) [Will Gordon] - Fix anchor link in FAQ doc (#895) [Will Gordon] - Fix / CLI command validate not working properly (#892) [Bruno Rocha, Pedro Pessoa] * fix #879 with @type converter - add type_map to convert quoted types to proper class types ("int" to int) - add functional test - small adjustment on existing tests/test_cli.py:test_validate * fix mypy complain on truthy callable This expression is always True (considering that the value of 'self.cast' is not directly modified by some other module) - 'cast' param in the constructor can be None, but if so, - 'self.cast' is initialized as 'lambda value: value', which - is a truthy value (a function class). * remove @type support and add tomlllib error handling * add proper type conversion and is_type_of error handling * adds for pre-commit and coverage * Update cli.py: remove unnecessary import * Update Makefile: revert accidental change --------- - Docs - Add question to faq and clarify settings loading (#894) [Pedro Pessoa] * add faq about overriding settings. fix #859 * clarify loading settings and preload-includes. fix #812 * grammarly-assisted typos and small corrections - Docs - Django functions in settings file and misc (#891) [Bruno Rocha, Pedro Pessoa] * restructure tip/warning boxes and related readability content - special look at "Reading Settings on Standalone Scripts" (has more changes) * add 'using django func inside custom settings' to django page and faq - used the user-tested example: https://github.com/dynaconf/dynaconf/issues/858#issuecomment-1418202630 - faq just mentions the doc section * add note on envvars uppercase prefix. fix #860 * grammarly-assisted typos on django.md * add mkdocs plugin to requirements-dev - it had to be installed manually, because it was just defined in 'requirements.txt' (which is not read by any make command) --------- - Docs/clarify merging defaults (#890) [Pedro Pessoa] * add better overview of merging. fix #863 * grammarly-assisted type checking - Add faq questions and small typos (#889) [Pedro Pessoa] - Docs - Change design of Available Options (#887) [Bruno Rocha, Pedro Pessoa] * configuration-page: rearrange info into headers for all opts. #884 some small modifications are out of scope of this design change and will be pointed in the PR * add clarification on preferred way of loading settings --------- - Update doc-ignore.yml. [Bruno Rocha] - Update doc-ignore.yml. [Bruno Rocha] I guess name must be the same as the required on main - Add workflow to run when only docs change. fix #886 (#888) [Pedro Pessoa] - CI - Prevent running when just doc changes are made (#885) [Pedro Pessoa] * add path-filter to main ci. fix rfc #883 * fix indentation (3 to 2 spaces) - Docs/add faq page (#882) [Pedro Pessoa] * add faq page to docs * add faq page to docs. fix #880 - Fix #876 - avoid failure on deleted cwd (#877) [Bruno Rocha] * Fix #876 no error if cwd is deleted and file is abs * Attempt to fix tests on mac and windows * Fix macos error - Add merge argumento to `loader.write` to fix #839 (#875) [Bruno Rocha, Bruno Rocha --------- Co- authored-by: Marcelo Lino , Marcelo Lino] * Add merge argument to `loaders.write` to reflect the documentation * Pass merge argument from `loaders.write` to `loader.write` reflect the expected behavior described in the documentation * Change argument order - Release version 3.1.12. [Bruno Rocha] Shortlog of commits since last release: André "decko" de Brito (2): Removes Codacy Coverage Reporter (#871) Add a namespace property for VAULT_FOR_DYNACONF dict (#870) Bruno Rocha (8): Release version 3.1.11 bump dev version to 3.1.12 Ensure `dynaconf get` returns a valid json string. (#813) [bugfix] Fix access of keys with spaces (#815) hotfix func tests (#816) Add Python 3.11 to CI (#830) fix lint error Fix casting on Validator and improve docs (#873) Bryan Weber (1): Fix typos in envvars docs (#840) Florian Apolloner (1): Small typo fix. (#822) Maxwell G (1): Include license files for vendored deps (#841) MicLon (1): fix: envvars.md get `HOME` environment variable (#831) Otávio Dantas (1): doc(pt-br): translate flask extension to pt-br (#852) Sergio Kef (1): Fix code snippet in docs (#843) Tetiana (1): #817 Add note about defining root_path when testing (#818) jctanner (1): Handle all failures when pwd does not exist. (#857) jmeichle (1): Add support for VAULT_NAMESPACE_FOR_DYNACONF (#854) 3.1.12 (2023-03-02) ------------------- Fix ~~~ - Envvars.md get `HOME` environment variable (#831) [MicLon] Other ~~~~~ - Release version 3.1.12. [Bruno Rocha] Shortlog of commits since last release: André "decko" de Brito (2): Removes Codacy Coverage Reporter (#871) Add a namespace property for VAULT_FOR_DYNACONF dict (#870) Bruno Rocha (8): Release version 3.1.11 bump dev version to 3.1.12 Ensure `dynaconf get` returns a valid json string. (#813) [bugfix] Fix access of keys with spaces (#815) hotfix func tests (#816) Add Python 3.11 to CI (#830) fix lint error Fix casting on Validator and improve docs (#873) Bryan Weber (1): Fix typos in envvars docs (#840) Florian Apolloner (1): Small typo fix. (#822) Maxwell G (1): Include license files for vendored deps (#841) MicLon (1): fix: envvars.md get `HOME` environment variable (#831) Otávio Dantas (1): doc(pt-br): translate flask extension to pt-br (#852) Sergio Kef (1): Fix code snippet in docs (#843) Tetiana (1): #817 Add note about defining root_path when testing (#818) jctanner (1): Handle all failures when pwd does not exist. (#857) jmeichle (1): Add support for VAULT_NAMESPACE_FOR_DYNACONF (#854) - Fix casting on Validator and improve docs (#873) [Bruno Rocha] fix: #823 fix: #834 - Fix lint error. [Bruno Rocha] - Small typo fix. (#822) [Bruno Rocha, Florian Apolloner] - Fix typos in envvars docs (#840) [Bruno Rocha, Bryan Weber] - Include license files for vendored deps (#841) [Bruno Rocha, Maxwell G] - Fix code snippet in docs (#843) [Sergio Kef] Fix syntax error, missing comma - Add a namespace property for VAULT_FOR_DYNACONF dict (#870) [André "decko" de Brito, Bruno Rocha] - Removes Codacy Coverage Reporter (#871) [André "decko" de Brito] - Add support for VAULT_NAMESPACE_FOR_DYNACONF (#854) [Bruno Rocha, jmeichle] * Add support for VAULT_NAMESPACE_FOR_DYNACONF * adjusting for linting --------- - Handle all failures when pwd does not exist. (#857) [jctanner] * Handle all failures when pwd does not exist. --------- - Doc(pt-br): translate flask extension to pt-br (#852) [Otávio Dantas] - Add Python 3.11 to CI (#830) [Bruno Rocha, Vicente Marçal] Fix a testing error on CI - #817 Add note about defining root_path when testing (#818) [Tetiana] fix #817 - Hotfix func tests (#816) [Bruno Rocha] - [bugfix] Fix access of keys with spaces (#815) [Bruno Rocha] Fix #814 - Ensure `dynaconf get` returns a valid json string. (#813) [Bruno Rocha] - Bump dev version to 3.1.12. [Bruno Rocha] - Release version 3.1.11. [Bruno Rocha] This release is a hotfix on top of 3.1.10 Please read changelog from https://github.com/dynaconf/dynaconf/releases/tag/3.1.10 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) 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] 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) - Bump dev version. [Bruno Rocha] [skip ci] - Bye Azure (#657) [Bruno Rocha] - Move to Github Actions (#656) [Bruno Rocha] * Move to Github Actions - [ ] Codecov Fix #640 * Enabled Vault and REdis - Add support for dynaconf_hooks(post) issue #654 (#655) [Bruno Rocha] - Update FUNDING.yml. [Bruno Rocha] - Fix a typo in the docs. [Linus Torvalds] - Fix typo (#647) [Ambient Lighter] - Delete requirements.txt. [Bruno Rocha] - Fix #491 pytest django Fix #491 pytest and django (#646) [Bruno Rocha] - Fix #596 django override (#645) [Bruno Rocha] * Fix #596 django.test.override issue * Fix CI side effects - Remove dependabot (this project has no dependencies) [Bruno Rocha] - Update codaci badge (#636) [Bruno Rocha] - Add myoy and pre-commit to CI (#635) [Bruno Rocha] - Description and type annotation for validator (#634) [Bruno Rocha] - Add a warning if `--env` is passed to `init` (#629) [Bruno Rocha, Bruno Rocha, Robert Rosca] * Add a warning if `--env` is passed to `init` * Fix typo, `file` was doubled in init help * Update docstrings for CLI * Raise error if using `-i` with `init` subcommand * Update docs to match current behaviour * add test coverage - Add type annotations for dynaconf.utils (#450) [Bruno Rocha, Bruno Rocha, Martin Thoma] * Add type annotations for dynaconf.utils Make 'mypy .' succeed; to a big extend by ignoring errors * Manually format line length * Drop Python 3.6 * Coverage fix - Do not search anywhere if the absolute path to a file provided (#570) [Bruno Rocha, Tanya Tereshchenko] * Do not search anywhere if the absolute path to a file provided fixes #569 * Fix test coverage and added some comments. - Update FUNDING.yml. [Bruno Rocha] - Fix black issues (#631) [Bruno Rocha] - Feat: add filter strategy with PrefixFilter (#625) [Nicholas Dentandt] - Fix typo (#588) [Bruno Rocha, puntonim] - Added an example using FastAPI (#571) [Bruno Rocha, Yusuf Kaka] - Fix dict iterator methods for flask DynaconfConfig (#581) [Bruno Rocha, Frank Battaglia, FrankBattaglia] - Fix #595 namedtuples are no more converted to BoxList (#623) [Bruno Rocha] - Add support for Python 3.9 and remove Ubuntu 16.04 that is deprecated in Azure Pipelines (#618) [Kamil Gałuszka] - Upgrade to GitHub-native Dependabot (#574) [dependabot-preview[bot], dependabot-preview[bot]] - Update configuration.md (#554) [Bruno Rocha, Konstantin] Remove redundant `s` (spelling error) - Update configuration.md (#553) [Bruno Rocha, Konstantin] Change spelling error - HOTFIX: Add coverage for 2 lines on validators. [Bruno Rocha] - Add the ability for selective validation (#549) [Bruno Rocha, Jacob Callahan] This change introduces the ability to control which sections of a settings object are subject to validation. This is controlled primarily by two mechanisms. 1: When creating a settings object, new arguments `validate_only` and `validate_exclude` have been added which receive a list of settings paths. 2: When manually calling validate, new arguments `only` and `exclude` have been added. All of these allow for either a string or list of strings representing settings paths. For example: `settings.validators.validate(only=["settings.something", "settings.another"])` settings = Dynaconf(..., validate_exclude="settings.bad") Fixes #508 - Bump mkdocs-material from 7.0.5 to 7.0.6 (#552) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 7.0.5 to 7.0.6. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/7.0.5...7.0.6) - Removed release_notes from the docs. (#550) [Bruno Rocha] * removed release_notes from the docs. towncrier will be implemented soon * fix #551 - Demo link (#546) [Bruno Rocha] * Add demo link, add better docstring. > **DEMO:** You can see a working demo here: https://github.com/rochacbruno/learndynaconf * add reference to Rust hydroconf - Release version 3.1.4. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (3): Release version 3.1.3 HOTFIX for 501 (#540) HOTFIX for 462 related issue, `default` on .get should be parsed as Box (#541) dependabot-preview[bot] (2): Bump mkdocs-material from 6.1.6 to 7.0.4 (#537) Bump mkdocs-material from 7.0.4 to 7.0.5 (#539) 3.1.5 (2021-08-20) ------------------ - Release version 3.1.5. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (4): Fix #595 namedtuples are no more converted to BoxList (#623) fix #596 django override (#645) fix #491 pytest django Fix #491 pytest and django (#646) Delete requirements.txt FrankBattaglia (1): fix dict iterator methods for flask DynaconfConfig (#581) 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) - Delete requirements.txt. [Bruno Rocha] - Fix #491 pytest django Fix #491 pytest and django (#646) [Bruno Rocha] - Fix #596 django override (#645) [Bruno Rocha] * Fix #596 django.test.override issue * Fix CI side effects - Add a warning if `--env` is passed to `init` (#629) [Bruno Rocha, Bruno Rocha, Robert Rosca] * Add a warning if `--env` is passed to `init` * Fix typo, `file` was doubled in init help * Update docstrings for CLI * Raise error if using `-i` with `init` subcommand * Update docs to match current behaviour * add test coverage - Fix dict iterator methods for flask DynaconfConfig (#581) [Bruno Rocha, Frank Battaglia, FrankBattaglia] - Fix #595 namedtuples are no more converted to BoxList (#623) [Bruno Rocha] - Do not search anywhere if the absolute path to a file provided (#570) [Bruno Rocha, Tanya Tereshchenko] * Do not search anywhere if the absolute path to a file provided fixes #569 * Fix test coverage and added some comments. 3.1.4 (2021-03-08) ------------------ - Release version 3.1.4. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (3): Release version 3.1.3 HOTFIX for 501 (#540) HOTFIX for 462 related issue, `default` on .get should be parsed as Box (#541) dependabot-preview[bot] (2): Bump mkdocs-material from 6.1.6 to 7.0.4 (#537) Bump mkdocs-material from 7.0.4 to 7.0.5 (#539) - HOTFIX for 462 related issue, `default` on .get should be parsed as Box (#541) [Bruno Rocha] objects In order to keep the same method api, default values should be parsed and converted to Boxed objects. https://github.com/rochacbruno/dynaconf/issues/462 - HOTFIX for 501 (#540) [Bruno Rocha] Flask still missing __contains__ - Bump mkdocs-material from 7.0.4 to 7.0.5 (#539) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 7.0.4 to 7.0.5. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/7.0.4...7.0.5) - Bump mkdocs-material from 6.1.6 to 7.0.4 (#537) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.6 to 7.0.4. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.6...7.0.4) - Release version 3.1.3. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (4): Release version 3.1.3rc1 Fix #462 make DynaBox nested List to use DynaBox as default class (#533) Fix #478 Make alias for environment -> environments (#534) Test to ensure #467 is not an issue (#535) 3.1.3 (2021-03-04) ------------------ Fix ~~~ - Environment variables filtering #470 (#474) [Michal Odnous] Other ~~~~~ - Release version 3.1.3. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (4): Release version 3.1.3rc1 Fix #462 make DynaBox nested List to use DynaBox as default class (#533) Fix #478 Make alias for environment -> environments (#534) Test to ensure #467 is not an issue (#535) - Test to ensure #467 is not an issue (#535) [Bruno Rocha] Closes #467 - Fix #478 Make alias for environment -> environments (#534) [Bruno Rocha] This is a common mistake to pass `environment` so it is alias. Fix #478 - Fix #462 make DynaBox nested List to use DynaBox as default class (#533) [Bruno Rocha] Fix #462 - Release version 3.1.3rc1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (11): Release version 3.1.2 Fix #445 casting on dottet get. (#446) Fix docs regarding --django argument on cli (#477) Fix #521 - FlaskDynaconf should raise KeyError for non existing keys (#522) Case insensitive envvar traversal (#524) Allow load_file to accept pathlib.Path (#525) Allow Title case lookup and validation. (#526) Fix #482 - formatter case insensitive (#527) Fix #449 - Django lazy templating Fix #449 (#528) Added a test to reproduce #492 (not able to reproduce) (#530) Fix #511 allow user to specify loaders argument to execute_loaders (#531) FrankBattaglia (1): Specify flask extension initializers by entry point object reference (#456) Ilito Torquato (3): fix merging hyperlink to fix #454 (#458) Changed enabled_core_loaders elements to be upper case to fix #455 (#457) Fix doc secrets from vault #403 (#459) Marcelo Lino (1): Add __contains__ to Dynaconf (#502) Michal Odnous (1): Fix: Environment variables filtering #470 (#474) dependabot-preview[bot] (5): Bump mkdocs-material from 6.0.2 to 6.1.0 (#453) Bump mkdocs-git-revision-date-localized-plugin from 0.5.2 to 0.7.3 (#463) Bump mkdocs-material from 6.1.0 to 6.1.5 (#473) Bump mkdocs-versioning from 0.2.1 to 0.3.1 (#475) Bump mkdocs-material from 6.1.5 to 6.1.6 (#476) mirrorrim (1): Fix reading secret from Vault kv v2 (#483) (#487) - Fix #511 allow user to specify loaders argument to execute_loaders (#531) [Bruno Rocha] Fix #511 ```py settings.execute_loaders(loaders=[dynaconf.loaders.env_loader]) ``` - Added a test to reproduce #492 (not able to reproduce) (#530) [Bruno Rocha] I can't reproduce the bug #492 but I added a test to ensure. - Fix #449 - Django lazy templating Fix #449 (#528) [Bruno Rocha] * Fix django laxy templates fix #449 * Delete unused files * Fix LOADERS enabling - Fix #482 - formatter case insensitive (#527) [Bruno Rocha] * Fix #482 - formatter using both upper and lowercase access Fix #482 * add more testes covering nested formatting - Allow Title case lookup and validation. (#526) [Bruno Rocha] Fix #486 - Allow load_file to accept pathlib.Path (#525) [Bruno Rocha] * Allow load_file to accept pathlib.Path Fix #494 * python 3.6 can't handle Pathlib base path addition to os.path - Case insensitive envvar traversal (#524) [Bruno Rocha] * Envvar traversal is now case insensitive - Fix #519 and fix #516 Fix #519 Fix #516 Now `export DYNACONF_FOO__bar__zaz` is the same as `DYNACONF_FOO__BAR__ZAZ` > first level prefix still needs to be uppercase! Added a warning about django to the docs. * Add functional test for issue #519 - Fix #521 - FlaskDynaconf should raise KeyError for non existing keys (#522) [Bruno Rocha] * Fix #521 - FlaskDynaconf should raise KeyError for non existing keys * Test coverage got dotted get - Add __contains__ to Dynaconf (#502) [Marcelo Lino, Marcelo Lino] * Add __contains__ to Dynaconf * Add contains assert for flask test * Remove duplicated contains from dynaconf - Fix reading secret from Vault kv v2 (#483) (#487) [Alexey Tylindus, mirrorrim] - Fix docs regarding --django argument on cli (#477) [Bruno Rocha] fix #465 fix #451 - Bump mkdocs-material from 6.1.5 to 6.1.6 (#476) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.5 to 6.1.6. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.5...6.1.6) - Bump mkdocs-versioning from 0.2.1 to 0.3.1 (#475) [dependabot- preview[bot]] Bumps [mkdocs-versioning](https://github.com/zayd62/mkdocs-versioning) from 0.2.1 to 0.3.1. - [Release notes](https://github.com/zayd62/mkdocs-versioning/releases) - [Commits](https://github.com/zayd62/mkdocs-versioning/compare/0.2.1...0.3.1) - Bump mkdocs-material from 6.1.0 to 6.1.5 (#473) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.0 to 6.1.5. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.0...6.1.5) - Bump mkdocs-git-revision-date-localized-plugin from 0.5.2 to 0.7.3 (#463) [dependabot-preview[bot]] Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 0.5.2 to 0.7.3. - [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases) - [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v0.5.2...v0.7.3) - Fix doc secrets from vault #403 (#459) [Bruno Rocha, Ilito Torquato, Ilito Torquato] * Fix secrets`s doc at Using Vault Server session * Fix secrets`s doc at Using Vault Server session * Revert "Fix secrets`s doc at Using Vault Server session" This reverts commit c47cd986bf089b3528e5c0e7c5a914cb7c1e69c8. - Changed enabled_core_loaders elements to be upper case to fix #455 (#457) [Bruno Rocha, Ilito Torquato, Ilito Torquato] * Changed enabled_core_loaders elements to be upper case to fix #455 * Change map to list comprehension and create empty [] as default value * fix wrong indentation - Fix merging hyperlink to fix #454 (#458) [Ilito Torquato, Ilito Torquato] - Specify flask extension initializers by entry point object reference (#456) [FrankBattaglia] - Bump mkdocs-material from 6.0.2 to 6.1.0 (#453) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.0.2 to 6.1.0. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.0.2...6.1.0) - Fix #445 casting on dottet get. (#446) [Bruno Rocha] * Fix #445 casting on dottet get. Fix the rebound of `cast` on dotted get. Fix #445 * better handling of casting data - Release version 3.1.2. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (13): Release version 3.1.1 Update diagram images Update docs/release_notes Fixing prospector warnings. (#425) Fix mkdocs config problem found in #423 Signed in for https://xscode.com/rochacbruno/dynaconf (#426) Remove links to outdated issues from guidelines Fix colors and KEyError handling on cli.py (#429) Fix #434 setenv failing to unset LazyValues (#437) Fix #432 no need for warning when env is missing on a file (#438) Add test to ensure fix #430 (#439) Close #284 not a bug (#440) Fix #443 object merge with same value on same level keys (#444) dependabot-preview[bot] (6): Bump mkdocs-material from 5.3.2 to 5.5.13 (#423) Bump pymdown-extensions from 7.1 to 8.0 (#422) Bump mkdocs-material-extensions from 1.0 to 1.0.1 (#427) Bump pymdown-extensions from 8.0 to 8.0.1 (#435) Bump mkdocs-material from 5.5.13 to 6.0.1 (#436) Bump mkdocs-material from 6.0.1 to 6.0.2 (#442) 3.1.2 (2020-10-08) ------------------ - Release version 3.1.2. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (13): Release version 3.1.1 Update diagram images Update docs/release_notes Fixing prospector warnings. (#425) Fix mkdocs config problem found in #423 Signed in for https://xscode.com/rochacbruno/dynaconf (#426) Remove links to outdated issues from guidelines Fix colors and KEyError handling on cli.py (#429) Fix #434 setenv failing to unset LazyValues (#437) Fix #432 no need for warning when env is missing on a file (#438) Add test to ensure fix #430 (#439) Close #284 not a bug (#440) Fix #443 object merge with same value on same level keys (#444) dependabot-preview[bot] (6): Bump mkdocs-material from 5.3.2 to 5.5.13 (#423) Bump pymdown-extensions from 7.1 to 8.0 (#422) Bump mkdocs-material-extensions from 1.0 to 1.0.1 (#427) Bump pymdown-extensions from 8.0 to 8.0.1 (#435) Bump mkdocs-material from 5.5.13 to 6.0.1 (#436) Bump mkdocs-material from 6.0.1 to 6.0.2 (#442) - Fix #443 object merge with same value on same level keys (#444) [Bruno Rocha] This solution is a temporary solution as it solves current problem, but there is still the case for `None` values. The best solution for this case would be wrapping all the values on assignment and give it a full path signature to compare. - Bump mkdocs-material from 6.0.1 to 6.0.2 (#442) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.0.1...6.0.2) - Close #284 not a bug (#440) [Bruno Rocha] 284 is not a bug but a missing of explicit merge tokens - Add test to ensure fix #430 (#439) [Bruno Rocha] I could not reproduce the problem resported on #430 considering it close #430 reopen as needed. - Fix #432 no need for warning when env is missing on a file (#438) [Bruno Rocha] When env is missing on a file there is no need to output a warning. All envs are optional on files. Fix #432 - Fix #434 setenv failing to unset LazyValues (#437) [Bruno Rocha] Fix #434 - Bump mkdocs-material from 5.5.13 to 6.0.1 (#436) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.5.13 to 6.0.1. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.5.13...6.0.1) - Bump pymdown-extensions from 8.0 to 8.0.1 (#435) [dependabot- preview[bot]] Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 8.0 to 8.0.1. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/8.0...8.0.1) - Fix colors and KEyError handling on cli.py (#429) [Bruno Rocha] - Remove links to outdated issues from guidelines. [Bruno Rocha] - Bump mkdocs-material-extensions from 1.0 to 1.0.1 (#427) [dependabot- preview[bot]] Bumps [mkdocs-material-extensions](https://github.com/facelessuser/mkdocs-material-extensions) from 1.0 to 1.0.1. - [Release notes](https://github.com/facelessuser/mkdocs-material-extensions/releases) - [Changelog](https://github.com/facelessuser/mkdocs-material-extensions/blob/master/changelog.md) - [Commits](https://github.com/facelessuser/mkdocs-material-extensions/compare/1.0...1.0.1) - Signed in for https://xscode.com/rochacbruno/dynaconf (#426) [Bruno Rocha] Offering paid support for dynaconf users. - Bump pymdown-extensions from 7.1 to 8.0 (#422) [dependabot- preview[bot]] Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 7.1 to 8.0. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/7.1...8.0) - Bump mkdocs-material from 5.3.2 to 5.5.13 (#423) [dependabot- preview[bot]] Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.3.2 to 5.5.13. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.3.2...5.5.13) - Fix mkdocs config problem found in #423. [Bruno Rocha] Fix #mkdocs-material/1941 - Fixing prospector warnings. (#425) [Bruno Rocha] * Fixing prospector warnings * Used vulture to detect and remove dead code - Update docs/release_notes. [Bruno Rocha] - Update diagram images. [Bruno Rocha] - Release version 3.1.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (2): Release version 3.1.1rc6 HOTFIX: Cli now accepts dotter keys 3.1.1 (2020-09-21) ------------------ - Release version 3.1.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (2): Release version 3.1.1rc6 HOTFIX: Cli now accepts dotter keys - HOTFIX: Cli now accepts dotter keys. [Bruno Rocha] - Release version 3.1.1rc6. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (2): Release version 3.1.1rc5 Do not include vendor_src on wheel target (#420) - Do not include vendor_src on wheel target (#420) [Bruno Rocha] - Release version 3.1.1rc5. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (3): Release version 3.1.1rc4 Small fix on release script Minification of vendored modules (#419) - Minification of vendored modules (#419) [Bruno Rocha] * Minified all the vendor folder saving 50% od disk space * Add vendor_src and minify script vendor_src is not included in the build, only the results of its minification - Small fix on release script. [Bruno Rocha] Correct path for mkdocs.yml - Release version 3.1.1rc4. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (3): Release version 3.1.1rc3 HOTFIX: Add missing instruction to release.sh Added full Dynaconf Diagram and few fizes. (#418) - Added full Dynaconf Diagram and few fizes. (#418) [Bruno Rocha] - HOTFIX: Add missing instruction to release.sh. [Bruno Rocha] - Release version 3.1.1rc3. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (5): Release version 3.1.1rc2 Fix set attribute directly and fresh vars (#412) 384 fix tail and dotted merge (#415) Fix #404 no more dup message on combined validators (#417) HOTFIX 414 update docs version on release Max Winterstein (1): Fix typo in release notes (#411) Mirek Długosz (1): Fix #407 - add proper equality test for CombinedValidator (#413) - HOTFIX 414 update docs version on release. [Bruno Rocha] Fix #414 - Fix #404 no more dup message on combined validators (#417) [Bruno Rocha] - 384 fix tail and dotted merge (#415) [Bruno Rocha] * attempt to fix tail call on object_merge Fix #384 * Fix list and dict merge issues - Fix typo in release notes (#411) [Bruno Rocha, Max Winterstein] - Fix #407 - add proper equality test for CombinedValidator (#413) [Mirek Długosz] * Fix #407 - add proper equality test for CombinedValidator * Update after review - Fix set attribute directly and fresh vars (#412) [Bruno Rocha] * Fix set attribute directly and fresh vars Fix #253 Fix #395 * No need to check for default_settings in setattr - Release version 3.1.1rc2. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (2): Release version 3.1.1rc1 HOTFIX: Logging instance has a `formatter` attribute (#410) - HOTFIX: Logging instance has a `formatter` attribute (#410) [Bruno Rocha] Dynaconf was trying to detect a lazy value by the existence of `formatter` attribute but in Django when the value is a logging it has `.formatter` attribute. - Release version 3.1.1rc1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (10): Release version 3.1.0 Create FUNDING.yml Fix #391 make box_settings optional, change vendoring strategy (#398) HOTFIX: Add missing vendor.txt Allow nested Lazy Values (#405) Makes PEP8 more strictly and remove unused variables (#408) Merge branch 'master' into vault boto is optional Merge branch 'vault' into master Included example of custom SOPS loader to the docs Christoph Schmatzler (1): Fix typo in Validation docs (#394) Gabriel Simonetto (1): Fix #399 - Update documentation link (#401) Jiranun Jiratrakanvong (1): Add auth username and password for redis settings (#378) Martijn Pieters (1): Correct typos in documentation and README (#400) Mirek Długosz (1): Test all names in Validator("foo", "bar", must_exist=False) (#406) Nikolai Bessonov (1): fix a typo (#393) Peng Yin (5): Read all secrets under a vault path Add option to auth vault with iam role Fix format Fix test for versioned kv engine in latest vault Merge branch 'master' into vault whg517 (1): docs: Fixed filename error in the case of the index page (#396) - Included example of custom SOPS loader to the docs. [Bruno Rocha] - Add auth username and password for redis settings (#378) [Bruno Rocha, Jiranun Jiratrakanvong, Jiranun Jiratrakanvong] - Merge branch 'vault' into master. [Bruno Rocha] - Boto is optional. [Bruno Rocha] - Merge branch 'master' into vault. [Bruno Rocha] - Fix a typo (#393) [Bruno Rocha, Nikolai Bessonov] - Fix typo in Validation docs (#394) [Bruno Rocha, Christoph Schmatzler] - Correct typos in documentation and README (#400) [Bruno Rocha, Martijn Pieters] * Correct minor documentation typo in the Dynamic Variables section. * Fix typos throughout the docs - Docs: Fixed filename error in the case of the index page (#396) [Bruno Rocha, whg517] - Fix #399 - Update documentation link (#401) [Bruno Rocha, Gabriel Simonetto] - Makes PEP8 more strictly and remove unused variables (#408) [Bruno Rocha] - Test all names in Validator("foo", "bar", must_exist=False) (#406) [Mirek Długosz] `Validator(must_exist=False)` incorrectly checked first name only. Given settings.yaml: bar: some_value `Validator("foo", "bar", must_exist=False)` would **not** raise ValidationError - it would return after checking that first name indeed is not defined. - Allow nested Lazy Values (#405) [Bruno Rocha] Fix #392 Fix #402 - Merge branch 'master' into vault. [Peng Yin] - HOTFIX: Add missing vendor.txt. [Bruno Rocha] - Fix #391 make box_settings optional, change vendoring strategy (#398) [Bruno Rocha] - Revert DynaBox box_settings to be optional - Change vendoring strategy - instead of hacking sys.modules, using abs paths - Pin to Box 4.2.2 without conflicting with system installed box - Added a Django example on tests to fix @daviddavis reported issue - Fix test for versioned kv engine in latest vault. [Peng Yin] - Fix format. [Peng Yin] - Add option to auth vault with iam role. [Peng Yin] - Read all secrets under a vault path. [Peng Yin] - Create FUNDING.yml. [Bruno Rocha] - Release version 3.1.0. [Bruno Rocha] Shortlog of commits since last release: Andreas Poehlmann (1): Allow importing SEARCHTREE before settings are configured (#383) Bruno Rocha (10): Release version 3.0.0 Hot fix removing unused imports Merge branch 'master' of github.com:rochacbruno/dynaconf Removing invalid links, adding allert on old docs fix #369 and fix #371 (#372) Fix #359 lazy template substitution on nested keys (#375) Flask fizes and other issues included. (#376) Fix #379 dict like iteration (#385) Fix #377 allow computed values (#386) Fix #388 URL reference for custom loaders (#389) Fix #382 add is_overridden method (#390) John Vandenberg (1): Allow testing against local redis server (#387) Piotr Baniukiewicz (1): Fix validation of optional fields (#370) 3.1.0 (2020-08-14) ------------------ - Release version 3.1.0. [Bruno Rocha] Shortlog of commits since last release: Andreas Poehlmann (1): Allow importing SEARCHTREE before settings are configured (#383) Bruno Rocha (10): Release version 3.0.0 Hot fix removing unused imports Merge branch 'master' of github.com:rochacbruno/dynaconf Removing invalid links, adding allert on old docs fix #369 and fix #371 (#372) Fix #359 lazy template substitution on nested keys (#375) Flask fizes and other issues included. (#376) Fix #379 dict like iteration (#385) Fix #377 allow computed values (#386) Fix #388 URL reference for custom loaders (#389) Fix #382 add is_overridden method (#390) John Vandenberg (1): Allow testing against local redis server (#387) Piotr Baniukiewicz (1): Fix validation of optional fields (#370) - Allow importing SEARCHTREE before settings are configured (#383) [Andreas Poehlmann] - Allow testing against local redis server (#387) [John Vandenberg] - Fix #382 add is_overridden method (#390) [Bruno Rocha] Fix #382 add is_overridden method for DJDT - Fix #388 URL reference for custom loaders (#389) [Bruno Rocha] Fix #388 URL reference for custom loaders - Fix #377 allow computed values (#386) [Bruno Rocha] This fixes #377 by allowing Validator to provide default values. - Fix #379 dict like iteration (#385) [Bruno Rocha] * Fix #379 add missing __iter__ and items * Fix docs - Flask fizes and other issues included. (#376) [Bruno Rocha] Fix #323 Fix #325 Fix #327 Fix #341 Examples added: example/issues/323_DEFAULT_VALUES_RESOLUTION/ example/issues/325_flask_dot_env/ example/issues/327_flask_extensions_warning/ example/issues/341_box_it_up/ - Fix #359 lazy template substitution on nested keys (#375) [Bruno Rocha] - Removing invalid links, adding allert on old docs fix #369 and fix #371 (#372) [Bruno Rocha] - Merge branch 'master' of github.com:rochacbruno/dynaconf. [Bruno Rocha] - Fix validation of optional fields (#370) [Bruno Rocha Co-authored-by: Bruno Rocha , Piotr Baniukiewicz] * Fix validation of optional fields * More concise code - Hot fix removing unused imports. [Bruno Rocha] - Release version 3.0.0. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (5): Release version 3.0.0rc2 Improvements on CLI and init command (#363) Writing new docs page 1 (#364) Add netlify (#366) Add netlify runtime file... 3.0.0 (2020-06-29) ------------------ - Release version 3.0.0. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (5): Release version 3.0.0rc2 Improvements on CLI and init command (#363) Writing new docs page 1 (#364) Add netlify (#366) Add netlify runtime file... - Add netlify runtime file... [Bruno Rocha] - Add netlify (#366) [Bruno Rocha] - Writing new docs page 1 (#364) [Bruno Rocha] * porting docs to mkdocs * Docs First Page * New docs ok - Improvements on CLI and init command (#363) [Bruno Rocha] - Release version 3.0.0rc2. [Bruno Rocha] Shortlog of commits since last release: Bernardo Gomes (2): Adding f string (#319) Added little information about how dev into this project. (#321) Bruno Rocha (18): Release version 3.0.0rc1 Better exception handling on env_loader (#316) Add support for config aliases (#332) Add ENVLESS_MODE (#337) Fix #272 allow access of lowercase keys (#338) Fix #298 allow auto complete for editors and console (#339) Vendoring dependencies Fix #301 (#345) Clean tox installation for local testing (#346) Validator improvements on conditions (#353) Add note about quoting in env vars (#347) DEPRECATED global settings object. DEPRECATED global settings object. (#356) Lowecase read allowed by default (#357) Merge branch 'master' of github.com:rochacbruno/dynaconf envless by default - breaking change ⚠️ (#358) dotenv is no more loaded by default (#360) No more loading of `settings.*` by default (#361) NO more logger and debug messages (#362) Douglas Maciel d'Auriol Souza (1): Insert news validator conditions: (len_eq, len_ne, len_min, len_max, contd) (#328) Jeff Wayne (1): s/DYNACONF_ENV/ENV_FOR_DYNACONF (#335) Marcos Benevides (1): Fix minor typo in Flask extension docs (#318) Nicholas Nadeau, Ph.D., P.Eng (1): Fixed comma typo (#334) sfunkhouser (1): Add option to override default mount_point for vault (#349) - NO more logger and debug messages (#362) [Bruno Rocha] * logger and DEBUG_LEVEL has gone. * Add logger as a backwards compat method - No more loading of `settings.*` by default (#361) [Bruno Rocha] - Dotenv is no more loaded by default (#360) [Bruno Rocha] - Envless by default - breaking change ⚠️ (#358) [Bruno Rocha] * ⚠️ Turning the default to be the envless mode (this is breaking change) ⚠️ ⚠️ THIS IS BREAKING CHANGE ⚠️ * envless by default is done * Fix redis and vault tests * CLI default to global instance with warnings - Merge branch 'master' of github.com:rochacbruno/dynaconf. [Bruno Rocha] - Lowecase read allowed by default (#357) [Bruno Rocha] * DEPRECATED global settings object. No more `from dynaconf import settings` * Lower case first level keys are now allowed by default - DEPRECATED global settings object. (#356) [Bruno Rocha] No more `from dynaconf import settings` - DEPRECATED global settings object. [Bruno Rocha] No more `from dynaconf import settings` - Add note about quoting in env vars (#347) [Bruno Rocha] - Validator improvements on conditions (#353) [Bruno Rocha] * Validators improvements * add cast argument to validators - Add option to override default mount_point for vault (#349) [sfunkhouser] - Clean tox installation for local testing (#346) [Bruno Rocha] - Vendoring dependencies Fix #301 (#345) [Bruno Rocha] - Fix #298 allow auto complete for editors and console (#339) [Bruno Rocha] implemented `__dir__` on Settings and Dynabox - Fix #272 allow access of lowercase keys (#338) [Bruno Rocha] - `settings.lowercase_key` is allowed - `settings.dynaconf` is a proxy to internal methods - `settings.__reserved_attributes` validates key names - `LazySettings __init__ parameters can receive lower case configs` - Add ENVLESS_MODE (#337) [Bruno Rocha] - S/DYNACONF_ENV/ENV_FOR_DYNACONF (#335) [Jeff Wayne] - Fixed comma typo (#334) [Nicholas Nadeau, Ph.D., P.Eng] - Add support for config aliases (#332) [Bruno Rocha] All _FOR_DYNACONF can now be aliased when passing to LazySettings. - Insert news validator conditions: (len_eq, len_ne, len_min, len_max, contd) (#328) [Bruno Rocha, Douglas Maciel d'Auriol Souza] * Insert news validator conditions: len_eq, len_ne, len_min, len_max, contd * Insert news validator conditions: len_eq, len_ne, len_min, len_max, contd * Update validator_conditions.py * Update test_validators_conditions.py * Checked: Flake8 * Black sugest * Change of the term contd to cont, in order to avoid false interpretation. - Better exception handling on env_loader (#316) [Bruno Rocha] - Added little information about how dev into this project. (#321) [Bernardo Gomes] - Adding f string (#319) [Bernardo Gomes] * First test to change to f-string * second change to f-string * Removed 95% of .format( * Removed % from code. * forget format. * Fixing flaked reports. * Fixing flaked reports-v2. * make run-pre-commit command executed. * Little bugfix f of f-string inside of the string. - Fix minor typo in Flask extension docs (#318) [Marcos Benevides] - Release version 3.0.0rc1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (8): Release version 2.2.3 Changed text format and fixed tests Merge branch '304-ShowDataTypeListCli' Fix issue #305 - printing and exporting LazyFormat (#312) Fix #288 - Nullable values (#300) Default to ruamel.yaml when it is available. (#313) Fix #306 - does not defaults to merge, deprecated reset - [Breaking Change] (#315) HOTFIX - tox.ini drops 3.5 Tiago Cordeiro (1): Added OSX builds to the Azure Pipeline (#307) Vicente Marçal (1): Changed CLI list to show data type of the envvars to fix #304 dependabot-preview[bot] (1): Unpinning python-box, removing box_it_up and default_box arguments (#279) - HOTFIX - tox.ini drops 3.5. [Bruno Rocha] - Fix #306 - does not defaults to merge, deprecated reset - [Breaking Change] (#315) [Bruno Rocha] - Don't default to `merge` for `__` variables - Made `@merge` more explicit and smart - Deprecated `@reset` - Unpinning python-box, removing box_it_up and default_box arguments (#279) [Bruno Rocha, dependabot-preview[bot]] - Default to ruamel.yaml when it is available. (#313) [Bruno Rocha] - Fix #288 - Nullable values (#300) [Bruno Rocha] * Attempt to fix #288 (needs more debugging) * Fixing bug on DynaBox.get - Fix issue #305 - printing and exporting LazyFormat (#312) [Bruno Rocha] - Merge branch '304-ShowDataTypeListCli' [Bruno Rocha] - Changed text format and fixed tests. [Bruno Rocha] - Changed CLI list to show data type of the envvars to fix #304. [Vicente Marçal] - Added OSX builds to the Azure Pipeline (#307) [Tiago Cordeiro] * Added OSX builds to the Azure Pipeline * Added OSX builds to the Azure Pipeline * skip docker tests on macOS - Release version 2.2.3. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (7): Release version 2.2.2 Fix #273 add Flask load extensions method. add t.me badge fix #262 Fix #145 allow lazy format using os.environ and settings values. Overriding strategy test Fix #203 document the usage with pytest (with examples) unpin dependencies Hildeberto (2): Fix pre-commit to run python3 rather than python3.7 Merge pull request #281 from hilam/fix_pre_commit JSP (1): fix object_merge issue #285 with meta value dependabot-preview[bot] (2): Update python-dotenv requirement from <=0.10.3 to <0.10.6 Update python-dotenv requirement from <0.10.6 to <0.11.1 2.2.3 (2020-02-28) ------------------ - Release version 2.2.3. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (7): Release version 2.2.2 Fix #273 add Flask load extensions method. add t.me badge fix #262 Fix #145 allow lazy format using os.environ and settings values. Overriding strategy test Fix #203 document the usage with pytest (with examples) unpin dependencies Hildeberto (2): Fix pre-commit to run python3 rather than python3.7 Merge pull request #281 from hilam/fix_pre_commit JSP (1): fix object_merge issue #285 with meta value dependabot-preview[bot] (2): Update python-dotenv requirement from <=0.10.3 to <0.10.6 Update python-dotenv requirement from <0.10.6 to <0.11.1 - Unpin dependencies. [Bruno Rocha] - Update python-dotenv requirement from <0.10.6 to <0.11.1. [dependabot- preview[bot]] Updates the requirements on [python-dotenv](https://github.com/theskumar/python-dotenv) to permit the latest version. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v0.1.1...v0.11.0) - Fix #203 document the usage with pytest (with examples) [Bruno Rocha] - Overriding strategy test. [Bruno Rocha] - Fix #145 allow lazy format using os.environ and settings values. [Bruno Rocha] - Add t.me badge fix #262. [Bruno Rocha] - Fix #273 add Flask load extensions method. [Bruno Rocha] - This commit adds a new method `load_extensions` to `FlaskDynaconf` class. - Update python-dotenv requirement from <=0.10.3 to <0.10.6. [dependabot-preview[bot]] Updates the requirements on [python-dotenv](https://github.com/theskumar/python-dotenv) to permit the latest version. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v0.1.1...v0.10.5) - Fix object_merge issue #285 with meta value. [JSP] - Merge pull request #281 from hilam/fix_pre_commit. [Hildeberto] Fix pre-commit to run python3 rather than python3.7 - Fix pre-commit to run python3 rather than python3.7. [Hildeberto] - Release version 2.2.2. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (3): Release version 2.2.1 Fix #258 custom message for validators Pin python-box version because of a breaking release Hildeberto (1): Close #178. Included integration tests redis/vault 2.2.2 (2019-12-26) ------------------ - Release version 2.2.2. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (3): Release version 2.2.1 Fix #258 custom message for validators Pin python-box version because of a breaking release Hildeberto (1): Close #178. Included integration tests redis/vault - Pin python-box version because of a breaking release. [Bruno Rocha] The release of python-box https://github.com/cdgriffith/Box/pull/116 is a breaking change. So pinning this until this project adapts. Also pinning other direct deps. - Fix #258 custom message for validators. [Bruno Rocha] - Close #178. Included integration tests redis/vault. [Hildeberto] - Release version 2.2.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (4): Release version 2.2.0 Fix #251 recursive call was using mutable memoized data (#254) Fix #266 created new variable FORCE_ENV to override ENV_FOR_DYNACONF Fix coverage for validators David Moreau Simard (1): Add ara as a user of dynaconf (#252) Emmanuel Nosa Evbuomwan (1): Update sensitive_secrets.md Hildeberto (1): Adjust remote upstream URL Jan Willhaus (1): Add support for detecting duplicate validators being added (and ignore them) (#256) Oliver Lehmann (5): fix: env_loader.write: quote_mode for non-string values : added line break fix str comparison changing quote logic fix open error @py3.5 2.2.1 (2019-12-06) ------------------ Fix ~~~ - Env_loader.write: quote_mode for non-string values. [Oliver Lehmann] Other ~~~~~ - Release version 2.2.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (4): Release version 2.2.0 Fix #251 recursive call was using mutable memoized data (#254) Fix #266 created new variable FORCE_ENV to override ENV_FOR_DYNACONF Fix coverage for validators David Moreau Simard (1): Add ara as a user of dynaconf (#252) Emmanuel Nosa Evbuomwan (1): Update sensitive_secrets.md Hildeberto (1): Adjust remote upstream URL Jan Willhaus (1): Add support for detecting duplicate validators being added (and ignore them) (#256) Oliver Lehmann (5): fix: env_loader.write: quote_mode for non-string values : added line break fix str comparison changing quote logic fix open error @py3.5 - Fix coverage for validators. [Bruno Rocha] - Fix #266 created new variable FORCE_ENV to override ENV_FOR_DYNACONF. [Bruno Rocha] - Adjust remote upstream URL. [Hildeberto] - Update sensitive_secrets.md. [Emmanuel Nosa Evbuomwan] Updated the file reference from `settings`.toml{json|py|ini|yaml} to the convention used thus far; `secrets`.toml{json|py|ini|yaml}. This can help alleviate the slightest chance of the information becoming misleading or confusing. This can also be ignored if Dynaconf can be set to search for secrets in files other than `secrets.` - Fix open error @py3.5. [Oliver Lehmann] - Changing quote logic. [Oliver Lehmann] - Fix str comparison. [Oliver Lehmann] - : added line break. [Oliver Lehmann] - Add support for detecting duplicate validators being added (and ignore them) (#256) [Jan Willhaus] - Fix #251 recursive call was using mutable memoized data (#254) [Bruno Rocha] replaced with recursive passing of parent data. NOTE to SELF: Never! use a mutable memoized data Always use `==` to compare when you dont know the types - Add ara as a user of dynaconf (#252) [David Moreau Simard] - Release version 2.2.0. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (5): Release version 2.1.1 Fix #236 added .local. files loading and module impersonation docs (#239) Replace key.upper with `upperfy` function that keeps `__` attributes (#240) Fix #241 new merge standards (#243) Add support for PRELOAD_ setting. (#244) Kedar Kulkarni (1): Fixing how filename.local.* files are loaded (#238) paskozdilar (1): fix crash on empty settings (#242) 2.2.0 (2019-10-09) ------------------ - Release version 2.2.0. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (5): Release version 2.1.1 Fix #236 added .local. files loading and module impersonation docs (#239) Replace key.upper with `upperfy` function that keeps `__` attributes (#240) Fix #241 new merge standards (#243) Add support for PRELOAD_ setting. (#244) Kedar Kulkarni (1): Fixing how filename.local.* files are loaded (#238) paskozdilar (1): fix crash on empty settings (#242) - Add support for PRELOAD_ setting. (#244) [Bruno Rocha] - Fix #241 new merge standards (#243) [Bruno Rocha] Adds dynaconf_merge and @merge for better merge standards. ref #241 - Fix crash on empty settings (#242) [paskozdilar] * fix crash on empty settings * add test for empty environment * fix PEP 8 issue (expected 2 blank lines, found 1) - Replace key.upper with `upperfy` function that keeps `__` attributes (#240) [Bruno Rocha] - Fix #236 added .local. files loading and module impersonation docs (#239) [Bruno Rocha] also MERGE_ENABLED is no more deprecated. - Fixing how filename.local.* files are loaded (#238) [Kedar Kulkarni] - Release version 2.1.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (7): Release version 2.1.0 Improve validators to use `from_env` method (#225) Add info about dunder envvars on django.md docs guide (#226) Fix #228 add `ignore` argument to Django explicit mode. (#229) Improvement to close #230 - do not throw error for base envs. (#231) dynaconf init will not write all possible envs, only [default] (#233) When both enabled, Vault has the priority over Redis for overriding (#234) Dave Barnow (1): Fix typo in CLI init (#227) Kedar Kulkarni (1): Fixing self._root_path to fall back to os.getcwd() only when `settings.load_file` is called directly or from includes (#232) 2.1.1 (2019-09-16) ------------------ - Release version 2.1.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (7): Release version 2.1.0 Improve validators to use `from_env` method (#225) Add info about dunder envvars on django.md docs guide (#226) Fix #228 add `ignore` argument to Django explicit mode. (#229) Improvement to close #230 - do not throw error for base envs. (#231) dynaconf init will not write all possible envs, only [default] (#233) When both enabled, Vault has the priority over Redis for overriding (#234) Dave Barnow (1): Fix typo in CLI init (#227) Kedar Kulkarni (1): Fixing self._root_path to fall back to os.getcwd() only when `settings.load_file` is called directly or from includes (#232) - When both enabled, Vault has the priority over Redis for overriding (#234) [Bruno Rocha] - Dynaconf init will not write all possible envs, only [default] (#233) [Bruno Rocha] - Fixing self._root_path to fall back to os.getcwd() only when `settings.load_file` is called directly or from includes (#232) [Kedar Kulkarni] - Improvement to close #230 - do not throw error for base envs. (#231) [Bruno Rocha] - Fix #228 add `ignore` argument to Django explicit mode. (#229) [Bruno Rocha] - Fix typo in CLI init (#227) [Dave Barnow] - Add info about dunder envvars on django.md docs guide (#226) [Bruno Rocha] - Improve validators to use `from_env` method (#225) [Bruno Rocha] - Release version 2.1.0. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (8): Release version 2.0.4 Merge branch 'dgarcia360-master' Fix #197 add support for DOTTED__ENV__VARS (#215) Add support to export merged env to filesystem via cli. (#217) Adds `from_env` method and change `_store` to be a `DynaBox` (#219) hotfix: next release will be 2.1.0 because new features added. (#220) Fix `make test_examples` to use better assertions, redis and vault loader now respects `envs` (#222) fix #221 removed JSON,YAML,INI,TOML cosntants from default_settings (#223) Kedar Kulkarni (1): Add `list_envs` function to vault loader and now envs can have `_` on its name. Pavel Alimpiev (1): Fix typo in documentation for a Validator class (#213) dgarcia360 (3): Updated configuration options table to csv table Added responsive table fix Fix format 2.1.0 (2019-09-05) ------------------ - Release version 2.1.0. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (8): Release version 2.0.4 Merge branch 'dgarcia360-master' Fix #197 add support for DOTTED__ENV__VARS (#215) Add support to export merged env to filesystem via cli. (#217) Adds `from_env` method and change `_store` to be a `DynaBox` (#219) hotfix: next release will be 2.1.0 because new features added. (#220) Fix `make test_examples` to use better assertions, redis and vault loader now respects `envs` (#222) fix #221 removed JSON,YAML,INI,TOML cosntants from default_settings (#223) Kedar Kulkarni (1): Add `list_envs` function to vault loader and now envs can have `_` on its name. Pavel Alimpiev (1): Fix typo in documentation for a Validator class (#213) dgarcia360 (3): Updated configuration options table to csv table Added responsive table fix Fix format - Fix #221 removed JSON,YAML,INI,TOML cosntants from default_settings (#223) [Bruno Rocha] Default settings should hold only constants ending in _FOR_DYNACONF - Fix `make test_examples` to use better assertions, redis and vault loader now respects `envs` (#222) [Bruno Rocha] - Hotfix: next release will be 2.1.0 because new features added. (#220) [Bruno Rocha] - Adds `from_env` method and change `_store` to be a `DynaBox` (#219) [Bruno Rocha] - Add `list_envs` function to vault loader and now envs can have `_` on its name. [Kedar Kulkarni] * Adding new feature to address issue #211 `list_envs ` function on vault loader * Removing restriction with env cannot contain underscore chars - Add support to export merged env to filesystem via cli. (#217) [Bruno Rocha] fix #200 ```bash dynaconf list -o path/to/file.yaml --output-flat ``` - Fix #197 add support for DOTTED__ENV__VARS (#215) [Bruno Rocha] * Fix #197 add support for DOTTED__ENV__VARS * Full support for `__` - @reset and @del markers - Merge branch 'dgarcia360-master' [Bruno Rocha] - Fix format. [dgarcia360] - Added responsive table fix. [dgarcia360] - Updated configuration options table to csv table. [dgarcia360] - Fix typo in documentation for a Validator class (#213) [Pavel Alimpiev] - Release version 2.0.4. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (2): Release version 2.0.3 Fix #207 allow python module path name for includes (#209) Michał Bartoszkiewicz (1): Update usage.md (#208) Pavel Alimpiev (2): Refactor Vault integration (#202) Update configuration.md (#205) Tanveer Alam (2): Update usage.md (#196) Update usage.md (#195) 2.0.4 (2019-08-22) ------------------ - Release version 2.0.4. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (2): Release version 2.0.3 Fix #207 allow python module path name for includes (#209) Michał Bartoszkiewicz (1): Update usage.md (#208) Pavel Alimpiev (2): Refactor Vault integration (#202) Update configuration.md (#205) Tanveer Alam (2): Update usage.md (#196) Update usage.md (#195) - Fix #207 allow python module path name for includes (#209) [Bruno Rocha] - Update usage.md (#208) [Michał Bartoszkiewicz] Change 'FLask' to 'Flask' - Update configuration.md (#205) [Pavel Alimpiev] - Refactor Vault integration (#202) [Pavel Alimpiev] * Add AppRole based authorization for Vault loader * Fix default value for VAULT_PATH_FOR_DYNACONF, Update docs * HVAC automatically adds /secret/ prefix on read and write access * /dynaconf was never added to the VAULT_PATH_FOR_DYNACONF value * Docs was inconsistent with the actual code base * Fix inconsistency in the docs * Remove VAULT_SESSION_FOR_DYNACONF config variable. * HVAC's session argument must be a fully initialized Session object, that means - it's very complicated to setup Vault client with this argument, via default instruments (.toml, .env, etc) * Users can still setup this argument by setting up VAULT_FOR_DYNACONF directly * Update documentation for VAULT_* configuration * Fix code style - Update usage.md (#195) [Tanveer Alam] - Update usage.md (#196) [Tanveer Alam] - Release version 2.0.3. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (2): Release version 2.0.2 Fix #194 flask.app.config __setitem__ (#199) Jan Willhaus (1): Catch BoxKeyError when contents are TOML parsable but not keyable (#192) Raoul Snyman (1): Use the Key Value API rather than the old 'read' and 'write' methods (#198) 2.0.3 (2019-06-27) ------------------ - Release version 2.0.3. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (2): Release version 2.0.2 Fix #194 flask.app.config __setitem__ (#199) Jan Willhaus (1): Catch BoxKeyError when contents are TOML parsable but not keyable (#192) Raoul Snyman (1): Use the Key Value API rather than the old 'read' and 'write' methods (#198) - Fix #194 flask.app.config __setitem__ (#199) [Bruno Rocha] Flask.config was not proxying __setitem__ attribute so this change adds a call to __setitem__ on contrib/flask_dynaconf - Use the Key Value API rather than the old 'read' and 'write' methods (#198) [Raoul Snyman] - Catch BoxKeyError when contents are TOML parsable but not keyable (#192) [Jan Willhaus] - Release version 2.0.2. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (8): Release version 2.0.1 Add note to release script Adhering to Github Community Standards (#175) removed pytest-xdist (#181) Add example and test for issue #182 (#183) Fix #179 dynaconf cli shows only user defined vars unless -a used (#188) Fix #184 - workdir should walk to root in ipython REPL (#190) Fix #189 added `settings.as_dict()` and `dynaconf list -o file.json` (#191) Jan Willhaus (4): Fix `False` not being an acceptable env (#176) Fix base loader when having no ENVVAR_PREFIX_ (Addresses #177) (#185) Hide DeprecationWarning from Pytest when testing for them (#186) Replace logging.basicConfig with handler on logger (#187) 2.0.2 (2019-04-29) ------------------ - Release version 2.0.2. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (8): Release version 2.0.1 Add note to release script Adhering to Github Community Standards (#175) removed pytest-xdist (#181) Add example and test for issue #182 (#183) Fix #179 dynaconf cli shows only user defined vars unless -a used (#188) Fix #184 - workdir should walk to root in ipython REPL (#190) Fix #189 added `settings.as_dict()` and `dynaconf list -o file.json` (#191) Jan Willhaus (4): Fix `False` not being an acceptable env (#176) Fix base loader when having no ENVVAR_PREFIX_ (Addresses #177) (#185) Hide DeprecationWarning from Pytest when testing for them (#186) Replace logging.basicConfig with handler on logger (#187) - Fix #189 added `settings.as_dict()` and `dynaconf list -o file.json` (#191) [Bruno Rocha] - Fix #184 - workdir should walk to root in ipython REPL (#190) [Bruno Rocha] - Fix #179 dynaconf cli shows only user defined vars unless -a used (#188) [Bruno Rocha] Command `dynaconf list` will show only user defined vars IF `--all|-a` is passed then it includes internal variables. - Replace logging.basicConfig with handler on logger (#187) [Jan Willhaus] - Hide DeprecationWarning from Pytest when testing for them (#186) [Jan Willhaus] * Hide DeprecationWarnings from Pytest when testing for them * Use parametrized test instead of repeating code - Fix base loader when having no ENVVAR_PREFIX_ (Addresses #177) (#185) [Jan Willhaus] * Fix `False` not being an acceptable env * Additional testcase for prefix being false from envvar * Fix mistaken reference to ENVVAR_PREFIX * Fix typo - Add example and test for issue #182 (#183) [Bruno Rocha] * Add working example for issue 182 * Option 2 added * Allowed `settings.load_file` programmatically - Removed pytest-xdist (#181) [Bruno Rocha] Now tests run in a separate tmpdir so xdist is not needed anymore - Fix `False` not being an acceptable env (#176) [Jan Willhaus] * Fix `False` not being an acceptable env * Additional testcase for prefix being false from envvar * unset envvar_prefix after test - Adhering to Github Community Standards (#175) [Bruno Rocha] - Add note to release script. [Bruno Rocha] - Release version 2.0.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (17): Release version 2.0.0 Added Django explicit mode to docs (#149) HOTIX: Django doc Logger is now cached (removed logging import time overhead) Update issue templates Adjusts issue templates Fix Typo in issue template fix #160 - invoking directory should not be search breaking point. Add explicit call to main() on cli.py (#165) Generate coverage.xml file (#167) Fix #166 renamed GLOBAL_ENV_ to ENVVAR_PREFIX_ (#168) Fix #169 renamed SETTINGS_MODULE_ to SETTINGS_FILE_ (#170) HOTFIX config.md on docs [skip ci] (#171) Fix some open file descriptors on exampls and tests (#172) Fix #151 setup pre-commit and black (#173) Add CONTRIBUTING.md, contrib instructions and Black badge (#174) Fix release script David Moreau Simard (1): Fix typos in bash export examples Jan Willhaus (2): Skip reloading envs for validators that only apply to current_env (#162) Fix #163 Allow disabling env prefix (#164) 2.0.1 (2019-04-22) ------------------ - Release version 2.0.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (17): Release version 2.0.0 Added Django explicit mode to docs (#149) HOTIX: Django doc Logger is now cached (removed logging import time overhead) Update issue templates Adjusts issue templates Fix Typo in issue template fix #160 - invoking directory should not be search breaking point. Add explicit call to main() on cli.py (#165) Generate coverage.xml file (#167) Fix #166 renamed GLOBAL_ENV_ to ENVVAR_PREFIX_ (#168) Fix #169 renamed SETTINGS_MODULE_ to SETTINGS_FILE_ (#170) HOTFIX config.md on docs [skip ci] (#171) Fix some open file descriptors on exampls and tests (#172) Fix #151 setup pre-commit and black (#173) Add CONTRIBUTING.md, contrib instructions and Black badge (#174) Fix release script David Moreau Simard (1): Fix typos in bash export examples Jan Willhaus (2): Skip reloading envs for validators that only apply to current_env (#162) Fix #163 Allow disabling env prefix (#164) - Fix release script. [Bruno Rocha] - Add CONTRIBUTING.md, contrib instructions and Black badge (#174) [Bruno Rocha] - Fix #151 setup pre-commit and black (#173) [Bruno Rocha] * Add pre-commit to makefile * Fix #151 setup pre-commit and black - Fix some open file descriptors on exampls and tests (#172) [Bruno Rocha] - HOTFIX config.md on docs [skip ci] (#171) [Bruno Rocha] - Fix #169 renamed SETTINGS_MODULE_ to SETTINGS_FILE_ (#170) [Bruno Rocha] Backwards compatibility maintained! - Fix #166 renamed GLOBAL_ENV_ to ENVVAR_PREFIX_ (#168) [Bruno Rocha] * Fix #166 renamed GLOBAL_ENV_ to ENVVAR_PREFIX_ See #166 * Added django compat example - Generate coverage.xml file (#167) [Bruno Rocha] - Add explicit call to main() on cli.py (#165) [Bruno Rocha] To use click-web tool the module should be able to be explicitly called. `python -m dynaconf.cli` - Fix #163 Allow disabling env prefix (#164) [Jan Willhaus, janw * Update docs for use of False instead of none] * Allow setting GLOBAL_ENV to "" or NoneType to remove prefix * Allow for underscore-only prefix with empty string GLOBAL_ENV * Test cases for the different GLOBAL_ENV settings * Update docs, add usage example * Apply suggestions from code review - Skip reloading envs for validators that only apply to current_env (#162) [Jan Willhaus] * Simplify control flow for single-env use-cases * Ensure uppercase env/current_env * Add test not reloading env with validation in same env * Pep8 compliance * Change mock call assertions for support in Py3.5 - Fix #160 - invoking directory should not be search breaking point. [Bruno Rocha] Search should stop at breaking point only if ROOT_PATH is defined - Fix Typo in issue template. [Bruno Rocha] - Adjusts issue templates. [Bruno Rocha] - Update issue templates. [Bruno Rocha] - Logger is now cached (removed logging import time overhead) [Bruno Rocha] Debugged using: `python3.7 -X importtime -c 'import app'` and `python3.7 -X importtime -c 'import dynaconf'` Found that the tries to import `logzero` were consuming 0.1us (not so much, but we dont want it) removed logzero, cached logger using lru_cache (that means that if loglevel changes, log changes) - improved docs and badges. - Fix typos in bash export examples. [David Moreau Simard] - HOTIX: Django doc. [Bruno Rocha] - Added Django explicit mode to docs (#149) [Bruno Rocha] - Release version 2.0.0. [Bruno Rocha] Shortlog of commits since last release: Aaron DeVore (1): GH-111: Fix MERGE_ENABLED merging settings with themselves Bruno Rocha (21): Merge branch 'jperras-merge-multiple-settings-files' Merge branch 'master' of github.com:rochacbruno/dynaconf Fix #106 make PROJECT_ROOT_FOR_DYNACONF to work with custom paths Update dynaconf/utils/boxing.py Update dynaconf/utils/boxing.py Add release script and CHANGELOG in place of history. Release version 1.2.0 Tox is now part of pre-publish command Drop Python 3.4 Release version 1.2.1 add top contributors Fix #129 on settings file, single keys should be case insensitive. Fix #125 settings_module not being set on .configure() Fix #127 add configurable yaml loader method, default to full_load Fix #122 allow disable of core loaders, added examples. Fix #117 add support for extra secrets file (like for jenkins CI) Fix #110 add docs for dynaconf_include Add dynaconf_include examples Set up CI with Azure Pipelines (#142) Add dynaconf_merge functionality for dict and list settings. (#139) Preparing for 2.0.0 Byungjin Park (1): Fix typo Jaepil Koh (1): Update django.md Joel Perras (3): Allow dotted-path based setting of configuration key/value pairs. Handle nested includes in settings files. Remove extraneous lines. Mantas (3): Add INSTANCE_FOR_DYNACONF and --instance Remove mocker fixture Python 3.4 has different error message Matthias (1): Fix small typo in README.md Pete Savage (1): Fix exponential slow down when loader is run multiple times Raoul Snyman (1): Add environments into the path in Vault so that the same Vault server can be used for multiple environments mspinelli (2): fixed infinite recursion caused by copy() add tests for dynabox fix 2.0.0 (2019-04-09) ------------------ - Release version 2.0.0. [Bruno Rocha] Shortlog of commits since last release: Aaron DeVore (1): GH-111: Fix MERGE_ENABLED merging settings with themselves Bruno Rocha (21): Merge branch 'jperras-merge-multiple-settings-files' Merge branch 'master' of github.com:rochacbruno/dynaconf Fix #106 make PROJECT_ROOT_FOR_DYNACONF to work with custom paths Update dynaconf/utils/boxing.py Update dynaconf/utils/boxing.py Add release script and CHANGELOG in place of history. Release version 1.2.0 Tox is now part of pre-publish command Drop Python 3.4 Release version 1.2.1 add top contributors Fix #129 on settings file, single keys should be case insensitive. Fix #125 settings_module not being set on .configure() Fix #127 add configurable yaml loader method, default to full_load Fix #122 allow disable of core loaders, added examples. Fix #117 add support for extra secrets file (like for jenkins CI) Fix #110 add docs for dynaconf_include Add dynaconf_include examples Set up CI with Azure Pipelines (#142) Add dynaconf_merge functionality for dict and list settings. (#139) Preparing for 2.0.0 Byungjin Park (1): Fix typo Jaepil Koh (1): Update django.md Joel Perras (3): Allow dotted-path based setting of configuration key/value pairs. Handle nested includes in settings files. Remove extraneous lines. Mantas (3): Add INSTANCE_FOR_DYNACONF and --instance Remove mocker fixture Python 3.4 has different error message Matthias (1): Fix small typo in README.md Pete Savage (1): Fix exponential slow down when loader is run multiple times Raoul Snyman (1): Add environments into the path in Vault so that the same Vault server can be used for multiple environments mspinelli (2): fixed infinite recursion caused by copy() add tests for dynabox fix - Preparing for 2.0.0. [Bruno Rocha] Dynaconf 2.0.0 - Fix #129 get_fresh should be case insensitive - Fix #125 .configure was not loading `settings_module` passed as argument - Fix #127 fix YAML warnings and default to full_load - Allow disable of core loaders #122 - Added support for Jenkins secrets file #117 - Added more examples for includes #110 - Moved to Azure Pipelines CI #142 - Added 100% test coverage on windows (Unit & Functional tests) - Deprecated MERGE_ENABLED in favor of local dynaconf_merge - Fix #74 - Better File Searching (now building a reasonable Search Tree) - Now it finds settings when invoking from out of Script folder - Fixed test environment (each test now run in a separate tmpdir) - Added a check to avoid Circular references when starting settings inside settings - Added Django Extension v2 with better syntax and a lot od `inspect` instrospetion - Updated documentation about new features - Added a not that YAML is the recommended format for Django - Added support for Django Standalone Script - Added support for Django unit testing - Fix #148 `env` was not being passed to custom loaders - Fix #144 removed `six` as it is a Py3.4+ only project - Added Backwards compatibility for users using old django Extension - start_dotenv is now Lazy (only when settings._setup is called) - Added new _FOR_DYNACONF config options ENV_SWITCHER, SKIP_FILES, INCLUDES & SECRETS - Renamed config PROJECT_ROOT -> ROOT_PATH - Add dynaconf_merge functionality for dict and list settings. (#139) [Bruno Rocha] If your settings has existing variables of types `list` or `dict` and you want to `merge` instead of `override` then the `dynaconf_merge` and `dynaconf_merge_unique` stanzas can mark that variable as a candidate for merging. For **dict** value: Your main settings file (e.g `settings.toml`) has an existing `DATABASE` dict setting on `[default]` env. Now you want to contribute to the same `DATABASE` key by addind new keys, so you can use `dynaconf_merge` at the end of your dict: In specific `[envs]` ```toml [default] database = {host="server.com", user="default"} [development] database = {user="dev_user", dynaconf_merge=true} [production] database = {user="prod_user", dynaconf_merge=true} ``` In an environment variable: ```bash export DYNACONF_DATABASE='{password=1234, dynaconf_merge=true}' ``` Or in an additional file (e.g `settings.yaml, .secrets.yaml, etc`): ```yaml default: database: password: 1234 dynaconf_merge: true ``` The `dynaconf_merge` token will mark that object to be merged with existing values (of course `dynaconf_merge` key will not be added to the final settings it is just a mark) The end result will be on `[development]` env: ```python settings.DATABASE == {'host': 'server.com', 'user': 'dev_user', 'password': 1234} ``` The same can be applied to **lists**: `settings.toml` ```toml [default] plugins = ["core"] [development] plugins = ["debug_toolbar", "dynaconf_merge"] ``` And in environment variable ```bash export DYNACONF_PLUGINS='["ci_plugin", "dynaconf_merge"]' ``` Then the end result on `[development]` is: ```python settings.PLUGINS == ["ci_plugin", "debug_toolbar", "core"] ``` The `dynaconf_merge_unique` is the token for when you want to avoid duplications in a list. Example: ```toml [default] scripts = ['install.sh', 'deploy.sh'] [development] scripts = ['dev.sh', 'test.sh', 'deploy.sh', 'dynaconf_merge_unique'] ``` ```bash export DYNACONF_SCRIPTS='["deploy.sh", "run.sh", "dynaconf_merge_unique"]' ``` The end result for `[development]` will be: ```python settings.SCRIPTS == ['install.sh', 'dev.sh', 'test.sh', 'deploy.sh', 'run.sh'] ``` > Note that `deploy.sh` is set 3 times but it is not repeated in the final settings. The **dynaconf_merge** functionality works only for the first level keys, it will not merge subdicts or nested lists (yet). - Set up CI with Azure Pipelines (#142) [Bruno Rocha] - setup azure pipelines ci - remove travis - fix windows support - Add dynaconf_include examples. [Bruno Rocha] - Fix #110 add docs for dynaconf_include. [Bruno Rocha] fix #110 - Fix #117 add support for extra secrets file (like for jenkins CI) [Bruno Rocha] Now it is possible to export SECRETS_FOR_DYNACONF and have this extra point loaded, like in a Jenkins CI you can specify on job. ```yaml secret_file: variable: SECRETS_FOR_DYNACONF credentials: type: specific_credentials value: /path/to/secrets_file.toml{json,ini,yaml,py} ``` That variable can also be a list of paths. - Fix #122 allow disable of core loaders, added examples. [Bruno Rocha] - Fix #127 add configurable yaml loader method, default to full_load. [Bruno Rocha] - Fix #125 settings_module not being set on .configure() [Bruno Rocha] - Fix #129 on settings file, single keys should be case insensitive. [Bruno Rocha] - GH-111: Fix MERGE_ENABLED merging settings with themselves. [Aaron DeVore] - Add top contributors. [Bruno Rocha] - Release version 1.2.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (9): Merge branch 'jperras-merge-multiple-settings-files' Merge branch 'master' of github.com:rochacbruno/dynaconf Fix #106 make PROJECT_ROOT_FOR_DYNACONF to work with custom paths Update dynaconf/utils/boxing.py Update dynaconf/utils/boxing.py Add release script and CHANGELOG in place of history. Release version 1.2.0 Tox is now part of pre-publish command Drop Python 3.4 Byungjin Park (1): Fix typo Jaepil Koh (1): Update django.md Joel Perras (3): Allow dotted-path based setting of configuration key/value pairs. Handle nested includes in settings files. Remove extraneous lines. Mantas (3): Add INSTANCE_FOR_DYNACONF and --instance Remove mocker fixture Python 3.4 has different error message Matthias (1): Fix small typo in README.md Pete Savage (1): Fix exponential slow down when loader is run multiple times Raoul Snyman (1): Add environments into the path in Vault so that the same Vault server can be used for multiple environments mspinelli (2): fixed infinite recursion caused by copy() add tests for dynabox fix 1.2.1 (2019-03-11) ------------------ - Release version 1.2.1. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (9): Merge branch 'jperras-merge-multiple-settings-files' Merge branch 'master' of github.com:rochacbruno/dynaconf Fix #106 make PROJECT_ROOT_FOR_DYNACONF to work with custom paths Update dynaconf/utils/boxing.py Update dynaconf/utils/boxing.py Add release script and CHANGELOG in place of history. Release version 1.2.0 Tox is now part of pre-publish command Drop Python 3.4 Byungjin Park (1): Fix typo Jaepil Koh (1): Update django.md Joel Perras (3): Allow dotted-path based setting of configuration key/value pairs. Handle nested includes in settings files. Remove extraneous lines. Mantas (3): Add INSTANCE_FOR_DYNACONF and --instance Remove mocker fixture Python 3.4 has different error message Matthias (1): Fix small typo in README.md Pete Savage (1): Fix exponential slow down when loader is run multiple times Raoul Snyman (1): Add environments into the path in Vault so that the same Vault server can be used for multiple environments mspinelli (2): fixed infinite recursion caused by copy() add tests for dynabox fix - Fix exponential slow down when loader is run multiple times. [Pete Savage] * When using context managers, the loader is invoked each time. This was slowing down in an exponential manner each time the it was run. The eventual cause of this was down to an attribute being used as a list. The object merge dutifully tried to expand this item out again and again even in the case that the list was a single item, resulting in [item], becoming [item, item]. The next time the merge was run, this process was run again, but for each item in the list. In this particular instance the list was identical, it meant that the list grew exponentially. * This fix is a short optimization that checks to see if the old list is identical to the new list. In which case, there is no merge to complete so we simply return. - Add environments into the path in Vault so that the same Vault server can be used for multiple environments. [Raoul Snyman] - Fix typo. [Byungjin Park] - Drop Python 3.4. [Bruno Rocha] - Tox is now part of pre-publish command. [Bruno Rocha] - Release version 1.2.0. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (6): Merge branch 'jperras-merge-multiple-settings-files' Merge branch 'master' of github.com:rochacbruno/dynaconf Fix #106 make PROJECT_ROOT_FOR_DYNACONF to work with custom paths Update dynaconf/utils/boxing.py Update dynaconf/utils/boxing.py Add release script and CHANGELOG in place of history. Jaepil Koh (1): Update django.md Joel Perras (3): Allow dotted-path based setting of configuration key/value pairs. Handle nested includes in settings files. Remove extraneous lines. Mantas (3): Add INSTANCE_FOR_DYNACONF and --instance Remove mocker fixture Python 3.4 has different error message Matthias (1): Fix small typo in README.md mspinelli (2): fixed infinite recursion caused by copy() add tests for dynabox fix 1.2.0 (2018-11-30) ------------------ - Release version 1.2.0. [Bruno Rocha] Shortlog of commits since last release: Bruno Rocha (6): Merge branch 'jperras-merge-multiple-settings-files' Merge branch 'master' of github.com:rochacbruno/dynaconf Fix #106 make PROJECT_ROOT_FOR_DYNACONF to work with custom paths Update dynaconf/utils/boxing.py Update dynaconf/utils/boxing.py Add release script and CHANGELOG in place of history. Jaepil Koh (1): Update django.md Joel Perras (3): Allow dotted-path based setting of configuration key/value pairs. Handle nested includes in settings files. Remove extraneous lines. Mantas (3): Add INSTANCE_FOR_DYNACONF and --instance Remove mocker fixture Python 3.4 has different error message Matthias (1): Fix small typo in README.md mspinelli (2): fixed infinite recursion caused by copy() add tests for dynabox fix - Add release script and CHANGELOG in place of history. [Bruno Rocha] - Add tests for dynabox fix. [mspinelli] - Update dynaconf/utils/boxing.py. [Bruno Rocha, mspinelli] - Update dynaconf/utils/boxing.py. [Bruno Rocha, mspinelli] - Fixed infinite recursion caused by copy() [mspinelli] - Fix #106 make PROJECT_ROOT_FOR_DYNACONF to work with custom paths. [Bruno Rocha] Added example/project_root and entry on Makefile:test_examples - Update django.md. [Jaepil Koh] Typo! - Fix small typo in README.md. [Matthias] - Merge branch 'master' of github.com:rochacbruno/dynaconf. [Bruno Rocha] - Python 3.4 has different error message. [Mantas] - Remove mocker fixture. [Mantas] Left this accidentally. https://travis-ci.org/rochacbruno/dynaconf/jobs/452612532 - Add INSTANCE_FOR_DYNACONF and --instance. [Mantas] There parameters allows dynaconf to use different LazySettings instance if project uses one. Also did some other fixes along the way: - Added `catch_exceptions=False` to `CliRunner.invoke` in order to prevent click from swallowing errors silently. This uncovered other errors in init and validate cli commands. - Removed module level code execution from cli module. Module level code execution makes it really difficult to test code. Now cli does not rely on global state and can be tested properly. - Removed a code snipper from LazySettings which modified global default_settings values. This means, that each LazySettings constructor call has side effects. - `dynaconf validate` command tests were useless because they didn't test anything and I found, that `dynaconf validate` command don't even work and raises ValidationError if there are any validation errors. Changed that to cli friendly error message. - Merge branch 'jperras-merge-multiple-settings-files' [Bruno Rocha] - Remove extraneous lines. [Joel Perras] - Handle nested includes in settings files. [Joel Perras] A settings file can include a `dynaconf_include` stanza, whose exact syntax will depend on the type of settings file (json, yaml, toml, etc) being used: ```toml [default] dynaconf_include = ["/absolute/path/to/plugin1.toml", "relative/path/to/plugin2.toml"] DEBUG = false SERVER = "www.example.com" ``` When loaded, the files located at the (relative or absolute) paths in the `dynaconf_include` key will be parsed, in order, and override any base settings that may exist in your current configuration. The paths can be relative to the base `settings.(toml|yaml|json|ini|py)` file, or can be absolute paths. The idea here is that plugins or extensions for whatever framework or architecture you are using can provide their own configuration values when necessary. It is also possible to specify glob-based patterns: ```toml [default] dynaconf_include = ["configurations/*.toml"] DEBUG = false SERVER = "www.example.com" ``` Currently, only a single level of includes is permitted to keep things simple and straightforward. - Allow dotted-path based setting of configuration key/value pairs. [Joel Perras] You can set a value with an arbitrary number of nested keys, separated by dots: ```python settings.set('nested_1.nested_2.nested_3.nested_4', 'secret') ``` And accessing the keys/values with dotted-path lookup behaves as expected: ```python print(settings.NESTED_1.NESTED_2.NESTED_3.to_dict()) ``` If for some reason you didn't want to have a key parsed into nested structures delimited by dots and just wanted a key of "foo.bar", you can disable the parsing with: ```python settings.set('nested_1.nested_2.nested_3.nested_4', 'secret', dotted_lookup=False) ``` And accessing keys that don't exist will raise `KeyError`: ```python settings.NESTED_1.NESTED_5 ``` 1.1.0 (2018-10-26) ------------------ - Released 1.1.0. [Bruno Rocha] - Added `MERGE_ENABLED_FOR_DYNACONF` with ability to merge nested dictionaries instead of replacing PR #88 - Support for dot notation to access nested dictionaries like `settings['KEY.OTHER.NESTED.DEEPER']` PR #93 - Support dotted notation for validators PR #98 - Fixed a bug in SETTINGS_MODULE cleanup when `.setenv` method was called PR #97 - Added Python 3.7 to test matrix PR #99 - Fixing new flake8 warnings. [Bruno Rocha] - Update py.test command in tox to allow passing positional arguments. [Joel Perras] The basic, default functionality of running `tox` remains unchanged. You are now, however, able to pass positional arguments to `py.test` at invocation time. For example: ```bash tox -- -x --pdb tests/test_basic.py ``` Which will pass all input after the `--` separator (which is used to signify the end of possible options to `tox`) down to the `py.test` command in the `{posargs}` location, as defined in `tox.ini`. - Enable Python 3.7 env for tox testing. [Joel Perras] - Enable python 3.7 in TravisCI config. [Joel Perras] - Updates Missing singleton with __eq__ dunder. (#98) [Joël Perras] Adds some additional tests for the `Missing` class and singleton instance usage to ensure it returns equality only for comparisons to itself and not `None`, or `False`, or `True`. - Merge branch 'jperras-dotted-validators' [Bruno Rocha] - Updates Missing singleton with __eq__ dunder. [Joel Perras] Adds some additional tests for the `Missing` class and singleton instance usage to ensure it returns equality only for comparisons to itself and not `None`, or `False`, or `True`. - Implements dotted-path validator name declarations. [Joel Perras] One is now able to write: ```python settings.validators.register( Validator('REDIS', must_exist=True, is_type_of=dict), Validator('REDIS.HOST', must_exist=True, is_type_of=str), Validator('REDIS.PORT', must_exist=True, is_type_of=int), ) ``` Which will validate the dotted attributes as nested structures. For example, in yaml: ```yaml DEFAULT: REDIS: HOST: localhost PORT: 1234 ``` This necessitated a slight but non-negligible change in the implementation of `Settings.exists()`, which previously did a shallow check of loaded data. It has now been updated to perform a `Settings.get()` of the key in question, and compares that to a newly defined sentinel value to ensure `None` values do not cause a false negative result. New tests and assertions have been added to cover the new functionality. Docs have been updated to show an example of the nested validator name definition in action. Closes rochacbruno/dynaconf#85. - Fix #94 setenv cleans SETTINGS_MODULE attribute. [Bruno Rocha] - Merge branch 'jperras-dot-traversal-access' [Bruno Rocha] - Merge branch 'dot-traversal-access' of https://github.com/jperras/dynaconf into jperras-dot-traversal-access. [Bruno Rocha] - Allow dot-traversal access to nested dictionaries. [Joel Perras] A simple memoized recursion has been added to `get()` if the key contains at least one dot. The caller can choose to opt-out of this behaviour by specifying the `dotted_lookup` argument: ```python settings('AUTH.USERNAME', dotted_lookup=False) ``` While the performance impact of this has not been quantified, the net impact in any real-world application should be minimal due to typical nesting levels, and the fact that we overwrite the memoized portion of the dotted-key lookup on each iteration. - Avoids regressions [✓] - Can be opted-out on a per-call basis [✓] - Minimal performance impact [✓] - Documented [✓] - Tested [✓] - Examples added [✓] Closes rochacbruno/dynaconf#84 - Merge branch 'rsnyman-merge-settings' [Bruno Rocha] - Add example for merge_configs. [Bruno Rocha] - Add setting merging. [Raoul Snyman] - Add the ability to merge nested structures instead of completely overwriting them - Use monkeypatch to stop one test from interfering with another - Updated documentation 1.0.6 (2018-09-13) ------------------ - Release 1.0.6. [Bruno Rocha] Fixed issue #81 - added ENCODING_FOR_DYNACONF to handle different settings files encodings specially on Windows - Add ENCODING_FOR_DYNACONF to handle different file encoding Fix #81. [Bruno Rocha] By default ENCODING_FOR_DYNACONF is utf-8 (recommended to always write settings files in utf-8) If you need to change the format of settings file set the variable: ``` export ENCODING_FOR_DYNACONF="cp1252" ``` 1.0.5 (2018-09-07) ------------------ - Bump dev version. [Bruno Rocha] - Added few more enhancements to django and flask extensions + docs. [Bruno Rocha] - Bump dev version. [Bruno Rocha] 1.0.4 (2018-09-07) ------------------ - Fix the definition of Django prefixed variable. [Bruno Rocha] - Merge pull request #78 from mattkatz/patch-1. [Bruno Rocha] small corrections for usage.md - Small corrections for usage.md. [Matt Katz] This library is great, just a correction in the docs. There must be AT LEAST one default section. Changed **ATTENTION** to **ATTENTION**: to match with the style of **NOTE:** - Merge pull request #75 from yoyonel/master. [Bruno Rocha] Fix in 'dynaconf/base.py' for __getitem__ method - Bump version. [latty] - [Fix] in 'dynaconf/base.py' for __getitem__ method, change (fix) the condition to raise a exception. Update unit tests. Bump version. [latty] - Release 1.0.3. [Bruno Rocha] - Excluded example and tests from release dist - removed root logger configuration 1.0.3 (2018-06-26) ------------------ - Merge pull request #72 from chobeat/issue_71. [Bruno Rocha] Removed root config - Removed root config. [Simone Robutti] - Merge pull request #70 from allan-silva/master. [Bruno Rocha] Exclude example and tests folders from setup (twitter help wanted) - Exclude example and tests folders from setup (twitter help wanted) [allan.silva] - Merge pull request #67 from cassiobotaro/patch-1. [Bruno Rocha] Incorrect help - Fix docs. [cassiobotaro] - Incorrect help. [Cássio Botaro] For while is impossible to use --to as argument. - Merge pull request #66 from gpkc/patch-1. [Bruno Rocha] Fixing typos - Merge pull request #1 from gpkc/patch-2. [Guilherme Caminha] Update README.md - Update README.md. [Guilherme Caminha] - Update usage.md. [Guilherme Caminha] - Adjust logs to include python module names. [Bruno Rocha] - Fix sphinx aafig syntax for python 3.x. [Bruno Rocha] 1.0.2 (2018-05-31) ------------------ - Merge pull request #65 from rochacbruno/testing_bare_install. [Bruno Rocha] Add install test stage - Add -y. [Bruno Rocha] - Add install test stage. [Bruno Rocha] - Fix loader import error and improved logs. [Bruno Rocha] - Clean up [skip ci] [Bruno Rocha] - Fix URL generation in markdown for sphinx [skip ci] [Bruno Rocha] - Merge pull request #64 from rochacbruno/improve_docs. [Bruno Rocha] Improved documentation - Improved documentation. [Bruno Rocha] - [skip ci] [Bruno Rocha] 1.0.1 (2018-05-30) ------------------ - Merge pull request #63 from rochacbruno/adds_more_python_versions. [Bruno Rocha] Adds more python versions - Cover. [Bruno Rocha] - Cover. [Bruno Rocha] - Skip 3.7-dev. [Bruno Rocha] - More trabis build stages. [Bruno Rocha] - Add missing .env. [Bruno Rocha] - Fix #60 CLI validator command. [Bruno Rocha] - Fix #59 cli commands working for Flask and Django apps. [Bruno Rocha] - Fixes to support Python 3.4, 3.5 and 3.6 Fix #62. [Bruno Rocha] - Strict use of _FOR_DYNACONF envvars. [Bruno Rocha] - Aafigure. [Bruno Rocha] - Pinned docutils. [Bruno Rocha] - Rtfd fix. [Bruno Rocha] - Add rtfd yml. [Bruno Rocha] - Added init file to docs. [Bruno Rocha] - Add import path. [Bruno Rocha] - Docs. [Bruno Rocha] - Finished README with all the new implementations. [Bruno Rocha] 1.0.0 (2018-05-28) ------------------ - Merge pull request #56 from rochacbruno/all_the_namespace_changed_to_env. [Bruno Rocha] Major Breaking Refactor related to #54 - Travis fix 2. [Bruno Rocha] - Travis global fix. [Bruno Rocha] - Travis fix for new style toml envvars. [Bruno Rocha] - Deprecated `@type` casting in favor of TOML syntax, rewriting readme. [Bruno Rocha] - Add `settings.flag` [Bruno Rocha] - Using `dynaconf write` in test_redis|vault. [Bruno Rocha] - Added `dynaconf --docs` [Bruno Rocha] - Added `dynaconf --version` [Bruno Rocha] - Removed transformators. [Bruno Rocha] - 100% coverage for validators. [Bruno Rocha] - Increase cli test coverage. [Bruno Rocha] - Dynaconf variables in blue and user variables in green. [Bruno Rocha] - Added `dynaconf list` and `dynaconf write` subcommands. [Bruno Rocha] - More cli commands list and write. [Bruno Rocha] - Added more tests for cli and py loader. [Bruno Rocha] - Replaced coveralls with codecov #57. [Bruno Rocha] - Modularized the loaders, added `dynaconf init` command. [Bruno Rocha] - As environment variable the only prefix allowed is the GLOBAL_ENV.. default to DYNACONF_ [Bruno Rocha] - Added more examples/tests and test_cli. [Bruno Rocha] - Removed cleaners. [Bruno Rocha] - Major Breaking Refactor related to #54. [Bruno Rocha] 0.7.6 (2018-05-21) ------------------ - Merge pull request #52 from rochacbruno/fix_namespace_in_django. [Bruno Rocha] Fix namespace switch in django apps - Add missing .env. [Bruno Rocha] - Fix namespace switch in django apps. [Bruno Rocha] 0.7.5 (2018-05-20) ------------------ - Merge pull request #51 from rochacbruno/added_django_extension. [Bruno Rocha] Added django extension - 0.7.5 release with Django extension (experimental) [Bruno Rocha] - Dont commit dbs. [Bruno Rocha] - Added Django extension tests and example app. [Bruno Rocha] - Added Django extension. [Bruno Rocha] 0.7.4 (2018-05-19) ------------------ - Merge pull request #50 from rochacbruno/074. [Bruno Rocha] Fix precedence of namespace in loaders - Fix precedence of namespace in loaders. [Bruno Rocha] - Merge pull request #49 from thekashifmalik/patch-1. [Bruno Rocha] Fix typo in README. - Fix typo in README. [Kashif Malik] - HOTFIX: redis config. [Bruno Rocha] - Merge pull request #48 from rochacbruno/redis_tests. [Bruno Rocha] Added tests for Redis loader - Added tests for Redis loader. [Bruno Rocha] - Merge pull request #47 from rochacbruno/vault_tests. [Bruno Rocha] Added test for vaultproject - Fix deadlock in vault writer. [Bruno Rocha] - Added test for vaultproject. [Bruno Rocha] 0.7.3 (2018-05-13) ------------------ - Merge pull request #45 from rochacbruno/vault_loader. [Bruno Rocha] Added support for vaultproject (hashi corp) loader - Added README section. [Bruno Rocha] - Added note to readme. [Bruno Rocha] - Added tests. [Bruno Rocha] - Fixing for python-box 3.2.0. [Bruno Rocha] - Added config AUTO_CAST_FOR_DYNACONF=off|0|disabled|false Suggested by @danilobellini. [Bruno Rocha] - Fixed env variable for debug level in README.md. [Simone Robutti] - Implementation of Vault loader. [Bruno Rocha] - Vaultproject loader implementation. [Bruno Rocha] - Merge pull request #46 from rochacbruno/disable_cast. [Bruno Rocha] Added config AUTO_CAST_FOR_DYNACONF=off|0|disabled|false - Added note to readme. [Bruno Rocha] - Added tests. [Bruno Rocha] - Fixing for python-box 3.2.0. [Bruno Rocha] - Added config AUTO_CAST_FOR_DYNACONF=off|0|disabled|false Suggested by @danilobellini. [Bruno Rocha] - Merge pull request #44 from chobeat/patch-1. [Bruno Rocha] Fixed env variable for debug level in README.md - Fixed env variable for debug level in README.md. [Simone Robutti] 0.7.2 (2018-05-07) ------------------ - Added test for compat. [Bruno Rocha] - Added SETTINGS_MODULE to SETTINGS_MODULE_FOR_DYNACONF in compat. [Bruno Rocha] - Added backwards compatibility for old style kwargs. [Bruno Rocha] - Merge pull request #30 from vladcalin/add-docs. [Bruno Rocha] Add docs skeleton with autogenerated module docs - Add docs skeleton with autogenerated module docs. [Vlad Calin] 0.7.0 (2018-05-07) ------------------ - README updates [ci skip] [Bruno Rocha] - Added support for `.secrets` files. [Bruno Rocha] - Merge pull request #43 from rochacbruno/test_coverage. [Bruno Rocha] Adjusting ENVVARS names and better test coverage - Travis testing. [Bruno Rocha] - Adjust travis.yml for muultiple jobs. [Bruno Rocha] - Never cleans default keys. [Bruno Rocha] - Refactoring for better test cov. [Bruno Rocha] - Adjusting ENVVARS names and better test coverage. [Bruno Rocha] 0.6.0 (2018-05-04) ------------------ - Release of 0.6.0. [Bruno Rocha] - Merge pull request #42 from rochacbruno/fix41. [Bruno Rocha] Fix #41 - Fix #41. [Bruno Rocha] - Merge pull request #40 from rochacbruno/inifiles. [Bruno Rocha] ini and json files + parseconf recursive and find_file function - Ini and json files + parseconf recursive and find_file function. [Bruno Rocha] - Added support for .ini and .json files - parse conf is now recursive to parse dict inner data - Cloned find_file function from dotenv - Merge pull request #38 from rochacbruno/flask_dot_env. [Bruno Rocha] Added Flask 1.0 dotenv support - IMplemented TOML loader. [Bruno Rocha] - Adjusted MARKDOWN. [Bruno Rocha] - Added Flask 1.0 dotenv support. [Bruno Rocha] 0.5.2 (2017-10-03) ------------------ - Small fix on 0.5.2 :hamster: [Bruno Rocha] - 0.5.1 with YAML hotfixes and allowing multiple yaml files. [Bruno Rocha] 0.5.0 (2017-09-26) ------------------ - Drop 3.4 and 3.5. [Bruno Rocha] - Silent errors on YAML missing namespace by default. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Merge branch 'Sytten-expand-yaml-config' [Bruno Rocha] - Specialized Box as Dynabox to allow upper and lower case access. [Bruno Rocha] - Use box. [Emile Fugulin] - Add expanded object for yaml. [Emile Fugulin] 0.4.5 (2017-05-30) ------------------ - Update README.md. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Merge pull request #20 from douglas/master. [Bruno Rocha] Upgrade dynaconf to 0.4.5 =) - Upgrade dynaconf to 0.4.5 =) [Douglas Soares de Andrade] - Improves the way Tox installs the projects dependencies. [Douglas Soares de Andrade] - Make tests directory a package. [Douglas Soares de Andrade] - So we can use the syntax from dynaconf import … - Make it clear where we are getting LazySettings from. [Douglas Soares de Andrade] - Add m2r and Flask to Pipenv. [Douglas Soares de Andrade] - Removing pdbpp as it breaks with Python 3.3. [Douglas Soares de Andrade] - Update readme. [Bruno Rocha] - Update README. [Bruno Rocha] 0.4.4 (2017-03-21) ------------------ - HOTFIX: Flask templates always expects `None` for KeyError or AttrError. [Bruno Rocha] - Bump version. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Added FlaskDynaconf to readme. [Bruno Rocha] - Merge pull request #16 from rochacbruno/added_flaskdynaconf. [Bruno Rocha] Added FlaskDynaconf - Added FlaskDynaconf. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Merge pull request #15 from douglas/master. [Bruno Rocha] Make the project work both on Python2 and Python3 - PEP8/Pylint and fixes equality operators. [Douglas Soares de Andrade] - Remove unused code. [Douglas Soares de Andrade] - PEP8 and Pylint fixes. [Douglas Soares de Andrade] - Remove pypy3 as it does not work. [Douglas Soares de Andrade] - Adding pypy and pypy3 to Travis. [Douglas Soares de Andrade] - Oops, need to rename _super to super. [Douglas Soares de Andrade] - Fix the import according to pep8. [Douglas Soares de Andrade] - Remove this to see if it is still an issue. [Douglas Soares de Andrade] - Adding Python 2.7. [Douglas Soares de Andrade] - Adding the editorconfig file. [Douglas Soares de Andrade] - Add Pipfile.lock to .gitignore. [Douglas Soares de Andrade] - Small Refactory. [Douglas Soares de Andrade] - Adding object to the Settings class to make it work with Python2 - Small Refactory. [Douglas Soares de Andrade] - Reordering the imports according to pylint and flake8 - Adding object to the classes to make them work with Python2 - Small Refactory. [Douglas Soares de Andrade] - Fixing the __init__ signature to make it compatible with python2 and python3 - Adding object to the class to make Python2 work - Adding the Pipenv file. [Douglas Soares de Andrade] - To allow us to use: https://github.com/kennethreitz/pipenv - Adding Tox to helps us test the library. [Douglas Soares de Andrade] - Fix #14 casting bool for booleans tks to @dbstraffin. [Bruno Rocha] - Fix yaml cleaner, renamed `defined` to `must_exist` in validator. [Bruno Rocha] - Added validators. [Bruno Rocha] 0.4.1 (2017-02-12) ------------------ - Bump 0.4.1. [Bruno Rocha] - Merge pull request #13 from rochacbruno/add_yaml_support. [Bruno Rocha] Added YAML support - Added YAML support. [Bruno Rocha] - Force pip upgrade in travis. [Bruno Rocha] - Drop support to Python 2.x - #MoveToPython3 Now! [Bruno Rocha] - Add 'decode_responses': True note. [Bruno Rocha] - Fix travis error. [Bruno Rocha] - Python 3 support. [Bruno Rocha] - Update README.md. [Bruno Rocha] 0.3.0 (2016-01-14) ------------------ - Fix error when envvar key has leading or trailing spaces. [Bruno Rocha] - Pip released. [Bruno Rocha] - Pypi is in troublr to release. [Bruno Rocha] - Bump. [Bruno Rocha] - If 'settings.py' is found on PROJECT_ROOT it is read. [Bruno Rocha] - Make release. [Bruno Rocha] - Path_for returns rooted path if starts with / [Bruno Rocha] - Added settings.path_for. [Bruno Rocha] - Ignore functional styling smells. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Travis envvars matrix. [Bruno Rocha] - Update .travis.yml. [Bruno Rocha] - Starting to write tests :) [Bruno Rocha] - Simplified objects, removed UserSettings, removed exceptions. [Bruno Rocha] 0.2.7 (2015-12-23) ------------------ - Removed six and used obj.set. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Added png. [Bruno Rocha] - Redis loader uses hash. [Bruno Rocha] - Using HASH to store data, added always_fresh vars and context thanks to @dmoliveira and @ederfmartins. [Bruno Rocha] - Added settings_module as cached property. [Bruno Rocha] - Added delete function to redis_writer. [Bruno Rocha] - Added note about get_fresh in readme. [Bruno Rocha] - Better namespace management, get_fresh(key) to access redis. [Bruno Rocha] - Now it can be used programmatically. [Bruno Rocha] 0.2.1 (2015-12-20) ------------------ - Added redis_writer. [Bruno Rocha] - Update readme. [Bruno Rocha] 0.2.0 (2015-12-20) ------------------ - Can also load from arbitrary filepath. [Bruno Rocha] - Renamed var, added loaders, bump version. [Bruno Rocha] 0.1.2 (2015-08-20) ------------------ - Format on readme. [Bruno Rocha] - More casting options. [Bruno Rocha] - Fix #1 multiple namespaces. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Update README.md. [Bruno Rocha] - Added default. [Bruno Rocha] - Initial commit. [Bruno Rocha] ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/CONTRIBUTING.md0000644000000000000000000001316015156324234015774 0ustar00pbrochadpbrochad# Contributing When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. Please note we have a code of conduct, please follow it in all your interactions with the project. This Diagram can help you understand visually what happens on Dynaconf: https://viewer.diagrams.net/?highlight=0000ff&edit=_blank&layers=1&nav=1&title=Dynaconf#Uhttps%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D11krXcDr67FGci_f36FZO-hiL08z4FuL9%26export%3Ddownload ## Pull Request Process 1. Ensure your local environment is set. 1. Clone your own fork of this repo 2. Activate a python3.8+ virtualenv 3. Code 2. Update the `docs/guides/` 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! ### Common Workflow: ```bash # clone your fork of this repo git clone git@github.com:{$USER}/dynaconf.git # Add the upstream remote git remote add upstream https://github.com/dynaconf/dynaconf.git # Activate your Python Environment python3.7 -m venv venv source venv/bin/activate # Install dynaconf for development make all # Checkout to a working branch git checkout -b my_feature # Open your favorite editor (VSCode for example) code . # After editing please rebase with upstream git fetch upstream; git rebase upstream/master # Fix any conflicts if any. # Update docs/guides/ if needed # 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 # Now commit your changes git commit -am "Changed XPTO to fix #issue_number" # Push to your own fork git push -u origin HEAD # Open github.com/dynaconf/dynaconf and send a Pull Request. ``` ### Run integration tests * "make all" do not run integration tests for Redis and Vault. * If you want to run integration tests, make sure you have docker and docker-compose installed. ```bash # To install docker curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh # To permit your user run docker commands without sudo sudo usermod -aG docker {$USER} # Run complete integration tests make test_integration # or Run functional example tests individually make test_redis make test_vault ``` ## Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at `rochacbruno [at] gmail [dot] com`. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ### Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/CONTRIBUTORS.md0000644000000000000000000000324415156324234016024 0ustar00pbrochadpbrochad# 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) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/LICENSE0000644000000000000000000000206615156311032014543 0ustar00pbrochadpbrochadThe MIT License (MIT) Copyright (c) 2015 Bruno Rocha 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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/MANIFEST.in0000644000000000000000000000022315156311032015265 0ustar00pbrochadpbrochadinclude *.md include *.png include LICENSE include VERSION include dynaconf/VERSION recursive-include vendor_licenses ** prune dynaconf/vendor_src ././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9233465 dynaconf-3.2.13/PKG-INFO0000644000000000000000000002231115156326623014642 0ustar00pbrochadpbrochadMetadata-Version: 2.4 Name: dynaconf Version: 3.2.13 Summary: The dynamic configurator for your Python Project Home-page: https://github.com/dynaconf/dynaconf Author: Bruno Rocha Author-email: rochacbruno@gmail.com License: MIT Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django Classifier: Framework :: Flask Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English 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.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Utilities Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=3.8 Description-Content-Type: text/markdown 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 Provides-Extra: redis Requires-Dist: redis; extra == "redis" Provides-Extra: vault Requires-Dist: hvac; extra == "vault" Provides-Extra: yaml Requires-Dist: ruamel.yaml; extra == "yaml" Provides-Extra: toml Requires-Dist: toml; extra == "toml" Provides-Extra: ini Requires-Dist: configobj; extra == "ini" Provides-Extra: configobj Requires-Dist: configobj; extra == "configobj" Provides-Extra: all Requires-Dist: redis; extra == "all" Requires-Dist: ruamel.yaml; extra == "all" Requires-Dist: configobj; extra == "all" Requires-Dist: hvac; extra == "all" Provides-Extra: test Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-cov; extra == "test" Requires-Dist: pytest-xdist; extra == "test" Requires-Dist: pytest-mock; extra == "test" Requires-Dist: radon; extra == "test" Requires-Dist: flask>=0.12; extra == "test" Requires-Dist: django; extra == "test" Requires-Dist: python-dotenv; extra == "test" Requires-Dist: toml; extra == "test" Requires-Dist: redis; extra == "test" Requires-Dist: hvac>=1.1.0; extra == "test" Requires-Dist: configobj; extra == "test" Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: description-content-type Dynamic: home-page Dynamic: license Dynamic: license-file Dynamic: platform Dynamic: provides-extra Dynamic: requires-python Dynamic: summary

dynaconf. new logo

> **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/) ![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 - Inspired by the [12-factor application guide](https://12factor.net/config) - Settings management (default values, validation, parsing, templating) - Protection of sensitive information (passwords/tokens) - Multiple file formats `toml|yaml|json|ini|py` and also customizable loaders. - Full support for environment variables to override existing settings (dotenv support included). - Optional layered system for multi environments `[default, development, testing, production]` - Built-in support for Hashicorp Vault and Redis as settings and secrets storage. - Built-in extensions for **Django** and **Flask** web frameworks. - CLI for common operations such as `init, list, write, validate, export`. - full docs on https://dynaconf.com ### Install ```bash $ pip install dynaconf ``` #### Initialize Dynaconf on project root directory ```plain $ cd path/to/your/project/ $ dynaconf init -f toml ⚙️ Configuring your Dynaconf environment ------------------------------------------ 🐍 The file `config.py` was generated. 🎛️ settings.toml created to hold your settings. 🔑 .secrets.toml created to hold your secrets. 🙈 the .secrets.* is also included in `.gitignore` beware to not push your secrets to a public repo. 🎉 Dynaconf is configured! read more on https://dynaconf.com ``` > **TIP:** You can select `toml|yaml|json|ini|py` on `dynaconf init -f ` **toml** is the default and also the most recommended format for configuration. #### Dynaconf init creates the following files ```plain . ├── 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 settings (optional) ``` On the file `config.py` Dynaconf init generates the following boilerpate ```py from dynaconf import Dynaconf settings = Dynaconf( envvar_prefix="DYNACONF", # export envvars with `export DYNACONF_FOO=bar`. settings_files=['settings.yaml', '.secrets.yaml'], # Load files in the given order. ) ``` > **TIP:** You can create the files yourself instead of using the `init` command as shown above and you can give any name you want instead of the default `config.py` (the file must be in your importable python path) - See more options that you can pass to `Dynaconf` class initializer on https://dynaconf.com #### Using Dynaconf Put your settings on `settings.{toml|yaml|ini|json|py}` ```toml username = "admin" port = 5555 database = {name='mydb', schema='main'} ``` Put sensitive information on `.secrets.{toml|yaml|ini|json|py}` ```toml password = "secret123" ``` > **IMPORTANT:** `dynaconf init` command puts the `.secrets.*` in your `.gitignore` to avoid it to be exposed on public repos but it is your responsibility to keep it safe in your local environment, also the recommendation for production environments is to use the built-in support for Hashicorp Vault service for password and tokens. Optionally you can now use environment variables to override values per execution or per environment. ```bash # override `port` from settings.toml file and automatically casts as `int` value. export DYNACONF_PORT=9900 ``` On your code import the `settings` object ```py from path.to.project.config import settings # Reading the settings settings.username == "admin" # dot notation with multi nesting support settings.PORT == 9900 # case insensitive settings['password'] == "secret123" # dict like access settings.get("nonexisting", "default value") # Default values just like a dict settings.databases.name == "mydb" # Nested key traversing settings['databases.schema'] == "main" # Nested key traversing ``` ## More - Settings Schema Validation - Custom Settings Loaders - Vault Services - Template substitutions - etc... There is a lot more you can do, **read the docs:** http://dynaconf.com ## Contribute 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 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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/README.md0000644000000000000000000001447115156324234015030 0ustar00pbrochadpbrochad

dynaconf. new logo

> **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/) ![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 - Inspired by the [12-factor application guide](https://12factor.net/config) - Settings management (default values, validation, parsing, templating) - Protection of sensitive information (passwords/tokens) - Multiple file formats `toml|yaml|json|ini|py` and also customizable loaders. - Full support for environment variables to override existing settings (dotenv support included). - Optional layered system for multi environments `[default, development, testing, production]` - Built-in support for Hashicorp Vault and Redis as settings and secrets storage. - Built-in extensions for **Django** and **Flask** web frameworks. - CLI for common operations such as `init, list, write, validate, export`. - full docs on https://dynaconf.com ### Install ```bash $ pip install dynaconf ``` #### Initialize Dynaconf on project root directory ```plain $ cd path/to/your/project/ $ dynaconf init -f toml ⚙️ Configuring your Dynaconf environment ------------------------------------------ 🐍 The file `config.py` was generated. 🎛️ settings.toml created to hold your settings. 🔑 .secrets.toml created to hold your secrets. 🙈 the .secrets.* is also included in `.gitignore` beware to not push your secrets to a public repo. 🎉 Dynaconf is configured! read more on https://dynaconf.com ``` > **TIP:** You can select `toml|yaml|json|ini|py` on `dynaconf init -f ` **toml** is the default and also the most recommended format for configuration. #### Dynaconf init creates the following files ```plain . ├── 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 settings (optional) ``` On the file `config.py` Dynaconf init generates the following boilerpate ```py from dynaconf import Dynaconf settings = Dynaconf( envvar_prefix="DYNACONF", # export envvars with `export DYNACONF_FOO=bar`. settings_files=['settings.yaml', '.secrets.yaml'], # Load files in the given order. ) ``` > **TIP:** You can create the files yourself instead of using the `init` command as shown above and you can give any name you want instead of the default `config.py` (the file must be in your importable python path) - See more options that you can pass to `Dynaconf` class initializer on https://dynaconf.com #### Using Dynaconf Put your settings on `settings.{toml|yaml|ini|json|py}` ```toml username = "admin" port = 5555 database = {name='mydb', schema='main'} ``` Put sensitive information on `.secrets.{toml|yaml|ini|json|py}` ```toml password = "secret123" ``` > **IMPORTANT:** `dynaconf init` command puts the `.secrets.*` in your `.gitignore` to avoid it to be exposed on public repos but it is your responsibility to keep it safe in your local environment, also the recommendation for production environments is to use the built-in support for Hashicorp Vault service for password and tokens. Optionally you can now use environment variables to override values per execution or per environment. ```bash # override `port` from settings.toml file and automatically casts as `int` value. export DYNACONF_PORT=9900 ``` On your code import the `settings` object ```py from path.to.project.config import settings # Reading the settings settings.username == "admin" # dot notation with multi nesting support settings.PORT == 9900 # case insensitive settings['password'] == "secret123" # dict like access settings.get("nonexisting", "default value") # Default values just like a dict settings.databases.name == "mydb" # Nested key traversing settings['databases.schema'] == "main" # Nested key traversing ``` ## More - Settings Schema Validation - Custom Settings Loaders - Vault Services - Template substitutions - etc... There is a lot more you can do, **read the docs:** http://dynaconf.com ## Contribute 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 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. ././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9128332 dynaconf-3.2.13/dynaconf/0000755000000000000000000000000015156326623015347 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776227.0 dynaconf-3.2.13/dynaconf/VERSION0000644000000000000000000000000715156326543016415 0ustar00pbrochadpbrochad3.2.13 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/__init__.py0000644000000000000000000000237515156311032017453 0ustar00pbrochadpbrochadfrom __future__ import annotations from dynaconf.base import LazySettings from dynaconf.constants import DEFAULT_SETTINGS_FILES from dynaconf.contrib import DjangoDynaconf from dynaconf.contrib import FlaskDynaconf from dynaconf.hooking import post_hook from dynaconf.utils.inspect import get_history from dynaconf.utils.inspect import inspect_settings from dynaconf.utils.parse_conf import add_converter from dynaconf.utils.parse_conf import DynaconfFormatError from dynaconf.utils.parse_conf import DynaconfParseError from dynaconf.validator import ValidationError from dynaconf.validator import Validator settings = LazySettings( # This global `settings` is deprecated from v3.0.0+ # kept here for backwards compatibility # To Be Removed in 4.0.x warn_dynaconf_global_settings=True, environments=True, lowercase_read=False, load_dotenv=True, default_settings_paths=DEFAULT_SETTINGS_FILES, ) # This is the new recommended base class alias Dynaconf = LazySettings # noqa __all__ = [ "Dynaconf", "LazySettings", "Validator", "FlaskDynaconf", "ValidationError", "DjangoDynaconf", "add_converter", "inspect_settings", "get_history", "DynaconfFormatError", "DynaconfParseError", "post_hook", ] ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/__main__.py0000644000000000000000000000015115156324234017432 0ustar00pbrochadpbrochadfrom __future__ import annotations from dynaconf.cli import main if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/base.py0000644000000000000000000015213415156324234016635 0ustar00pbrochadpbrochadfrom __future__ import annotations import copy import importlib import inspect import os import warnings from collections import defaultdict from contextlib import contextmanager from contextlib import suppress from pathlib import Path from typing import Any from typing import Callable from dynaconf import default_settings from dynaconf.loaders import default_loader from dynaconf.loaders import enable_external_loaders from dynaconf.loaders import env_loader from dynaconf.loaders import execute_instance_hooks from dynaconf.loaders import execute_module_hooks from dynaconf.loaders import py_loader from dynaconf.loaders import settings_loader from dynaconf.loaders import yaml_loader from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import BANNER from dynaconf.utils import compat_kwargs from dynaconf.utils import ensure_a_list from dynaconf.utils import missing from dynaconf.utils import object_merge from dynaconf.utils import RENAMED_VARS from dynaconf.utils import upperfy from dynaconf.utils.boxing import DynaBox from dynaconf.utils.files import find_file from dynaconf.utils.files import glob from dynaconf.utils.functional import empty from dynaconf.utils.functional import LazyObject from dynaconf.utils.parse_conf import apply_converter from dynaconf.utils.parse_conf import boolean_fix from dynaconf.utils.parse_conf import converters from dynaconf.utils.parse_conf import Lazy from dynaconf.utils.parse_conf import parse_conf_data from dynaconf.utils.parse_conf import true_values from dynaconf.validator import ValidationError from dynaconf.validator import ValidatorList from dynaconf.vendor.box.box_list import BoxList class LazySettings(LazyObject): """Loads settings lazily from multiple sources: settings = Dynaconf( settings_files=["settings.toml"], # path/glob environments=True, # activate layered environments envvar_prefix="MYAPP", # `export MYAPP_FOO=bar` env_switcher="MYAPP_MODE", # `export MYAPP_MODE=production` load_dotenv=True, # read a .env file ) More options available on https://www.dynaconf.com/configuration/ """ def __init__(self, wrapped=None, **kwargs): """ handle initialization for the customization cases :param wrapped: a deepcopy of this object will be wrapped (issue #596) :param kwargs: values that overrides default_settings """ self._wrapper_class = kwargs.pop("_wrapper_class", Settings) self._warn_dynaconf_global_settings = kwargs.pop( "warn_dynaconf_global_settings", None ) # in 3.0.0 global settings is deprecated self.__resolve_config_aliases(kwargs) compat_kwargs(kwargs) self._kwargs = kwargs super().__init__() if wrapped: if self._django_override: # This fixes django issue #596 self._wrapped = copy.deepcopy(wrapped) else: self._wrapped = wrapped def __resolve_config_aliases(self, kwargs): """takes aliases for _FOR_DYNACONF configurations e.g: ROOT_PATH='/' is transformed into `ROOT_PATH_FOR_DYNACONF` """ misspells = { "settings_files": "settings_file", "SETTINGS_FILES": "SETTINGS_FILE", "environment": "environments", "ENVIRONMENT": "ENVIRONMENTS", } for misspell, correct in misspells.items(): if misspell in kwargs: kwargs[correct] = kwargs.pop(misspell) for_dynaconf_keys = { key for key in UPPER_DEFAULT_SETTINGS if key.endswith("_FOR_DYNACONF") } aliases = { key.upper() for key in kwargs if f"{key.upper()}_FOR_DYNACONF" in for_dynaconf_keys } for alias in aliases: value = kwargs.pop(alias, empty) if value is empty: value = kwargs.pop(alias.lower()) kwargs[f"{alias}_FOR_DYNACONF"] = value def __getattr__(self, name): """Allow getting keys from self.store using dot notation""" if self._wrapped is empty: self._setup() if name in self._wrapped._deleted: # noqa raise AttributeError( f"Attribute {name} was deleted, or belongs to different env" ) if name not in RESERVED_ATTRS: lowercase_mode = self._kwargs.get( "LOWERCASE_READ_FOR_DYNACONF", default_settings.LOWERCASE_READ_FOR_DYNACONF, ) if lowercase_mode is True: name = name.upper() if ( name.isupper() and ( self._wrapped._fresh or name in self._wrapped.FRESH_VARS_FOR_DYNACONF ) and name not in UPPER_DEFAULT_SETTINGS ): return self._wrapped.get_fresh(name) value = getattr(self._wrapped, name) return value def __call__(self, *args, **kwargs): """Allow direct call of settings('val') in place of settings.get('val') """ 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( boolean_fix(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.""" if self._warn_dynaconf_global_settings: warnings.warn( "Usage of `from dynaconf import settings` is now " "DEPRECATED in 3.0.0+. You are encouraged to change it to " "your own instance e.g: `settings = Dynaconf(*options)`", DeprecationWarning, ) self._wrapper_class = Settings # Force unhooked for this default_settings.reload(self._should_load_dotenv) environment_variable = self._kwargs.get( "ENVVAR_FOR_DYNACONF", default_settings.ENVVAR_FOR_DYNACONF ) settings_module = os.environ.get(environment_variable) self._wrapped = self._wrapper_class( settings_module=settings_module, **self._kwargs ) def configure(self, settings_module=None, **kwargs): """ Allows user to reconfigure settings object passing a new settings module or separated kwargs :param settings_module: defines the settings file :param kwargs: override default settings """ default_settings.reload(self._should_load_dotenv) environment_var = self._kwargs.get( "ENVVAR_FOR_DYNACONF", default_settings.ENVVAR_FOR_DYNACONF ) settings_module = settings_module or os.environ.get(environment_var) compat_kwargs(kwargs) kwargs.update(self._kwargs) self._wrapped = self._wrapper_class( settings_module=settings_module, **kwargs ) @property def configured(self): """If wrapped is configured""" return self._wrapped is not empty class Settings: """ Common logic for settings whether set by a module or by the user. """ dynaconf_banner = BANNER _store = DynaBox() def __init__(self, settings_module=None, **kwargs): # pragma: no cover """Execute loaders and custom initialization :param settings_module: defines the settings file :param kwargs: override default settings """ self._fresh = False self._loaded_envs = [] self._loaded_hooks = defaultdict(dict) self._loaded_py_modules = [] self._loaded_files = [] self._deleted = set() self._store = kwargs.pop("_store", DynaBox(box_settings=self)) self._env_cache = {} self._loaded_by_loaders: dict[SourceMetadata | str, Any] = {} self._loaders = [] self._defaults = DynaBox(box_settings=self) self.environ = os.environ self.SETTINGS_MODULE = None self.filter_strategy = kwargs.get("filter_strategy", None) 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) ) self._post_hooks: list[Callable] = ensure_a_list( kwargs.get("post_hooks", []) ) compat_kwargs(kwargs) if settings_module: self.set( "SETTINGS_FILE_FOR_DYNACONF", settings_module, loader_identifier="init_settings_module", ) for key, value in kwargs.items(): self.set( key, value, loader_identifier="init_kwargs", validate=False ) # execute loaders only after setting defaults got from kwargs self._defaults = kwargs # 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')` in place of `settings.get('val')` """ return self.get(*args, **kwargs) def __setattr__(self, name, value): """Allow `settings.FOO = 'value'` while keeping internal attrs.""" if name in RESERVED_ATTRS: super().__setattr__(name, value) else: self.set(name, value) def __delattr__(self, name): """stores reference in `_deleted` for proper error management""" self._deleted.add(name) if hasattr(self, name): super().__delattr__(name) def __contains__(self, item): """Respond to `item in settings`""" nested_sep = getattr(self, "NESTED_SEPARATOR_FOR_DYNACONF", None) if isinstance(item, str) and nested_sep and nested_sep in item: item = item.replace(nested_sep, ".") if isinstance(item, str) and "." in item: return self.get(item, default=missing) is not missing return item.upper() in self.store or item.lower() in self.store def __getattribute__(self, name): if ( name.startswith("__") or name in RESERVED_ATTRS + UPPER_DEFAULT_SETTINGS ): return super().__getattribute__(name) # This is to keep the only upper case mode working # self._store has Lazy values already evaluated if ( name.islower() and self._store.get("LOWERCASE_READ_FOR_DYNACONF", empty) is False ): try: # only matches exact casing, first levels always upper return self._store.__getattribute__(name) except KeyError: return super().__getattribute__(name) # then go to the regular .get which triggers hooks among other things value = self.get(name, default=empty) if value is empty: return super().__getattribute__(name) return value def __getitem__(self, item): """Allow getting variables as dict keys `settings['KEY']`""" value = self.get(item, default=empty) if value is empty: raise KeyError(f"{item} does not exist") return value def __setitem__(self, key, value): """Allow `settings['KEY'] = 'value'`""" self.set(key, value) @property def store(self): """Gets internal storage""" return self._store def __dir__(self): """Enable auto-complete for code editors""" return ( RESERVED_ATTRS + [k.lower() for k in self.keys()] + list(self.keys()) ) def __iter__(self): """Redirects to store object""" yield from self._store def items(self): """Redirects to store object""" return self._store.items() def keys(self): """Redirects to store object""" return self.store.keys() def values(self): """Redirects to store object""" return self.store.values() def setdefault( self, item, default, apply_default_on_none=False, env: str = "unknown" ): """Returns value if exists or set it as the given default apply_default_on_none: if True, default is set when value is None env: used to create the source identifier """ value = self.get(item, 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 ) ) ) loader_identifier = SourceMetadata("setdefault", "unique", env.lower()) if apply_default: self.set( item, default, loader_identifier=loader_identifier, tomlfy=True, ) return default return value def as_dict(self, env=None, internal=False): """Returns a dictionary with set key and values. :param env: Str env name, default self.current_env `DEVELOPMENT` :param internal: bool - should include dynaconf internal vars? """ ctx_mgr = suppress() if env is None else self.using_env(env) with ctx_mgr: data = self.store.to_dict().copy() # if not internal remove internal settings if not internal: for name in UPPER_DEFAULT_SETTINGS: data.pop(name, None) return data to_dict = as_dict # backwards compatibility def _dotted_get( self, dotted_key, default=None, parent=None, cast=None, **kwargs ): """ Perform dotted key lookups and keep track of where we are. :param key: The name of the setting value, will always be upper case :param default: In case of not found it will be returned :param parent: Is there a pre-loaded parent in a nested data? """ split_key = dotted_key.split(".") name, keys = split_key[0], split_key[1:] result = self.get(name, default=default, parent=parent, **kwargs) # If we've reached the end, or parent key not found, then return result if not keys or result == default: if cast and cast in converters: return apply_converter(cast, result, box_settings=self) elif cast is True: return parse_conf_data(result, tomlfy=True, box_settings=self) return result # Still keys left, but current result/parent is not a data container if keys and not isinstance(result, (dict, list)): result_type = type(result).__name__ raise AttributeError( f"Invalid dotted lookup in {dotted_key}. {name} is a {result_type}" ) # If we've still got key elements to traverse, let's do that. return self._dotted_get( ".".join(keys), default=default, parent=result, cast=cast, **kwargs ) def get( self, key, default=None, cast=None, fresh=False, dotted_lookup=empty, parent=None, sysenv_fallback=None, ) -> Any: """ Get a value from settings store, this is the preferred way to access:: >>> from dynaconf import settings >>> settings.get('KEY') :param key: The name of the setting value, will always be upper case :param default: In case of not found it will be returned :param cast: Should cast in to @int, @float, @bool or @json ? :param fresh: Should reload from loaders store before access? :param dotted_lookup: Should perform dotted-path lookup? :param parent: Is there a pre-loaded parent in a nested data? :param sysenv_fallback: Should fallback to system environ if not found? :return: The value if found, default or None """ if sysenv_fallback is None: sysenv_fallback = self._store.get("SYSENV_FALLBACK_FOR_DYNACONF") nested_sep = self._store.get("NESTED_SEPARATOR_FOR_DYNACONF") if isinstance(key, str): if nested_sep and nested_sep in key: # 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, default=default, cast=cast, fresh=fresh, parent=parent, ) key = upperfy(key) # handles system environment fallback if default is None: key_in_sysenv_fallback_list = isinstance( sysenv_fallback, list ) and key in [upperfy(k) for k in sysenv_fallback] if sysenv_fallback is True or key_in_sysenv_fallback_list: default = self.get_environ(key, cast=True) # default values should behave exactly Dynaconf parsed values if default is not None: if isinstance(default, list): default = BoxList(default) elif isinstance(default, dict): default = DynaBox(default) if key in self._deleted: return default if ( fresh or self._fresh or key in getattr(self, "FRESH_VARS_FOR_DYNACONF", ()) ) and key not in UPPER_DEFAULT_SETTINGS: self.unset(key) self.execute_loaders(key=key) data = _get_with_default(parent or self.store, key, default) if cast: data = apply_converter(cast, data, box_settings=self) return data def exists(self, key, fresh=False): """Check if key exists :param key: the name of setting variable :param fresh: if key should be taken from source directly :return: Boolean """ key = upperfy(key) if key in self._deleted: return False return self.get(key, fresh=fresh, default=missing) is not missing def get_fresh(self, key, default=None, cast=None): """This is a shortcut to `get(key, fresh=True)`. always reload from loaders store before getting the var. :param key: The name of the setting value, will always be upper case :param default: In case of not found it will be returned :param cast: Should cast in to @int, @float, @bool or @json ? :return: The value if found, default or None """ return self.get(key, default=default, cast=cast, fresh=True) def get_environ(self, key, default=None, cast=None): """Get value from environment variable using os.environ.get :param key: The name of the setting value, will always be upper case :param default: In case of not found it will be returned :param cast: Should cast in to @int, @float, @bool or @json ? or cast must be true to use cast inference :return: The value if found, default or None """ key = upperfy(key) data = self.environ.get(key, default) if data: if cast in converters: data = apply_converter(cast, data, box_settings=self) elif cast is True: data = parse_conf_data( boolean_fix(data), tomlfy=True, box_settings=self ) return data def exists_in_environ(self, key): """Return True if env variable is exported""" return upperfy(key) in self.environ def as_bool(self, key): """Partial method for get with bool cast""" return self.get(key, cast="@bool") def as_int(self, key): """Partial method for get with int cast""" return self.get(key, cast="@int") def as_float(self, key): """Partial method for get with float cast""" return self.get(key, cast="@float") def as_json(self, key): """Partial method for get with json cast""" return self.get(key, cast="@json") @property def loaded_envs(self): """Get or create internal loaded envs list""" if not self._loaded_envs: self._loaded_envs = [] return self._loaded_envs @loaded_envs.setter def loaded_envs(self, value): """Setter for env list""" self._loaded_envs = value # compat loaded_namespaces = loaded_envs @property def loaded_by_loaders(self): # pragma: no cover """Gets the internal mapping of LOADER -> values""" return self._loaded_by_loaders def from_env(self, env="", keep=False, **kwargs): """Return a new isolated settings object pointing to specified env. Example of settings.toml:: [development] message = 'This is in dev' [other] message = 'this is in other env' Program:: >>> from dynaconf import settings >>> print(settings.MESSAGE) 'This is in dev' >>> print(settings.from_env('other').MESSAGE) 'This is in other env' # The existing settings object remains the same. >>> print(settings.MESSAGE) 'This is in dev' Arguments: env {str} -- Env to load (development, production, custom) Keyword Arguments: keep {bool} -- Keep pre-existing values (default: {False}) kwargs {dict} -- Passed directly to new instance. """ cache_key = f"{env}_{keep}_{kwargs}" if cache_key in self._env_cache: return self._env_cache[cache_key] new_data = { key: self.get(key) for key in UPPER_DEFAULT_SETTINGS if key not in RENAMED_VARS } if self.filter_strategy: # Retain the filtering strategy when switching environments new_data["filter_strategy"] = self.filter_strategy # This is here for backwards compatibility # To be removed on 4.x.x default_settings_paths = self.get("default_settings_paths") if default_settings_paths: # pragma: no cover new_data["default_settings_paths"] = default_settings_paths if keep: # keep existing values from current env new_data.update( { key: value for key, value in self.store.to_dict().copy().items() if key.isupper() and key not in RENAMED_VARS } ) new_data.update(kwargs) new_data["FORCE_ENV_FOR_DYNACONF"] = env new_settings = LazySettings(**new_data) self._env_cache[cache_key] = new_settings # update source metadata for inspecting self._loaded_by_loaders.update(new_settings._loaded_by_loaders) return new_settings @contextmanager def using_env(self, env, clean=True, silent=True, filename=None): """ This context manager allows the contextual use of a different env Example of settings.toml:: [development] message = 'This is in dev' [other] message = 'this is in other env' Program:: >>> from dynaconf import settings >>> print settings.MESSAGE 'This is in dev' >>> with settings.using_env('OTHER'): ... print settings.MESSAGE 'this is in other env' :param env: Upper case name of env without any _ :param clean: If preloaded vars should be cleaned :param silent: Silence errors :param filename: Custom filename to load (optional) :return: context """ try: self.setenv(env, clean=clean, silent=silent, filename=filename) yield finally: if env.lower() != self.ENV_FOR_DYNACONF.lower(): del self.loaded_envs[-1] self.setenv(self.current_env, clean=clean, filename=filename) # compat using_namespace = using_env @contextmanager def fresh(self): """ this context manager force the load of a key direct from the store:: $ export DYNACONF_VALUE='Original' >>> from dynaconf import settings >>> print settings.VALUE 'Original' $ export DYNACONF_VALUE='Changed Value' >>> print settings.VALUE # will not be reloaded from env vars 'Original >>> with settings.fresh(): # inside this context all is reloaded ... print settings.VALUE 'Changed Value' an alternative is using `settings.get_fresh(key)` :return: context """ self._fresh = True yield self._fresh = False @property def current_env(self): """Return the current active env""" if self.ENVIRONMENTS_FOR_DYNACONF is False: 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: return self.loaded_envs[-1] except IndexError: return self.ENV_FOR_DYNACONF # compat current_namespace = current_env @property def settings_module(self): """Gets SETTINGS_MODULE variable""" settings_module = parse_conf_data( os.environ.get( self.ENVVAR_FOR_DYNACONF, self.SETTINGS_FILE_FOR_DYNACONF ), tomlfy=True, box_settings=self, ) if settings_module != getattr(self, "SETTINGS_MODULE", None): self.set( "SETTINGS_MODULE", settings_module, loader_identifier="settings_module_method", ) # This is for backewards compatibility, to be removed on 4.x.x if not self.SETTINGS_MODULE and self.get("default_settings_paths"): self.SETTINGS_MODULE = self.get("default_settings_paths") return self.SETTINGS_MODULE # Backwards compatibility see #169 settings_file = settings_module def setenv(self, env=None, clean=True, silent=True, filename=None): """Used to interactively change the env Example of settings.toml:: [development] message = 'This is in dev' [other] message = 'this is in other env' Program:: >>> from dynaconf import settings >>> print settings.MESSAGE 'This is in dev' >>> with settings.using_env('OTHER'): ... print settings.MESSAGE 'this is in other env' :param env: Upper case name of env without any _ :param clean: If preloaded vars should be cleaned :param silent: Silence errors :param filename: Custom filename to load (optional) :return: context """ env = env or self.ENV_FOR_DYNACONF if not isinstance(env, str) or " " in env: raise ValueError("env should be a string without spaces") env = env.upper() if env != self.ENV_FOR_DYNACONF: self.loaded_envs.append(env) else: self.loaded_envs = [] if clean: self.clean(env=env) self.execute_loaders(env=env, silent=silent, filename=filename) # compat namespace = setenv def clean(self, *args, **kwargs): """Clean all loaded values to reload when switching envs""" for key in list(self.store.keys()): self.unset(key) def unset(self, key, force=False): """Unset on all references :param key: The key to be unset :param force: Bypass default checks and force unset """ key = upperfy(key.strip()) if ( key not in UPPER_DEFAULT_SETTINGS and key not in self._defaults or force ): with suppress(KeyError, AttributeError): # AttributeError can happen when a LazyValue consumes # a previously deleted key delattr(self, key) del self.store[key] def unset_all(self, keys, force=False): # pragma: no cover """Unset based on a list of keys :param keys: a list of keys :param force: Bypass default checks and force unset """ for key in keys: self.unset(key, force=force) def _dotted_set( self, dotted_key, value, tomlfy=False, validate=empty, **kwargs ): """Sets dotted keys as nested dictionaries. Dotted set will always reassign the value, to merge use `@merge` token Arguments: dotted_key {str} -- A traversal name e.g: foo.bar.zaz value {Any} -- The value to set to the nested value. Keyword Arguments: tomlfy {bool} -- Perform toml parsing (default: {False}) validate {bool} -- """ if validate is empty: validate = self.get( "VALIDATE_ON_UPDATE_FOR_DYNACONF" ) # pragma: nocover split_keys = dotted_key.split(".") existing_data = self.get(split_keys[0], {}) new_data = tree = DynaBox(box_settings=self) for k in split_keys[:-1]: tree = tree.setdefault(k, {}) value = parse_conf_data(value, tomlfy=tomlfy, box_settings=self) tree[split_keys[-1]] = value if existing_data: old_data = DynaBox( {split_keys[0]: existing_data}, box_settings=self ) new_data = object_merge( old=old_data, new=new_data, full_path=split_keys, ) self.update(data=new_data, tomlfy=tomlfy, validate=validate, **kwargs) def set( self, key, value, loader_identifier: SourceMetadata | str | None = None, tomlfy=False, dotted_lookup=empty, is_secret="DeprecatedArgument", # noqa validate=empty, merge=empty, ): """Set a value storing references for the loader :param key: The key to store. Can be of any type. :param value: The raw value to parse and store :param loader_identifier: Optional loader name e.g: toml, yaml etc. Or instance of SourceMetadata :param tomlfy: Bool define if value is parsed by toml (defaults False) :param merge: Bool define if existing nested data will be merged. :param validate: Bool define if validation will be triggered """ # Ensure source_metadata always is set even if set is called # without a loader_identifier if isinstance(loader_identifier, str) or loader_identifier is None: source_metadata = SourceMetadata( loader="set_method", identifier=loader_identifier or "undefined", merged=merge is True, ) else: # loader identifier must be a SourceMetadata instance source_metadata = loader_identifier if validate is empty: validate = self.get("VALIDATE_ON_UPDATE_FOR_DYNACONF") if dotted_lookup is empty: dotted_lookup = self.get("DOTTED_LOOKUP_FOR_DYNACONF") nested_sep = self.get("NESTED_SEPARATOR_FOR_DYNACONF") if isinstance(key, str): if nested_sep and nested_sep in key: key = key.replace(nested_sep, ".") # FOO__bar -> FOO.bar if "." in key and dotted_lookup is True: return self._dotted_set( key, value, loader_identifier=source_metadata, tomlfy=tomlfy, validate=validate, ) key = upperfy(key.strip()) parsed = parse_conf_data(value, tomlfy=tomlfy, box_settings=self) # Fix for #869 - The call to getattr trigger early evaluation existing = ( self.store.get(key, None) if not isinstance(parsed, Lazy) else None ) if getattr(parsed, "_dynaconf_insert", False): # `@insert` calls insert in a list by index if existing and isinstance(existing, list): source_metadata = source_metadata._replace(merged=True) existing.insert(parsed.index, parsed.unwrap()) parsed = existing else: parsed = [parsed.unwrap()] if getattr(parsed, "_dynaconf_del", None): self.unset(key, force=True) # `@del` in a first level var. return if getattr(parsed, "_dynaconf_reset", False): # pragma: no cover parsed = parsed.unwrap() # `@reset` in a first level var. if getattr(parsed, "_dynaconf_merge_unique", False): # `@merge_unique` in a first level var if existing: # update SourceMetadata (for inspecting purposes) source_metadata = source_metadata._replace(merged=True) parsed = object_merge(existing, parsed.unwrap(), unique=True) else: parsed = parsed.unwrap() if getattr(parsed, "_dynaconf_merge", False): # `@merge` in a first level var if existing: # update SourceMetadata (for inspecting purposes) source_metadata = source_metadata._replace(merged=True) parsed = object_merge(existing, parsed.unwrap()) else: parsed = parsed.unwrap() should_merge = existing is not None and existing != parsed if should_merge: # `dynaconf_merge` used in file root `merge=True` if merge and merge is not empty: source_metadata = source_metadata._replace(merged=True) parsed = object_merge(existing, parsed) else: # `dynaconf_merge` may be used within the key structure # Or merge_enabled is set to True parsed, source_metadata = self._merge_before_set( existing, parsed, source_metadata, context_merge=merge ) if isinstance(parsed, dict) and not isinstance(parsed, DynaBox): parsed = DynaBox(parsed, box_settings=self) # Set the parsed value self.store[key] = parsed self._deleted.discard(key) # check if str because we can't directly set/get non-str with obj. e.g. # setting.1 # settings.(1,2) if isinstance(key, str): super().__setattr__(key, parsed) # Track history for inspect, store the raw_value if source_metadata in self._loaded_by_loaders: self._loaded_by_loaders[source_metadata][key] = value else: self._loaded_by_loaders[source_metadata] = {key: value} if loader_identifier is None: # if .set is called without loader identifier it becomes # a default value and goes away only when explicitly unset self._defaults[key] = parsed if validate is True: self.validators.validate() def update( self, data=None, loader_identifier=None, tomlfy=False, merge=empty, is_secret="DeprecatedArgument", # noqa dotted_lookup=empty, validate=empty, **kwargs, ): """ Update values in the current settings object without saving in stores:: >>> from dynaconf import settings >>> print settings.NAME 'Bruno' >>> settings.update({'NAME': 'John'}, other_value=1) >>> print settings.NAME 'John' >>> print settings.OTHER_VALUE 1 :param data: Data to be updated :param loader_identifier: Only to be used by custom loaders :param tomlfy: Bool define if value is parsed by toml (defaults False) :param merge: Bool define if existing nested data will be merged. :param validate: Bool define if validators will trigger automatically :param kwargs: extra values to update :return: None """ if validate is empty: validate = self.get("VALIDATE_ON_UPDATE_FOR_DYNACONF") data = data or {} data.update(kwargs) for key, value in data.items(): # update() will handle validation later with suppress(ValidationError): self.set( key, value, loader_identifier=loader_identifier, tomlfy=tomlfy, merge=merge, dotted_lookup=dotted_lookup, validate=validate, ) # handle param `validate` if validate is True: self.validators.validate() elif validate == "all": self.validators.validate_all() def _merge_before_set( self, existing, value, identifier: SourceMetadata | None = None, context_merge=empty, ): """ Merge the new value being set with the existing value before set Returns the merged value and the updated identifier (for inspecting). """ # context_merge may come from file_scope or env_scope if context_merge is empty: context_merge = self.get("MERGE_ENABLED_FOR_DYNACONF") if isinstance(value, dict): local_merge = value.pop( "dynaconf_merge", value.pop("dynaconf_merge_unique", None) ) if local_merge not in (True, False, None) and not value: # In case `dynaconf_merge:` holds value not boolean - ref #241 value = local_merge if local_merge or (context_merge and local_merge is not False): identifier = ( identifier._replace(merged=True) if identifier else None ) value = object_merge(existing, value) if isinstance(value, (list, tuple)): value = list(value) local_merge = None unique = False if "dynaconf_merge" in value: value.remove("dynaconf_merge") local_merge = True elif "dynaconf_merge_unique" in value: value.remove("dynaconf_merge_unique") local_merge = True unique = True if local_merge or (context_merge and local_merge is not False): identifier = ( identifier._replace(merged=True) if identifier else None ) value = object_merge(existing, value, unique=unique) return value, identifier @property def loaders(self): # pragma: no cover """Return available loaders""" if self.LOADERS_FOR_DYNACONF in (None, 0, "0", "false", False): return [] if not self._loaders: self._loaders = self.LOADERS_FOR_DYNACONF return [importlib.import_module(loader) for loader in self._loaders] def reload(self, env=None, silent=None): # pragma: no cover """Clean end Execute all loaders""" self.clean() self._loaded_hooks.clear() for hook in self._post_hooks: with suppress(AttributeError, TypeError): hook._called = False self.execute_loaders(env, silent) def execute_loaders( self, env=None, silent=None, key=None, filename=None, loaders=None ): """Execute all internal and registered loaders :param env: The environment to load :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 """ if key is None: default_loader(self, self._defaults) env = (env or self.current_env).upper() silent = silent or self.SILENT_ERRORS_FOR_DYNACONF if loaders is None: self.pre_load(env, silent=silent, key=key) settings_loader( self, env=env, silent=silent, key=key, filename=filename ) self.load_extra_yaml(env, silent, key) # DEPRECATED enable_external_loaders(self) loaders = self.loaders # non setting_file or py_module loaders for core_loader in loaders: core_loader.load(self, env, silent=silent, key=key) self.load_includes(env, silent=silent, key=key) # execute hooks execute_module_hooks("post", self, env, silent=silent, key=key) execute_instance_hooks(self, "post", self._post_hooks) def pre_load(self, env, silent, key): """Do we have any file to pre-load before main settings file?""" preloads = self.get("PRELOAD_FOR_DYNACONF", []) if preloads: self.load_file(path=preloads, env=env, silent=silent, key=key) def load_includes(self, env, silent, key): """Do we have any nested includes we need to process?""" includes = ensure_a_list(self.get("DYNACONF_INCLUDE")) includes.extend(ensure_a_list(self.get("INCLUDES_FOR_DYNACONF"))) if includes: self.load_file(path=includes, env=env, silent=silent, key=key) # ensure env vars are the last thing loaded after all includes last_loader = self.loaders and self.loaders[-1] if last_loader and last_loader == env_loader: last_loader.load(self, env, silent, key) def load_file( self, path=None, env=None, silent=True, key=None, validate=empty, run_hooks=True, ): """Programmatically load files from ``path``. When using relative paths, the basedir fallbacks in this order: - ROOT_PATH_FOR_DYNACONF - Directory of the last loaded file - CWD :param path: A single filename, a glob or a file list :param env: Which env to load from file (default current_env) :param silent: Should raise errors? :param key: Load a single key? :param validate: Should trigger validation? :param run_hooks: Should run collected hooks? """ files = ensure_a_list(path) if not files: # a glob pattern may return empty return if validate is empty: validate = self.get("VALIDATE_ON_UPDATE_FOR_DYNACONF") env = "_envless" if env is False else (env or self.current_env).upper() # Using inspect take the filename and line number of the caller # to be used in the source_metadata frame = inspect.currentframe() caller = inspect.getouterframes(frame)[1] already_loaded = set() for _filename in files: # load_file() will handle validation later with suppress(ValidationError): source_metadata = SourceMetadata( loader=f"load_file@{caller.filename}:{caller.lineno}", identifier=_filename, env=env, ) if py_loader.try_to_load_from_py_module_name( obj=self, name=_filename, silent=True, identifier=source_metadata, ): # if it was possible to load from module name # continue the loop. continue 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(root_dir, str(_filename)) paths = [p for p in sorted(glob(filepath)) if ".local." not in p] local_paths = [p for p in sorted(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 continue # load_file() will handle validation later with suppress(ValidationError): source_metadata = SourceMetadata( loader=f"load_file@{caller.filename}:{caller.lineno}", identifier=path, env=env, ) settings_loader( obj=self, env=env, silent=silent, key=key, filename=path, validate=validate, identifier=source_metadata, ) already_loaded.add(path) if run_hooks: # this will call any collected hook that was not called yet execute_instance_hooks( self, "post", [ _hook for _hook in self._post_hooks if getattr(_hook, "_dynaconf_hook", False) is True and not getattr(_hook, "_called", False) ], ) # handle param `validate` if validate is True: self.validators.validate() elif validate == "all": self.validators.validate_all() @property def _root_path(self): """ROOT_PATH_FOR_DYNACONF or the path of first loaded file or '.'""" if self.ROOT_PATH_FOR_DYNACONF is not None: return self.ROOT_PATH_FOR_DYNACONF if self._loaded_files: # called once root_path = os.path.dirname(self._loaded_files[0]) self.set( "ROOT_PATH_FOR_DYNACONF", root_path, loader_identifier="_root_path", ) return root_path def load_extra_yaml(self, env, silent, key): """This is deprecated, kept for compat .. deprecated:: 1.0.0 Use multiple settings or INCLUDES_FOR_DYNACONF files instead. """ if self.get("YAML") is not None: warnings.warn( "The use of YAML var is deprecated, please define multiple " "filepaths instead: " "e.g: SETTINGS_FILE_FOR_DYNACONF = " "'settings.py,settings.yaml,settings.toml' or " "INCLUDES_FOR_DYNACONF=['path.toml', 'folder/*']" ) yaml_loader.load( self, env=env, filename=self.find_file(self.get("YAML")), silent=silent, key=key, ) def path_for(self, *args): """Path containing _root_path""" if args and args[0].startswith(os.path.sep): return os.path.join(*args) return os.path.join(self._root_path or os.getcwd(), *args) def find_file(self, *args, **kwargs): kwargs.setdefault("project_root", self._root_path) kwargs.setdefault( "skip_files", self.get("SKIP_FILES_FOR_DYNACONF", []) ) return find_file(*args, **kwargs) def flag(self, key, env=None): """Feature flagging system write flags to redis $ dynaconf write redis -s DASHBOARD=1 -e premiumuser meaning: Any premium user has DASHBOARD feature enabled In your program do:: # premium user has access to dashboard? >>> if settings.flag('dashboard', 'premiumuser'): ... activate_dashboard() The value is ensured to be loaded fresh from redis server It also works with file settings but the recommended is redis as the data can be loaded once it is updated. :param key: The flag name :param env: The env to look for """ env = env or self.ENVVAR_PREFIX_FOR_DYNACONF or "DYNACONF" with self.using_env(env): value = self.get_fresh(key) return value is True or value in true_values def populate_obj( self, obj, keys=None, ignore=None, internal=False, convert_to_dict=False, ): """Given the `obj` populate it using self.store items. :param obj: An object to be populated, a class instance. :param keys: A list of keys to be included. :param ignore: A list of keys to be excluded. :param internal: Include internal keys. :param convert_to_dict: Convert the settings to a pure dict (no Box) before populating. """ data = self.to_dict(internal=internal) if convert_to_dict else self keys = keys or self.keys() for key in keys: key = upperfy(key) if not internal: if key in UPPER_DEFAULT_SETTINGS: continue if ignore and key in ignore: continue value = data.get(key, empty) if value is not empty: setattr(obj, key, value) def dynaconf_clone(self): """Clone the current settings object.""" try: return copy.deepcopy(self) except (TypeError, copy.Error): # 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 new_data["_registered_hooks"] = {} new_data["_REGISTERED_HOOKS"] = {} return self.__class__(**new_data) @property def dynaconf(self): """A proxy to access internal methods and attributes Starting in 3.0.0 Dynaconf now allows first level lower case keys that are not reserved keyword, so this is a proxy to internal methods and attrs. """ class AttrProxy: def __init__(self, obj): self.obj = obj def __getattr__(self, name): return getattr(self.obj, f"dynaconf_{name}") return AttrProxy(self) @property def logger(self): # pragma: no cover """backwards compatibility with pre 3.0 loaders In dynaconf 3.0.0 logger and debug messages has been removed. """ warnings.warn( "logger and DEBUG messages has been removed on dynaconf 3.0.0" ) import logging # noqa return logging.getLogger("dynaconf") def is_overridden(self, setting): # noqa """This is to provide Django DJDT support: issue 382""" return False """Upper case default settings""" UPPER_DEFAULT_SETTINGS = [k for k in dir(default_settings) if k.isupper()] """Attributes created on Settings before 3.0.0""" RESERVED_ATTRS = ( [ item[0] for item in inspect.getmembers(LazySettings) if not item[0].startswith("__") ] + [ item[0] for item in inspect.getmembers(Settings) if not item[0].startswith("__") ] + [ "_defaults", "_deleted", "_env_cache", "_fresh", "_kwargs", "_loaded_by_loaders", "_loaded_envs", "_loaded_hooks", "_loaded_py_modules", "_loaded_files", "_loaders", "_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", "_post_hooks", "_registered_hooks", "_REGISTERED_HOOKS", ] ) # These are special fields defined by Dynaconf, but users can access it _PUBLIC_PROPERTIES = [ name for name, _ in inspect.getmembers( Settings, lambda x: isinstance(x, property) ) ] def _get_with_default(data: dict | list, key: str, default): if isinstance(data, dict): return data.get(key, default) elif isinstance(data, list): if not key.isdigit(): raise ValueError(f"Expected integer, got: {key}") try: return data[int(key)] except KeyError: return default else: raise AttributeError(f"Unknown data container type: {type(data)}") ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/cli.py0000644000000000000000000006743715156324234016505 0ustar00pbrochadpbrochadfrom __future__ import annotations import importlib import inspect as python_inspect import json import os import pprint import sys import warnings import webbrowser from contextlib import redirect_stdout from contextlib import suppress from pathlib import Path from dynaconf import constants from dynaconf import default_settings from dynaconf import LazySettings from dynaconf import loaders from dynaconf import settings as legacy_settings from dynaconf.base import Settings from dynaconf.loaders.py_loader import get_module from dynaconf.utils import prepare_json from dynaconf.utils import upperfy from dynaconf.utils.files import read_file from dynaconf.utils.functional import empty from dynaconf.utils.inspect import builtin_dumpers from dynaconf.utils.inspect import EnvNotFoundError from dynaconf.utils.inspect import inspect_settings from dynaconf.utils.inspect import KeyNotFoundError from dynaconf.utils.inspect import OutputFormatError from dynaconf.utils.inspect import print_debug_info 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 = None with suppress(FileNotFoundError): CWD = Path.cwd() EXTS = ["ini", "toml", "yaml", "json", "py", "env"] WRITERS = ["ini", "toml", "yaml", "json", "py", "redis", "vault", "env"] ENC = default_settings.ENCODING_FOR_DYNACONF def set_settings(ctx, instance=None): """Pick correct settings instance and set it to a global variable.""" global settings settings = None _echo_enabled = ctx.invoked_subcommand not in [ "get", "inspect", "debug-info", None, ] if "--json" in sys.argv: _echo_enabled = False if instance is not None: if ctx.invoked_subcommand in ["init"]: raise click.UsageError( "-i/--instance option is not allowed for `init` command" ) sys.path.insert(0, ".") settings = import_settings(instance) elif "FLASK_APP" in os.environ: # pragma: no cover with suppress(ImportError, click.UsageError): from flask.cli import ScriptInfo # noqa from dynaconf import FlaskDynaconf app_import_path = os.environ["FLASK_APP"] flask_app = ScriptInfo(app_import_path).load_app() settings = FlaskDynaconf(flask_app, **flask_app.config).settings if _echo_enabled: click.echo( click.style( "Flask app detected", fg="white", bg="bright_black" ) ) elif "DJANGO_SETTINGS_MODULE" in os.environ: # pragma: no cover sys.path.insert(0, os.path.abspath(os.getcwd())) import django # noqa django.setup() # ensure django is setup to avoid AppRegistryNotReady settings_module = import__django_settings( os.environ["DJANGO_SETTINGS_MODULE"] ) found_settings = python_inspect.getmembers( settings_module, lambda item: isinstance(item, (LazySettings, Settings)), ) if found_settings: settings = found_settings[0][1] if settings is not None and _echo_enabled: click.echo( click.style( "Django app detected", fg="white", bg="bright_black" ) ) if settings is None: if instance is None and "--help" not in sys.argv: if ctx.invoked_subcommand and ctx.invoked_subcommand not in [ "init", ]: warnings.warn( "Starting on 3.x the param --instance/-i is now required. " "try passing it `dynaconf -i path.to.settings ` " "Example `dynaconf -i config.settings list` " ) settings = legacy_settings else: settings = LazySettings(create_new_settings=True) else: settings = LazySettings() def import__django_settings(django_settings_module): """Import the Django settings module from the string importable path.""" try: with redirect_stdout(None): module = importlib.import_module(django_settings_module) except ImportError as e: raise click.UsageError(e) except FileNotFoundError: return return module def import_settings(dotted_path): """Import settings instance from python dotted path. Last item in dotted path must be settings instance. Example: import_settings('path.to.settings') """ if "." in dotted_path: module, name = dotted_path.rsplit(".", 1) else: raise click.UsageError( f"invalid path to settings instance: {dotted_path}" ) try: with redirect_stdout(None): 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: raise click.UsageError(e) def split_vars(_vars): """Splits values like foo=bar=zaz in {'foo': 'bar=zaz'}""" return ( { upperfy(k.strip()): parse_conf_data( v.strip(), tomlfy=True, box_settings=settings ) for k, _, v in [item.partition("=") for item in _vars] } if _vars else {} ) def read_file_in_root_directory(*names, **kwargs): """Read a file on root dir.""" return read_file( os.path.join(os.path.dirname(__file__), *names), encoding=kwargs.get("encoding", "utf-8"), ) def print_version(ctx, param, value): if not value or ctx.resilient_parsing: return click.echo(read_file_in_root_directory("VERSION")) ctx.exit() def open_docs(ctx, param, value): # pragma: no cover if not value or ctx.resilient_parsing: return url = "https://dynaconf.com/" webbrowser.open(url, new=2) click.echo(f"{url} opened in browser") ctx.exit() def show_banner(ctx, param, value): """Shows dynaconf awesome banner""" if not value or ctx.resilient_parsing: return set_settings(ctx) click.echo(settings.dynaconf_banner) click.echo("Learn more at: http://github.com/dynaconf/dynaconf") ctx.exit() @click.group() @click.option( "--version", is_flag=True, callback=print_version, expose_value=False, is_eager=True, help="Show dynaconf version", ) @click.option( "--docs", is_flag=True, callback=open_docs, expose_value=False, is_eager=True, help="Open documentation in browser", ) @click.option( "--banner", is_flag=True, callback=show_banner, expose_value=False, is_eager=True, help="Show awesome banner", ) @click.option( "--instance", "-i", default=None, envvar="INSTANCE_FOR_DYNACONF", help="Custom instance of LazySettings", ) @click.pass_context def main(ctx, instance): """Dynaconf - Command Line Interface\n Documentation: https://dynaconf.com/ """ set_settings(ctx, instance) @main.command() @click.option( "--format", "fileformat", "-f", default="toml", type=click.Choice(EXTS) ) @click.option( "--path", "-p", default=CWD, help="defaults to current directory" ) @click.option( "--env", "-e", default=None, help="deprecated command (kept for compatibility but unused)", ) @click.option( "--vars", "_vars", "-v", multiple=True, default=None, help=( "extra values to write to settings file " "e.g: `dynaconf init -v NAME=foo -v X=2`" ), ) @click.option( "--secrets", "_secrets", "-s", multiple=True, default=None, help=( "secret key values to be written in .secrets " "e.g: `dynaconf init -s TOKEN=kdslmflds" ), ) @click.option("--wg/--no-wg", default=True) @click.option("-y", default=False, is_flag=True) @click.option("--django", default=os.environ.get("DJANGO_SETTINGS_MODULE")) @click.pass_context def init(ctx, fileformat, path, env, _vars, _secrets, wg, y, django): """ Inits a dynaconf project. By default it creates a settings.toml and a .secrets.toml for [default|development|staging|testing|production|global] envs. The format of the files can be changed passing --format=yaml|json|ini|py. This command must run on the project's root folder or you must pass --path=/myproject/root/folder. The --env/-e is deprecated (kept for compatibility but unused) """ 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" ) sys.exit(1) path = Path(path) if env is not None: click.secho( "⚠️ The --env/-e option is deprecated (kept for\n" " compatibility but unused)\n", fg="red", bold=True, # stderr=True, ) if settings.get("create_new_settings") is True: filename = Path("config.py") if not filename.exists(): with open(filename, "w") as new_settings: new_settings.write( constants.INSTANCE_TEMPLATE.format( settings_files=[ f"settings.{fileformat}", f".secrets.{fileformat}", ] ) ) click.echo( "🐍 The file `config.py` was generated.\n" " on your code now use `from config import settings`.\n" " (you must have `config` importable in your PYTHONPATH).\n" ) else: click.echo( f"⁉️ You already have a {filename} so it is not going to be\n" " generated for you, you will need to create your own \n" " settings instance e.g: config.py \n" " from dynaconf import Dynaconf \n" " settings = Dynaconf(**options)\n" ) sys.path.append(str(path)) set_settings(ctx, "config.settings") env = settings.current_env.lower() loader = importlib.import_module(f"dynaconf.loaders.{fileformat}_loader") # Turn foo=bar=zaz in {'foo': 'bar=zaz'} env_data = split_vars(_vars) _secrets = split_vars(_secrets) # create placeholder data for every env settings_data = {} secrets_data = {} if env_data: settings_data[env] = env_data settings_data["default"] = {k: "a default value" for k in env_data} if _secrets: secrets_data[env] = _secrets secrets_data["default"] = {k: "a default value" for k in _secrets} if str(path).endswith( constants.ALL_EXTENSIONS + ("py",) ): # pragma: no cover # noqa settings_path = path secrets_path = path.parent / f".secrets.{fileformat}" gitignore_path = path.parent / ".gitignore" else: if fileformat == "env": if str(path) in (".env", "./.env"): # pragma: no cover settings_path = path elif str(path).endswith("/.env"): # pragma: no cover settings_path = path elif str(path).endswith(".env"): # pragma: no cover settings_path = path.parent / ".env" else: settings_path = path / ".env" Path.touch(settings_path) secrets_path = None else: settings_path = path / f"settings.{fileformat}" secrets_path = path / f".secrets.{fileformat}" gitignore_path = path / ".gitignore" if fileformat in ["py", "env"] or env == "main": # for Main env, Python and .env formats writes a single env settings_data = settings_data.get(env, {}) secrets_data = secrets_data.get(env, {}) if not y and settings_path and settings_path.exists(): # pragma: no cover click.confirm( f"⁉ {settings_path} exists do you want to overwrite it?", abort=True, ) if not y and secrets_path and secrets_path.exists(): # pragma: no cover click.confirm( f"⁉ {secrets_path} exists do you want to overwrite it?", abort=True, ) if settings_path: loader.write(settings_path, settings_data, merge=True) click.echo(f"🎛️ {settings_path.name} created to hold your settings.\n") if secrets_path: loader.write(secrets_path, secrets_data, merge=True) click.echo(f"🔑 {secrets_path.name} created to hold your secrets.\n") ignore_line = ".secrets.*" comment = "\n# Ignore dynaconf secret files\n" if not gitignore_path.exists(): with open(str(gitignore_path), "w", encoding=ENC) as f: f.writelines([comment, ignore_line, "\n"]) else: existing = ( ignore_line in open(str(gitignore_path), encoding=ENC).read() ) if not existing: # pragma: no cover with open(str(gitignore_path), "a+", encoding=ENC) as f: f.writelines([comment, ignore_line, "\n"]) click.echo( f"🙈 the {secrets_path.name} is also included in `.gitignore` \n" " beware to not push your secrets to a public repo \n" " or use dynaconf builtin support for Vault Servers.\n" ) if django: # pragma: no cover dj_module, _ = get_module({}, django) dj_filename = dj_module.__file__ if Path(dj_filename).exists(): click.confirm( f"⁉ {dj_filename} is found do you want to add dynaconf?", abort=True, ) with open(dj_filename, "a") as dj_file: dj_file.write(constants.DJANGO_PATCH) click.echo("🎠 Now your Django settings are managed by Dynaconf") else: click.echo("❌ Django settings file not written.") else: click.echo( "🎉 Dynaconf is configured! read more on https://dynaconf.com\n" " Use `dynaconf -i config.settings list` to see your settings\n" ) @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 printed 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: try: result = settings[key] except KeyError: click.echo("Key not found", nl=False, err=True) sys.exit(1) if unparse: result = unparse_conf_data(result) if isinstance(result, (dict, list, tuple)): result = json.dumps(prepare_json(result), sort_keys=True, default=repr) click.echo(result, nl=False) @main.command(name="list") @click.option( "--env", "-e", default=None, help="Filters the env to get the values" ) @click.option("--key", "-k", default=None, help="Filters a single key") @click.option( "--more", "-m", default=None, help="Pagination more|less style", is_flag=True, ) @click.option( "--loader", "-l", default=None, help="a loader identifier to filter e.g: toml|yaml", ) @click.option( "--all", "_all", "-a", default=False, is_flag=True, help="show dynaconf internal settings?", ) @click.option( "--output", "-o", type=click.Path(writable=True, dir_okay=False), default=None, help="Filepath to write the listed values as json", ) @click.option( "--output-flat", "flat", is_flag=True, default=False, help="Output file is flat (do not include [env] name)", ) @click.option( "--json", "_json", "-j", is_flag=True, default=False, help="Prints out data serialized as JSON", ) def _list( env, key, more, loader, _all=False, output=None, flat=False, _json=False, ): """ Lists user defined settings or all (including internal configs). By default, shows only user defined. If `--all` is passed it also shows dynaconf internal variables aswell. """ if env: env = env.strip() if key: key = key.strip() if loader: loader = loader.strip() if env: settings.setenv(env) cur_env = settings.current_env.lower() if cur_env == "main": flat = True if not _json: click.echo( click.style( f"Working in {cur_env} environment ", bold=True, bg="bright_blue", fg="bright_white", ) ) if not loader: data = settings.as_dict(env=env, internal=_all) else: identifier = f"{loader}_{cur_env}" data = settings._loaded_by_loaders.get(identifier, {}) data = data or settings._loaded_by_loaders.get(loader, {}) # remove to avoid displaying twice data.pop("SETTINGS_MODULE", None) def color(_k): if _k in dir(default_settings): return "blue" return "magenta" def format_setting(_k, _v): key = click.style(_k, bg=color(_k), fg="bright_white") data_type = click.style( f"<{type(_v).__name__}>", bg="bright_black", fg="bright_white" ) value = pprint.pformat(_v) return f"{key}{data_type} {value}" if not key: if not _json: datalines = "\n".join( format_setting(k, v) for k, v in data.items() if k not in data.get("RENAMED_VARS", []) ) (click.echo_via_pager if more else click.echo)(datalines) if output: loaders.write(output, prepare_json(data), env=not flat and cur_env) if _json: json_data = json.dumps( prepare_json(data), sort_keys=True, default=repr ) click.echo(json_data, nl=False) else: key = upperfy(key) try: value = settings.get(key, empty) except AttributeError: value = empty if value is empty: click.secho("Key not found", bg="red", fg="white", err=True) sys.exit(1) if not _json: click.echo(format_setting(key, value)) if output: loaders.write( output, prepare_json({key: value}), env=not flat and cur_env ) if _json: click.echo( json.dumps(prepare_json({key: value}), default=repr), nl=True ) if env: settings.setenv() @main.command() @click.argument("to", required=True, type=click.Choice(WRITERS)) @click.option( "--vars", "_vars", "-v", multiple=True, default=None, help=( "key values to be written " "e.g: `dynaconf write toml -e NAME=foo -e X=2`" ), ) @click.option( "--secrets", "_secrets", "-s", multiple=True, default=None, help=( "secret key values to be written in .secrets " "e.g: `dynaconf write toml -s TOKEN=kdslmflds -s X=2`" ), ) @click.option( "--path", "-p", default=CWD, help="defaults to current directory/settings.{ext}", ) @click.option( "--env", "-e", default="default", help=( "env to write to defaults to DEVELOPMENT for files " "for external sources like Redis and Vault " "it will be DYNACONF or the value set in " "$ENVVAR_PREFIX_FOR_DYNACONF" ), ) @click.option("-y", default=False, is_flag=True) def write(to, _vars, _secrets, path, env, y): """Writes data to specific source.""" _vars = split_vars(_vars) _secrets = split_vars(_secrets) loader = importlib.import_module(f"dynaconf.loaders.{to}_loader") if to in EXTS: # Lets write to a file path = Path(path) if str(path).endswith(constants.ALL_EXTENSIONS + ("py",)): settings_path = path secrets_path = path.parent / f".secrets.{to}" else: if to == "env": if str(path) in (".env", "./.env"): # pragma: no cover settings_path = path elif str(path).endswith("/.env"): settings_path = path elif str(path).endswith(".env"): settings_path = path.parent / ".env" else: settings_path = path / ".env" Path.touch(settings_path) secrets_path = None _vars.update(_secrets) else: settings_path = path / f"settings.{to}" secrets_path = path / f".secrets.{to}" if ( _vars and not y and settings_path and settings_path.exists() ): # pragma: no cover # noqa click.confirm( f"{settings_path} exists do you want to overwrite it?", abort=True, ) if ( _secrets and not y and secrets_path and secrets_path.exists() ): # pragma: no cover # noqa click.confirm( f"{secrets_path} exists do you want to overwrite it?", abort=True, ) if to not in ["py", "env"]: if _vars: _vars = {env: _vars} if _secrets: _secrets = {env: _secrets} if _vars and settings_path: loader.write(settings_path, _vars, merge=True) click.echo(f"Data successful written to {settings_path}") if _secrets and secrets_path: loader.write(secrets_path, _secrets, merge=True) click.echo(f"Data successful written to {secrets_path}") else: # pragma: no cover # lets write to external source with settings.using_env(env): # make sure we're in the correct environment loader.write(settings, _vars, **_secrets) click.echo(f"Data successful written to {to}") @main.command() @click.option( "--path", "-p", default=CWD, help="defaults to current directory" ) def validate(path): # pragma: no cover """ Validates Dynaconf settings based on provided rules. Rules should be defined in dynaconf_validators.toml """ # reads the 'dynaconf_validators.toml' from path # for each section register the validator for specific env # call validate path = Path(path) if not str(path).endswith(".toml"): path = path / "dynaconf_validators.toml" if not path.exists(): # pragma: no cover # noqa click.echo(click.style(f"{path} not found", fg="white", bg="red")) sys.exit(1) # parse validator file 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), ) except tomllib.TOMLDecodeError as e: click.echo( click.style( f"Error parsing TOML: {e}. Maybe it should be quoted.", fg="white", bg="red", ) ) sys.exit(1) # guarantee there is an environment validation_data = {k.lower(): v for k, v in validation_data.items()} if not validation_data.get("default"): validation_data = {"default": validation_data} success = True for env, name_data in validation_data.items(): for name, data in name_data.items(): if not isinstance(data, dict): # pragma: no cover click.echo( click.style( f"Invalid rule for parameter '{name}'" "(this will be skipped)", fg="white", bg="yellow", ) ) else: data.setdefault("env", env) click.echo( click.style( f"Validating '{name}' with '{data}'", fg="white", bg="blue", ) ) try: Validator(name, **data).validate(settings) except ValidationError as e: click.echo( click.style(f"Error: {e}", fg="white", bg="red") ) success = False if success: click.echo(click.style("Validation success!", fg="white", bg="green")) else: click.echo(click.style("Validation error!", fg="white", bg="red")) sys.exit(1) INSPECT_FORMATS = list(builtin_dumpers.keys()) @main.command() @click.option("--key", "-k", help="Filters result by key.") @click.option( "--env", "-e", help="Filters result by environment on --report-mode=inspect.", default=None, ) @click.option( "--format", "-f", help="The output format.", default="json", type=click.Choice(INSPECT_FORMATS), ) @click.option( "--old-first", "new_first", "-s", help="Invert history sorting to 'old-first' on --report-mode=inspect.", default=True, is_flag=True, ) @click.option( "--limit", "history_limit", "-n", default=None, type=int, help="Limits how many history entries are shown on --report-mode=inspect.", ) @click.option( "--all", "_all", "-a", default=False, is_flag=True, help="Show dynaconf internal settings?", ) @click.option( "--report-mode", "report_mode", "-m", default="inspect", type=click.Choice(["inspect", "debug"]), ) @click.option( "-v", "--verbose", count=True, help="Increase verbosity of the output on --report-mode=debug.", ) def inspect( key, env, format, new_first, history_limit, _all, report_mode, verbose ): # pragma: no cover """ Inspect the loading history of the given settings instance. Filters by key and environment, otherwise shows all. """ if report_mode == "debug": print_debug_info( settings, dumper=format, verbosity=verbose, key=key, ) click.echo() elif report_mode == "inspect": try: inspect_settings( settings, key=key, env=env or None, dumper=format, new_first=new_first, include_internal=_all, history_limit=history_limit, print_report=True, ) click.echo() except (KeyNotFoundError, EnvNotFoundError, OutputFormatError) as err: click.echo(err) sys.exit(1) else: click.echo("Invalid report mode") sys.exit(1) if __name__ == "__main__": # pragma: no cover main() ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/constants.py0000644000000000000000000000235315156311032017724 0ustar00pbrochadpbrochad# pragma: no cover from __future__ import annotations INI_EXTENSIONS = (".ini", ".conf", ".properties") TOML_EXTENSIONS = (".toml", ".tml") YAML_EXTENSIONS = (".yaml", ".yml") JSON_EXTENSIONS = (".json",) ALL_EXTENSIONS = ( INI_EXTENSIONS + TOML_EXTENSIONS + YAML_EXTENSIONS + JSON_EXTENSIONS ) # noqa EXTERNAL_LOADERS = { "ENV": "dynaconf.loaders.env_loader", "VAULT": "dynaconf.loaders.vault_loader", "REDIS": "dynaconf.loaders.redis_loader", } DJANGO_PATCH = """ # HERE STARTS DYNACONF EXTENSION LOAD (Keep at the very bottom of settings.py) # 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) """ INSTANCE_TEMPLATE = """ from dynaconf import Dynaconf settings = Dynaconf( envvar_prefix="DYNACONF", settings_files={settings_files}, ) # `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. # `settings_files` = Load these files in the order. """ EXTS = ( "py", "toml", "tml", "yaml", "yml", "ini", "conf", "properties", "json", ) DEFAULT_SETTINGS_FILES = [f"settings.{ext}" for ext in EXTS] + [ f".secrets.{ext}" for ext in EXTS ] ././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9134624 dynaconf-3.2.13/dynaconf/contrib/0000755000000000000000000000000015156326623017007 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/contrib/__init__.py0000644000000000000000000000033015156311032021100 0ustar00pbrochadpbrochadfrom __future__ import annotations from dynaconf.contrib.django_dynaconf_v2 import DjangoDynaconf from dynaconf.contrib.flask_dynaconf import DynaconfConfig from dynaconf.contrib.flask_dynaconf import FlaskDynaconf ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/contrib/django_dynaconf_v2.py0000644000000000000000000001113315156324234023106 0ustar00pbrochadpbrochad"""Dynaconf django extension 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://www.dynaconf.com/django/ import dynaconf # noqa settings = dynaconf.DjangoDynaconf(__name__) # noqa # HERE ENDS DYNACONF EXTENSION LOAD (No more code below this line) Now in the root of your Django project (the same folder where manage.py is located) Put your config files `settings.{py|yaml|toml|ini|json}` and or `.secrets.{py|yaml|toml|ini|json}` On your projects root folder now you can start as:: DJANGO_DEBUG='false' \ DJANGO_ALLOWED_HOSTS='["localhost"]' \ python manage.py runserver """ from __future__ import annotations import inspect import os import sys import dynaconf from dynaconf.hooking import HookableSettings try: # pragma: no cover from django import conf from django.conf import settings as django_settings django_installed = True except ImportError: # pragma: no cover django_installed = False def load(django_settings_module_name=None, **kwargs): # pragma: no cover if not django_installed: raise RuntimeError( "To use this extension django must be installed " "install it with: pip install django" ) try: django_settings_module = sys.modules[django_settings_module_name] except KeyError: django_settings_module = sys.modules[ 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.upper(): v for k, v in django_settings_module.__dict__.items() if k.isupper() } options.update(kwargs) options.setdefault( "SKIP_FILES_FOR_DYNACONF", [settings_file, "dynaconf_merge"] ) options.setdefault("ROOT_PATH_FOR_DYNACONF", _root_path) options.setdefault("ENVVAR_PREFIX_FOR_DYNACONF", "DJANGO") options.setdefault("ENV_SWITCHER_FOR_DYNACONF", "DJANGO_ENV") options.setdefault("ENVIRONMENTS_FOR_DYNACONF", True) options.setdefault("load_dotenv", True) options.setdefault( "default_settings_paths", dynaconf.DEFAULT_SETTINGS_FILES ) options.setdefault("_wrapper_class", HookableSettings) class UserSettingsHolder(dynaconf.LazySettings): _django_override = True lazy_settings = dynaconf.LazySettings(**options) dynaconf.settings = lazy_settings # rebind the settings # 2) Set all settings back to django_settings_module for 'django check' lazy_settings.populate_obj(django_settings_module) # 3) Bind `settings` and `DYNACONF` setattr(django_settings_module, "settings", lazy_settings) setattr(django_settings_module, "DYNACONF", lazy_settings) # 4) keep django original settings dj = {} for key in dir(django_settings): if ( key.isupper() and (key != "SETTINGS_MODULE") and key not in lazy_settings.store ): dj[key] = getattr(django_settings, key, None) dj["ORIGINAL_SETTINGS_MODULE"] = django_settings.SETTINGS_MODULE 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: # lazy_settings = conf.settings.lazy_settings def __getattribute__(self, name): if name == "settings": return lazy_settings if name == "UserSettingsHolder": return UserSettingsHolder return getattr(conf, name) # This implementation is recommended by Guido Van Rossum # https://mail.python.org/pipermail/python-ideas/2012-May/014969.html sys.modules["django.conf"] = Wrapper() # 6) Enable standalone scripts to use Dynaconf # This is for when `django.conf.settings` is imported directly # on external `scripts` (out of Django's lifetime) for stack_item in reversed(inspect.stack()): if isinstance( stack_item.frame.f_globals.get("settings"), conf.LazySettings ): stack_item.frame.f_globals["settings"] = lazy_settings return lazy_settings # syntax sugar DjangoDynaconf = load # noqa ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/contrib/flask_dynaconf.py0000644000000000000000000001661715156324234022351 0ustar00pbrochadpbrochadfrom __future__ import annotations import warnings from collections import ChainMap from contextlib import suppress try: from flask.config import Config flask_installed = True except ImportError: # pragma: no cover flask_installed = False Config = object from importlib.metadata import EntryPoint import dynaconf class FlaskDynaconf: """The arguments are. app = The created app dynaconf_args = Extra args to be passed to Dynaconf (validator for example) All other values are stored as config vars specially:: ENVVAR_PREFIX_FOR_DYNACONF = env prefix for your envvars to be loaded example: if you set to `MYSITE` then export MYSITE_SQL_PORT='@int 5445' with that exported to env you access using: app.config.SQL_PORT app.config.get('SQL_PORT') app.config.get('sql_port') # get is case insensitive app.config['SQL_PORT'] Dynaconf uses `@int, @bool, @float, @json` to cast env vars SETTINGS_FILE_FOR_DYNACONF = The name of the module or file to use as default to load settings. If nothing is passed it will be `settings.*` or value found in `ENVVAR_FOR_DYNACONF` Dynaconf supports .py, .yml, .toml, ini, json ATTENTION: Take a look at `settings.yml` and `.secrets.yml` to know the required settings format. Settings load order in Dynaconf: - Load all defaults and Flask defaults - Load all passed variables when applying FlaskDynaconf - Update with data in settings files - Update with data in environment vars `ENVVAR_FOR_DYNACONF_` TOML files are very useful to have `envd` settings, lets say, `production` and `development`. You can also achieve the same using multiple `.py` files naming as `settings.py`, `production_settings.py` and `development_settings.py` (see examples/validator) Example:: app = Flask(__name__) FlaskDynaconf( app, ENV='MYSITE', SETTINGS_FILE='settings.yml', EXTRA_VALUE='You can add additional config vars here' ) Take a look at examples/flask in Dynaconf repository """ def __init__( self, app=None, instance_relative_config=False, dynaconf_instance=None, extensions_list=False, **kwargs, ): """kwargs holds initial dynaconf configuration""" if not flask_installed: # pragma: no cover raise RuntimeError( "To use this extension Flask must be installed " "install it with: pip install flask" ) self.kwargs = {k.upper(): v for k, v in kwargs.items()} self.kwargs.setdefault("ENVVAR_PREFIX", "FLASK") env_prefix = f"{self.kwargs['ENVVAR_PREFIX']}_ENV" # FLASK_ENV self.kwargs.setdefault("ENV_SWITCHER", env_prefix) self.kwargs.setdefault("ENVIRONMENTS", True) self.kwargs.setdefault("LOAD_DOTENV", True) self.kwargs.setdefault( "default_settings_paths", dynaconf.DEFAULT_SETTINGS_FILES ) self.dynaconf_instance = dynaconf_instance self.instance_relative_config = instance_relative_config self.extensions_list = extensions_list if app: self.init_app(app, **kwargs) def init_app(self, app, **kwargs): """kwargs holds initial dynaconf configuration""" self.kwargs.update(kwargs) self.settings = self.dynaconf_instance or dynaconf.LazySettings( **self.kwargs ) dynaconf.settings = self.settings # rebind customized settings app.config = self.make_config(app) app.dynaconf = self.settings if self.extensions_list: if not isinstance(self.extensions_list, str): self.extensions_list = "EXTENSIONS" app.config.load_extensions(self.extensions_list) def make_config(self, app): root_path = app.root_path if self.instance_relative_config: # pragma: no cover root_path = app.instance_path if self.dynaconf_instance: self.settings.update(self.kwargs) return DynaconfConfig( root_path=root_path, defaults=app.config, _settings=self.settings, _app=app, ) class DynaconfConfig(Config): """ Replacement for flask.config_class that responds as a Dynaconf instance. """ def __init__(self, _settings, _app, *args, **kwargs): """perform the initial load""" super().__init__(*args, **kwargs) # Bring Dynaconf instance value to Flask Config Config.update(self, _settings.store) self._settings = _settings self._app = _app def __contains__(self, item): return hasattr(self, item) def __getitem__(self, key): try: return self._settings[key] except KeyError: return Config.__getitem__(self, key) def __setitem__(self, key, value): """ Allows app.config['key'] = 'foo' """ return self._settings.__setitem__(key, value) def _chain_map(self): return ChainMap(self._settings, dict(dict.items(self))) def keys(self): return self._chain_map().keys() def values(self): return self._chain_map().values() 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__() def __getattr__(self, name): """ First try to get value from dynaconf then from Flask Config """ with suppress(AttributeError): return getattr(self._settings, name) with suppress(KeyError): return self[name] raise AttributeError( f"'{self.__class__.__name__}' object has no attribute '{name}'" ) def __call__(self, name, *args, **kwargs): return self.get(name, *args, **kwargs) def get(self, key, default=None): """Gets config from dynaconf variables if variables does not exists in dynaconf try getting from `app.config` to support runtime settings.""" return self._settings.get(key, Config.get(self, key, default)) def load_extensions(self, key="EXTENSIONS", app=None): """Loads flask extensions dynamically.""" app = app or self._app extensions = app.config.get(key) if not extensions: warnings.warn( f"Settings is missing {key} to load Flask Extensions", RuntimeWarning, ) return for object_reference in app.config[key]: # parse the entry point specification entry_point = EntryPoint( name=None, group=None, value=object_reference ) # dynamically resolve the entry point initializer = entry_point.load() # Invoke extension initializer initializer(app) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/default_settings.py0000644000000000000000000002316615156324234021271 0ustar00pbrochadpbrochadfrom __future__ import annotations import importlib import os import sys import warnings from dynaconf.utils import RENAMED_VARS from dynaconf.utils import upperfy from dynaconf.utils import warn_deprecations from dynaconf.utils.files import find_file from dynaconf.utils.parse_conf import boolean_fix from dynaconf.utils.parse_conf import parse_conf_data from dynaconf.vendor.dotenv import load_dotenv def try_renamed(key, value, older_key, current_key): if value is None: if key == current_key: if older_key in os.environ: warnings.warn( f"{older_key} is deprecated please use {current_key}", DeprecationWarning, ) value = os.environ[older_key] return value def get(key, default=None): value = os.environ.get(upperfy(key)) # compatibility with renamed variables for old, new in RENAMED_VARS.items(): value = try_renamed(key, value, old, new) return ( parse_conf_data(boolean_fix(value), tomlfy=True, box_settings={}) if value is not None else default ) def start_dotenv(obj=None, root_path=None): # load_from_dotenv_if_installed obj = obj or {} _find_file = getattr(obj, "find_file", find_file) root_path = ( root_path or getattr(obj, "_root_path", None) or get("ROOT_PATH_FOR_DYNACONF") ) dotenv_path = ( obj.get("DOTENV_PATH_FOR_DYNACONF") or get("DOTENV_PATH_FOR_DYNACONF") or _find_file(".env", project_root=root_path) ) load_dotenv( dotenv_path, verbose=obj.get("DOTENV_VERBOSE_FOR_DYNACONF", False), override=obj.get("DOTENV_OVERRIDE_FOR_DYNACONF", False), ) warn_deprecations(os.environ) def reload(load_dotenv=None, *args, **kwargs): if load_dotenv: start_dotenv(*args, **kwargs) importlib.reload(sys.modules[__name__]) # default proj root # pragma: no cover ROOT_PATH_FOR_DYNACONF = get("ROOT_PATH_FOR_DYNACONF", None) # Default settings file SETTINGS_FILE_FOR_DYNACONF = get("SETTINGS_FILE_FOR_DYNACONF", []) # MISSPELLS `FILES` when/if it happens misspelled_files = get("SETTINGS_FILES_FOR_DYNACONF", None) if not SETTINGS_FILE_FOR_DYNACONF and misspelled_files is not None: SETTINGS_FILE_FOR_DYNACONF = misspelled_files # # ENV SETTINGS # # In dynaconf 1.0.0 `NAMESPACE` got renamed to `ENV` # 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) # The environment variable to switch current env ENV_SWITCHER_FOR_DYNACONF = get( "ENV_SWITCHER_FOR_DYNACONF", "ENV_FOR_DYNACONF" ) # The current env by default is DEVELOPMENT # to switch is needed to `export ENV_FOR_DYNACONF=PRODUCTION` # or put that value in .env file # this value is used only when reading files like .toml|yaml|ini|json ENV_FOR_DYNACONF = get(ENV_SWITCHER_FOR_DYNACONF, "DEVELOPMENT") # This variable exists to support `from_env` method FORCE_ENV_FOR_DYNACONF = get("FORCE_ENV_FOR_DYNACONF", None) # Default values is taken from DEFAULT pseudo env # this value is used only when reading files like .toml|yaml|ini|json DEFAULT_ENV_FOR_DYNACONF = get("DEFAULT_ENV_FOR_DYNACONF", "DEFAULT") # Global values are taken from DYNACONF env used for exported envvars # Values here overwrites all other envs # This namespace is used for files and also envvars ENVVAR_PREFIX_FOR_DYNACONF = get("ENVVAR_PREFIX_FOR_DYNACONF", "DYNACONF") # By default all environment variables (filtered by `envvar_prefix`) will # be pulled into settings space. In case some of them are polluting the space, # setting this flag to `True` will change this behaviour. # Only "known" variables will be considered -- that is variables defined before # in settings files (or includes/preloads). 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` # Should result in settings.DATABASE == {'server': 'server.com', 'PORT': 6666} # To disable it one can set `NESTED_SEPARATOR_FOR_DYNACONF=false` NESTED_SEPARATOR_FOR_DYNACONF = get("NESTED_SEPARATOR_FOR_DYNACONF", "__") # The env var specifying settings module ENVVAR_FOR_DYNACONF = get("ENVVAR_FOR_DYNACONF", "SETTINGS_FILE_FOR_DYNACONF") # Default values for redis configs default_redis = { "host": get("REDIS_HOST_FOR_DYNACONF", "localhost"), "port": int(get("REDIS_PORT_FOR_DYNACONF", 6379)), "db": int(get("REDIS_DB_FOR_DYNACONF", 0)), "decode_responses": get("REDIS_DECODE_FOR_DYNACONF", True), "username": get("REDIS_USERNAME_FOR_DYNACONF", None), "password": get("REDIS_PASSWORD_FOR_DYNACONF", None), } REDIS_FOR_DYNACONF = get("REDIS_FOR_DYNACONF", default_redis) REDIS_ENABLED_FOR_DYNACONF = get("REDIS_ENABLED_FOR_DYNACONF", False) # Hashicorp Vault Project vault_scheme = get("VAULT_SCHEME_FOR_DYNACONF", "http") vault_host = get("VAULT_HOST_FOR_DYNACONF", "localhost") vault_port = get("VAULT_PORT_FOR_DYNACONF", "8200") default_vault = { "url": get( "VAULT_URL_FOR_DYNACONF", f"{vault_scheme}://{vault_host}:{vault_port}" ), "token": get("VAULT_TOKEN_FOR_DYNACONF", None), "cert": get("VAULT_CERT_FOR_DYNACONF", None), "verify": get("VAULT_VERIFY_FOR_DYNACONF", None), "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) VAULT_PATH_FOR_DYNACONF = get("VAULT_PATH_FOR_DYNACONF", "dynaconf") VAULT_MOUNT_POINT_FOR_DYNACONF = get( "VAULT_MOUNT_POINT_FOR_DYNACONF", "secret" ) VAULT_ROOT_TOKEN_FOR_DYNACONF = get("VAULT_ROOT_TOKEN_FOR_DYNACONF", None) VAULT_KV_VERSION_FOR_DYNACONF = get("VAULT_KV_VERSION_FOR_DYNACONF", 1) VAULT_AUTH_WITH_IAM_FOR_DYNACONF = get( "VAULT_AUTH_WITH_IAM_FOR_DYNACONF", False ) VAULT_AUTH_ROLE_FOR_DYNACONF = get("VAULT_AUTH_ROLE_FOR_DYNACONF", None) VAULT_ROLE_ID_FOR_DYNACONF = get("VAULT_ROLE_ID_FOR_DYNACONF", None) VAULT_SECRET_ID_FOR_DYNACONF = get("VAULT_SECRET_ID_FOR_DYNACONF", None) VAULT_USERNAME_FOR_DYNACONF = get("VAULT_USERNAME_FOR_DYNACONF", None) VAULT_PASSWORD_FOR_DYNACONF = get("VAULT_PASSWORD_FOR_DYNACONF", None) VAULT_TOKEN_RENEW_FOR_DYNACONF = get("VAULT_TOKEN_RENEW_FOR_DYNACONF", False) # Only core loaders defined on this list will be invoked core_loaders = ["YAML", "TOML", "INI", "JSON", "PY"] CORE_LOADERS_FOR_DYNACONF = get("CORE_LOADERS_FOR_DYNACONF", core_loaders) # External Loaders to read vars from different data stores default_loaders = [ "dynaconf.loaders.env_loader", # 'dynaconf.loaders.redis_loader' # 'dynaconf.loaders.vault_loader' ] LOADERS_FOR_DYNACONF = get("LOADERS_FOR_DYNACONF", default_loaders) # Errors in loaders should be silenced? SILENT_ERRORS_FOR_DYNACONF = get("SILENT_ERRORS_FOR_DYNACONF", True) # always fresh variables FRESH_VARS_FOR_DYNACONF = get("FRESH_VARS_FOR_DYNACONF", []) DOTENV_PATH_FOR_DYNACONF = get("DOTENV_PATH_FOR_DYNACONF", None) DOTENV_VERBOSE_FOR_DYNACONF = get("DOTENV_VERBOSE_FOR_DYNACONF", False) DOTENV_OVERRIDE_FOR_DYNACONF = get("DOTENV_OVERRIDE_FOR_DYNACONF", False) # Currently this is only used by cli. INSTANCE_FOR_DYNACONF specifies python # dotted path to custom LazySettings instance. Last dotted path item should be # instance of LazySettings. INSTANCE_FOR_DYNACONF = get("INSTANCE_FOR_DYNACONF", None) # https://msg.pyyaml.org/load YAML_LOADER_FOR_DYNACONF = get("YAML_LOADER_FOR_DYNACONF", "safe_load") # Use commentjson? https://commentjson.readthedocs.io/en/latest/ COMMENTJSON_ENABLED_FOR_DYNACONF = get( "COMMENTJSON_ENABLED_FOR_DYNACONF", False ) # Extra file, or list of files where to look for secrets # useful for CI environment like jenkins # where you can export this variable pointing to a local # absolute path of the secrets file. SECRETS_FOR_DYNACONF = get("SECRETS_FOR_DYNACONF", None) # To include extra paths based on envvar INCLUDES_FOR_DYNACONF = get("INCLUDES_FOR_DYNACONF", []) # To pre-load extra paths based on envvar 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 ) # Auto trigger validation when Settings update methods are called directly # (set, update, load_file) VALIDATE_ON_UPDATE_FOR_DYNACONF = get("VALIDATE_ON_UPDATE_FOR_DYNACONF", False) # Use system environ as fallback when a setting was not set SYSENV_FALLBACK_FOR_DYNACONF = get("SYSENV_FALLBACK_FOR_DYNACONF", False) # Backwards compatibility with renamed variables for old, new in RENAMED_VARS.items(): setattr(sys.modules[__name__], old, locals()[new]) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/hooking.py0000644000000000000000000002562615156324234017366 0ustar00pbrochadpbrochadfrom __future__ import annotations from dataclasses import dataclass from enum import Enum from functools import wraps from typing import Any from typing import Callable from dynaconf.base import Settings from dynaconf.loaders.base import SourceMetadata __all__ = [ "hookable", "EMPTY_VALUE", "Hook", "EagerValue", "HookValue", "MethodValue", "Action", "HookableSettings", "post_hook", ] class Empty: ... EMPTY_VALUE = Empty() def hookable(function=None, name=None): """Adds before and after hooks to any method. :param function: function to be decorated :param name: name of the method to be decorated (default to method name) :return: decorated function Usage: class MyHookableClass(Settings): @hookable def execute_loaders(....): # do whatever you want here return super().execute_loaders(....) settings = Dynaconf(_wrapper_class=MyHookableClass) def hook_function(temp_settings, value, ...): # do whatever you want here return value settings.add_hook("after_execute_loaders", Hook(function)) settings.FOO # will trigger execute_loaders # -> will trigger the hookable method # -> will execute registered hooks see tests/test_hooking.py for more examples. """ if function and not callable(function): raise TypeError("hookable must be applied with named arguments only") def dispatch(fun, self, *args, **kwargs): """calls the decorated function and its hooks""" # if object has no hooks, return the original if not (_registered_hooks := get_hooks(self)): return fun(self, *args, **kwargs) function_name = name or fun.__name__ # function being called not in the list of hooks, return the original if not set(_registered_hooks).intersection( (f"before_{function_name}", f"after_{function_name}") ): return fun(self, *args, **kwargs) # Create an unhook-able (to avoid recursion) # temporary settings to pass to the hooked function temp_settings = TempSettingsHolder(self) def _hook(action: str, value: HookValue) -> HookValue: """executes the hooks for the given action""" hooks = _registered_hooks.get(f"{action}_{function_name}", []) for hook in hooks: value = hook.function(temp_settings, value, *args, **kwargs) value = HookValue.new(value) return value # Value starts as en empty value on the first before hook value = _hook("before", HookValue(EMPTY_VALUE)) # If the value is EagerValue, it means main function should not be # executed and the value should go straight to the after hooks if any original_value = EMPTY_VALUE if not isinstance(value, EagerValue): value = MethodValue(fun(self, *args, **kwargs)) original_value = value.value value = _hook("after", value) # track the loading history # adding inspect history like: # "identifier": "get_hook_(read_settings_from_cache_or_db)" if value.value != original_value and function_name == "get": hook_names = "_".join( [ hook.function.__name__ for list_of_hooks in _registered_hooks.values() for hook in list_of_hooks ] ) metadata = SourceMetadata( loader="hooking", identifier=f"{function_name}_hook_({hook_names})", merged=True, ) history = self._loaded_by_loaders.setdefault(metadata, {}) key = args[0] if args else kwargs.get("key") history[key] = value.value # unwrap the value from the HookValue so it can be returned # normally to the caller return value.value if function: # decorator applied without parameters e.g: @hookable @wraps(function) def wrapper(*args, **kwargs): return dispatch(function, *args, **kwargs) wrapper.original_function = function return wrapper def decorator(function): # decorator applied with parameters e.g: @hookable(before=False) @wraps(function) def wrapper(*args, **kwargs): return dispatch(function, *args, **kwargs) wrapper.original_function = function return wrapper return decorator def get_hooks(obj): """get registered hooks from object must try different casing and accessors because of tests and casing mode set on dynaconf. """ attr = "_registered_hooks" for key in [attr, attr.upper()]: if hasattr(obj, key): return getattr(obj, key) elif isinstance(obj, dict) and key in obj: return obj[key] elif hasattr(obj, "_store") and key in obj._store: return obj._store[key] return {} @dataclass class Hook: """Hook to wrap a callable on _registered_hooks list. :param callable: The callable to be wrapped The callable must accept the following arguments: - temp_settings: Settings or a Dict - value: The value to be processed wrapper in a HookValue (accumulated from previous hooks, last hook will receive the final value) - *args: The args passed to the original method - **kwargs: The kwargs passed to the original method The callable must return the value: - value: The processed value to be passed to the next hook """ function: Callable @dataclass class HookValue: """Base class for hook values. Hooks must return a HookValue instance. """ value: Any @classmethod def new(cls, value: Any) -> HookValue: """Return a new HookValue instance with the given value.""" if isinstance(value, HookValue): return value return cls(value) def __str__(self) -> str: return str(self.value) def __eq__(self, other) -> bool: return self.value == other def __ne__(self, other) -> bool: return self.value != other def __bool__(self) -> bool: return bool(self.value) def __len__(self) -> int: return len(self.value) def __iter__(self): return iter(self.value) def __getitem__(self, item): return self.value[item] def __setitem__(self, key, value): self.value[key] = value def __delitem__(self, key): del self.value[key] def __contains__(self, item): return item in self.value def __getattr__(self, item): return getattr(self.value, item) def __setattr__(self, key, value): if key == "value": super().__setattr__(key, value) else: setattr(self.value, key, value) def __add__(self, other): return self.value + other def __sub__(self, other): return self.value - other def __mul__(self, other): return self.value * other def __truediv__(self, other): return self.value / other def __floordiv__(self, other): return self.value // other def __mod__(self, other): return self.value % other def __divmod__(self, other): return divmod(self.value, other) def __pow__(self, power, modulo=None): return pow(self.value, power, modulo) def __delattr__(self, item): delattr(self.value, item) def __repr__(self) -> str: return repr(self.value) class MethodValue(HookValue): """A value returned by a method The main decorated method have its value wrapped in this class """ class EagerValue(HookValue): """Use this wrapper to return earlier from a hook. Main function is bypassed and value is passed to after hooks.""" class Action(str, Enum): """All the hookable functions""" AFTER_GET = "after_get" BEFORE_GET = "before_get" class HookableSettings(Settings): """Wrapper for dynaconf.base.Settings that adds hooks to get method.""" _REGISTERED_HOOKS: dict[Action, list[Hook]] = {} # needed because django of Django admin see #1000 @hookable def get(self, *args, **kwargs): return Settings.get(self, *args, **kwargs) class TempSettingsHolder: """Holds settings to be passed down to hooks. To save runtime resources initialize a copy of it only if accessed. """ _settings = None def __init__(self, settings): self._original_settings = settings def _initialize(self): if self._settings is None: self._settings = Settings( dynaconf_skip_loaders=True, dynaconf_skip_validators=True, _store=self._original_settings._store.copy(bypass_eval=True), ) def __getattr__(self, attr): self._initialize() return getattr(self._settings, attr) def __getitem__(self, item): self._initialize() return self._settings[item] def __setitem__(self, item, value): self._initialize() self._settings[item] = value def __iter__(self): self._initialize() return iter(self._settings) def __contains__(self, item): self._initialize() return item in self._settings def __setattr__(self, attr, value): if attr in ["_original_settings", "_settings"]: super().__setattr__(attr, value) else: self._initialize() setattr(self._settings, attr, value) def post_hook(function: Callable) -> Callable: """This decorator marks a function as a post hook. This works by adding the _dynaconf_hook attribute to the function, then the python loader, when reading the module, will look for this attribute and register the function as a post_hook for the settings. e.g: On a settings file with .py extension: from dynaconf import post_hook @post_hook def set_log_handlers(settings) -> dict: data = {} # data to be merged into settings # conditionals if (logging := settings.get('LOGGING')) is not None: # do something with logging # add it back to data data['LOGGING'] = logging return data """ try: function._dynaconf_hook = True # type: ignore function._called = False # type: ignore function._dynaconf_hook_source = function.__module__ # type: ignore except (AttributeError, TypeError): raise TypeError( "post_hook decorator must be applied to a function or method." ) else: # On the same scope where the decorated function is defined we # add a variable with the same name as the function but prefixed # with _dynaconf_hook_ this variable will be used by the loader # to register the function as a post_hook function.__globals__[f"_dynaconf_hook_{function.__name__}"] = function return function ././@PaxHeader0000000000000000000000000000007100000000000010213 xustar0015 uid=4214970 15 gid=4214970 27 mtime=1773776274.914119 dynaconf-3.2.13/dynaconf/loaders/0000755000000000000000000000000015156326623017000 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/loaders/__init__.py0000644000000000000000000003422015156324234021106 0ustar00pbrochadpbrochadfrom __future__ import annotations import importlib import os from contextlib import suppress from typing import Callable from typing import TYPE_CHECKING from dynaconf import constants as ct from dynaconf import default_settings from dynaconf.loaders import ini_loader from dynaconf.loaders import json_loader from dynaconf.loaders import py_loader from dynaconf.loaders import toml_loader from dynaconf.loaders import yaml_loader from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import deduplicate from dynaconf.utils import ensure_a_list from dynaconf.utils.boxing import DynaBox from dynaconf.utils.files import get_local_filename from dynaconf.utils.files import glob from dynaconf.utils.files import has_magic from dynaconf.utils.parse_conf import false_values if TYPE_CHECKING: from dynaconf.base import Settings def default_loader(obj, defaults=None): """Initial loader for the initialization process. Steps: - Load default settings (from static module) + kwargs overrides (together) - Load envvar overrides """ # LOAD DEFAULT STATIC + KWARGS OVERRIDES defaults = defaults or {} default_settings_values = { key: value for key, value in default_settings.__dict__.items() # noqa if key.isupper() } all_keys = deduplicate( list(defaults.keys()) + list(default_settings_values.keys()) ) for key in all_keys: if not obj.exists(key): value = defaults.get(key, default_settings_values.get(key)) obj.set( key, value, loader_identifier="default_settings", validate=False, ) # LOAD ENVVAR OVERRIDES # start dotenv to get default env vars from there # check overrides in env vars if obj.get("load_dotenv") is True: default_settings.start_dotenv(obj) # Deal with cases where a custom ENV_SWITCHER_IS_PROVIDED # Example: Flask and Django Extensions env_switcher = defaults.get( "ENV_SWITCHER_FOR_DYNACONF", "ENV_FOR_DYNACONF" ) for key in all_keys: if key not in default_settings_values.keys(): continue env_value = obj.get_environ( env_switcher if key == "ENV_FOR_DYNACONF" else key, default="_not_found", ) if env_value != "_not_found": obj.set( key, env_value, tomlfy=True, loader_identifier="envvars_first_load", ) def execute_instance_hooks( obj: Settings, hook_type: str, hook_functions: list[Callable] ): """Execute hooks provided by Setting instance""" hook_source = "instance" for hook_func in hook_functions: _run_hook_function(obj, hook_type, hook_func, hook_source) def execute_module_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__ modules = modules or obj._loaded_py_modules for loaded_module in modules: hook_module_name = ".".join( loaded_module.split(".")[:-1] + ["dynaconf_hooks"] ) try: hook_module = importlib.import_module(hook_module_name) except (ImportError, TypeError): # There was no hook on the same path as a python module continue else: _run_hook_module( hook_type=hook, hook_module=hook_module, obj=obj, key=key, ) # Try to load from python filename path files = files or obj._loaded_files for loaded_file in files: hook_file = os.path.join( os.path.dirname(loaded_file), "dynaconf_hooks.py" ) if not os.path.exists(hook_file): # Return early if file doesn't exist. # Faster than attempting to import. continue hook_module = py_loader.import_from_filename( obj, hook_file, silent=silent ) _run_hook_module( hook_type=hook, hook_module=hook_module, obj=obj, key=key, ) # alias execute_hooks = execute_module_hooks def _get_unique_hook_id(hook_func, hook_source): """get unique identifier for a hook function. in most of cases this will be the function name@source_file however, if the function is a lambda, it will be a hash of the code object. because lambda functions are not hashable itself and we can't rely on its id. """ hook_unique_id = hook_func.__name__ if hook_unique_id == "": frame_info = getattr(hook_func, "__code__", None) if frame_info: hook_unique_id = f"lambda_{hash(frame_info.co_code)}" else: hook_unique_id = f"lambda_{id(hook_func)}" return f"{hook_unique_id}@{hook_source}" def _run_hook_module(hook_type, hook_module, obj, key=""): """ Run a hook function from hook_module. Given a @hook_type, a @hook_module and a settings @obj, load the function and execute it if found. """ # check errors if hook_module and getattr(hook_module, "_error", False): if not isinstance(hook_module._error, FileNotFoundError): raise hook_module._error # execute hook hook_source = hook_module.__file__ hook_func = getattr(hook_module, hook_type, None) if hook_func: identifier = _get_unique_hook_id(hook_func, hook_source) if hook_type not in obj._loaded_hooks.get(identifier, {}): _run_hook_function(obj, hook_type, hook_func, hook_source, key) def _run_hook_function( obj: Settings, hook_type: str, hook_func: Callable, hook_source: str = "default", key: str = "", ): """ Run a hook function: It execute @hook_func, update the results into settings @obj and add it to _loaded_hook registry ([@hook_source][@hook_type]) """ # if the function has a _dynaconf_hook_source attribute set # hook_source to it hook_source = getattr(hook_func, "_dynaconf_hook_source", hook_source) # optional settings argument try: hook_dict = hook_func(obj.dynaconf.clone()) except TypeError: hook_dict = hook_func() # mark as called so executors such as `load_file` can avoid calling it again with suppress(AttributeError, TypeError): # callable may not be writable, the caveat is that it will be called again in case of reload # however, this must not be a problem since the function should be idempotent # and documentation warns about this behavior. hook_func._called = True identifier = _get_unique_hook_id(hook_func, hook_source) if hook_dict: # update obj settings merge = hook_dict.pop( "dynaconf_merge", hook_dict.pop("DYNACONF_MERGE", False) ) if key and key in hook_dict: obj.set( key, hook_dict[key], tomlfy=False, merge=merge, loader_identifier=identifier, ) elif not key: obj.update( hook_dict, tomlfy=False, merge=merge, loader_identifier=identifier, ) # add to registry obj._loaded_hooks[identifier][hook_type] = hook_dict def settings_loader( obj, settings_module=None, env=None, silent=True, key=None, filename=None, validate=False, identifier="settings_loader", ): """Loads from defined settings module :param obj: A dynaconf instance :param settings_module: A path or a list of paths e.g settings.toml :param env: Env to look for data defaults: development :param silent: Boolean to raise loading errors :param key: Load a single key if provided :param filename: optional filename to override the settings_module :param validate: If True validate the loaded data :param identifier: A string or SourceMetadata to identify the loader """ if filename is None: settings_module = settings_module or obj.settings_module if not settings_module: # pragma: no cover return files = ensure_a_list(settings_module) else: files = ensure_a_list(filename) files.extend(ensure_a_list(obj.get("SECRETS_FOR_DYNACONF", None))) found_files = [] modules_names = [] for item in files: item = str(item) # Ensure str in case of LocalPath/Path is passed. p_root = obj._root_path or ( os.path.dirname(found_files[0]) if found_files else None ) if has_magic(item): # handle possible globs inside files list # like ["path/*.yaml", "path/ABC?.yaml"] globedfiles = glob(item, root_dir=p_root) for globedfile in globedfiles: # use object.find_file logic to handle skip files found = obj.find_file(globedfile, project_root=p_root) if found: found_files.append(found) elif item.endswith(ct.ALL_EXTENSIONS + (".py",)): found = obj.find_file(item, project_root=p_root) if found: found_files.append(found) else: # a bare python module name w/o extension modules_names.append(item) enabled_core_loaders = [ item.upper() for item in obj.get("CORE_LOADERS_FOR_DYNACONF") or [] ] # add `.local.` to found_files list to search for local files. found_files.extend( [ get_local_filename(item) for item in found_files if ".local." not in str(item) ] ) for mod_file in modules_names + found_files: # can be set to multiple files settings.py,settings.yaml,... # Cascade all loaders loaders = [ {"ext": ct.YAML_EXTENSIONS, "name": "YAML", "loader": yaml_loader}, {"ext": ct.TOML_EXTENSIONS, "name": "TOML", "loader": toml_loader}, {"ext": ct.INI_EXTENSIONS, "name": "INI", "loader": ini_loader}, {"ext": ct.JSON_EXTENSIONS, "name": "JSON", "loader": json_loader}, ] for loader in loaders: if loader["name"] not in enabled_core_loaders: continue if mod_file.endswith(loader["ext"]): if isinstance(identifier, str): # ensure it is always loader name identifier = loader["name"].lower() loader["loader"].load( obj, filename=mod_file, env=env, silent=silent, key=key, validate=validate, identifier=identifier, ) continue if mod_file.endswith(ct.ALL_EXTENSIONS): continue if "PY" not in enabled_core_loaders: # pyloader is disabled continue # must be Python file or module # load from default defined module settings.py or .secrets.py if exists py_loader.load( obj, mod_file, key=key, validate=validate, identifier=identifier ) # load from the current env e.g: development_settings.py # counting on the case where env is a comma separated string env = env or obj.current_env if env and isinstance(env, str): for env_name in env.split(","): load_from_env_named_file( obj, env_name, key, validate, identifier, mod_file ) def load_from_env_named_file(obj, env, key, validate, identifier, mod_file): """Load from env named file e.g: development_settings.py""" if mod_file.endswith(".py"): if ".secrets.py" == mod_file: tmpl = ".{0}_{1}{2}" mod_file = "secrets.py" else: tmpl = "{0}_{1}{2}" dirname = os.path.dirname(mod_file) filename, extension = os.path.splitext(os.path.basename(mod_file)) new_filename = tmpl.format(env.lower(), filename, extension) env_mod_file = os.path.join(dirname, new_filename) global_filename = tmpl.format("global", filename, extension) global_mod_file = os.path.join(dirname, global_filename) else: parts = mod_file.rsplit(".", 1) if len(parts) > 1: head, tail = parts else: head, tail = None, parts[0] tail = env_mod_file = f"{env.lower()}_{tail}" if head: env_mod_file = f"{head}.{tail}" global_mod_file = f"{head}.global_{tail}" else: env_mod_file = tail global_mod_file = f"global_{tail}" source_metadata = SourceMetadata( loader="py", identifier=identifier, env=env, ) py_loader.load( obj, env_mod_file, identifier=source_metadata, silent=True, key=key, validate=validate, ) # load from global_settings.py py_loader.load( obj, global_mod_file, identifier="py_global", silent=True, key=key, validate=validate, ) def enable_external_loaders(obj): """Enable external service loaders like `VAULT_` and `REDIS_` looks forenv variables like `REDIS_ENABLED_FOR_DYNACONF` """ for name, loader in ct.EXTERNAL_LOADERS.items(): enabled = getattr(obj, f"{name.upper()}_ENABLED_FOR_DYNACONF", False) if ( enabled and enabled not in false_values and loader not in obj.LOADERS_FOR_DYNACONF ): # noqa obj.LOADERS_FOR_DYNACONF.insert(0, loader) def write(filename, data, env=None, merge=False): """Writes `data` to `filename` infers format by file extension.""" loader_name = f"{filename.rpartition('.')[-1]}_loader" loader = globals().get(loader_name) if not loader: 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: data = {env: data} loader.write(filename, data, merge=merge) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/loaders/base.py0000644000000000000000000002017515156324234020265 0ustar00pbrochadpbrochadfrom __future__ import annotations import warnings from typing import NamedTuple from dynaconf.utils import build_env_list from dynaconf.utils import ensure_a_list from dynaconf.utils import upperfy from dynaconf.utils.functional import empty class BaseLoader: """Base loader for dynaconf source files. :param obj: {[LazySettings]} -- [Dynaconf settings] :param env: {[string]} -- [the current env to be loaded defaults to [development]] :param identifier: {[string]} -- [identifier ini, yaml, json, py, toml] :param extensions: {[list]} -- [List of extensions with dots ['.a', '.b']] :param file_reader: {[callable]} -- [reads file return dict] :param string_reader: {[callable]} -- [reads string return dict] """ def __init__( self, obj, env, identifier, extensions, file_reader, string_reader, opener_params=None, validate=False, ): """Instantiates a loader for different sources""" self.obj = obj self.env = env or obj.current_env self.identifier = identifier 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"), } self.validate = validate @staticmethod def warn_not_installed(obj, identifier): # pragma: no cover if identifier not in obj._not_installed_warnings: warnings.warn( f"{identifier} support is not installed in your environment. " f"`pip install dynaconf[{identifier}]`" ) obj._not_installed_warnings.append(identifier) def load(self, filename=None, key=None, silent=True, merge=empty): """ Reads and loads in to `self.obj` a single key or all keys from source :param filename: Optional filename to load :param key: if provided load a single key :param silent: if load errors should be silenced """ filename = filename or self.obj.get(self.identifier.upper()) if not filename: return if not isinstance(filename, (list, tuple)): split_files = ensure_a_list(filename) if all([f.endswith(self.extensions) for f in split_files]): # noqa files = split_files # it is a ['file.ext', ...] else: # it is a single config as string files = [filename] else: # it is already a list/tuple files = filename source_data = self.get_source_data(files) if ( self.env == "_envless" or self.obj.get("ENVIRONMENTS_FOR_DYNACONF") is False ): self._envless_load(source_data, silent, key) else: self._load_all_envs(source_data, silent, key) def get_source_data(self, files): """Reads each file and returns source data for each file {"path/to/file.ext": {"key": "value"}} """ data = {} for source_file in files: if source_file.endswith(self.extensions): try: 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 OSError as e: if ".local." not in source_file: warnings.warn( f"{self.identifier}_loader: {source_file} " f":{str(e)}" ) else: # for tests it is possible to pass string content = self.string_reader(source_file) if content: data[source_file] = content return data def _envless_load(self, source_data, silent=True, key=None): """Load all the keys from each file without env separation""" for file_name, file_data in source_data.items(): # is there a `dynaconf_merge` on top level of file? file_merge = file_data.get("dynaconf_merge", empty) # set source metadata source_metadata = SourceMetadata( self.identifier, file_name, "default" ) self._set_data_to_obj( file_data, source_metadata, file_merge=file_merge, key=key, ) def _load_all_envs(self, source_data, silent=True, key=None): """ Load configs from files separating by each environment source_data should have format: { "path/to/src": { "env": {...}, "env2": {...} } } """ for file_name, file_data in source_data.items(): # env name is checked in lower file_data = {k.lower(): value for k, value in file_data.items()} # is there a `dynaconf_merge` on top level of file? file_merge = file_data.get("dynaconf_merge", empty) # 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 # print(self.env, file_data) # set source metadata source_metadata = SourceMetadata( self.identifier, file_name, env ) try: data = file_data[env] or {} except KeyError: if silent: continue raise if not data: continue self._set_data_to_obj( data, source_metadata, file_merge, key, file_dotted_lookup=file_dotted_lookup, ) def _set_data_to_obj( self, data, identifier: SourceMetadata, file_merge=empty, key=False, file_dotted_lookup=None, ): """Calls settings.set to add the keys""" # data 1st level keys should be transformed to upper case. data = {upperfy(k): v for k, v in data.items()} if key: key = upperfy(key) if self.obj.filter_strategy: data = self.obj.filter_strategy(data) # is there a `dynaconf_merge` inside an `[env]`? env_scope_merge = data.pop("DYNACONF_MERGE", None) if env_scope_merge is not None: file_merge = env_scope_merge # 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, validate=self.validate, ) elif key in data: self.obj.set( key, data.get(key), loader_identifier=identifier, merge=file_merge, dotted_lookup=file_dotted_lookup, validate=self.validate, ) class SourceMetadata(NamedTuple): """ Useful metadata about some loaded source (file, envvar, etc). Serve as a unique identifier for data from a specific env and a specific source (file, envvar, validationd default, etc) Examples: SourceMetadata(loader="envvar", identifier="os", env="global") SourceMetadata(loader="yaml", identifier="path/to/file.yml", env="dev") """ loader: str identifier: str env: str = "global" merged: bool = False ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/loaders/env_loader.py0000644000000000000000000001066215156324234021471 0ustar00pbrochadpbrochadfrom __future__ import annotations from contextlib import suppress from os import environ from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import missing from dynaconf.utils import upperfy from dynaconf.utils.parse_conf import boolean_fix from dynaconf.utils.parse_conf import parse_conf_data DOTENV_IMPORTED = False with suppress(ImportError, FileNotFoundError): from dynaconf.vendor.dotenv import cli as dotenv_cli DOTENV_IMPORTED = True IDENTIFIER_PREFIX = "env" def load( obj, env=None, silent=True, key=None, validate=False, identifier="global" ): """Loads envvars with prefixes: `DYNACONF_` (default global) or `$(ENVVAR_PREFIX_FOR_DYNACONF)_` if envvar_prefix is set to: str: -> load {str}_* str,str1 -> load [{str}_, {str1}_] False -> load * None -> return not loading anything """ global_prefix = obj.get("ENVVAR_PREFIX_FOR_DYNACONF") if global_prefix is None: return if global_prefix is False or global_prefix.upper() != "DYNACONF": load_from_env( obj, "DYNACONF", key, silent, f"{IDENTIFIER_PREFIX}_{identifier}", validate=validate, ) # Load the global env if exists and overwrite everything # if the prefix is separated by comma then load all prefixes # counting on the case where global_prefix is set to None, False or "" prefixes = global_prefix.split(",") if global_prefix else [global_prefix] for prefix in prefixes: load_from_env( obj, prefix, key, silent, f"{IDENTIFIER_PREFIX}_{identifier}", validate=validate, ) def load_from_env( obj, prefix=False, key=None, silent=False, identifier=IDENTIFIER_PREFIX, env=False, # backwards compatibility bc renamed param validate=False, ): if prefix is False and env is not False: prefix = env env_ = "" if prefix is not False: if not isinstance(prefix, str): raise TypeError("`prefix/env` must be str or False") prefix = prefix.upper() env_ = f"{prefix}_" # set source metadata source_metadata = SourceMetadata(identifier, prefix, "global") # Load a single environment variable explicitly. if key: key = upperfy(key) value = environ.get(f"{env_}{key}") if value: try: # obj is a Settings obj.set( key, boolean_fix(value), loader_identifier=source_metadata, tomlfy=True, validate=validate, ) except AttributeError: # obj is a dict obj[key] = parse_conf_data( boolean_fix(value), tomlfy=True, box_settings=obj ) # Load environment variables in bulk (when matching). else: # Only known variables should be loaded from environment? ignore_unknown = obj.get("IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF") # prepare data trim_len = len(env_) data = { key[trim_len:]: parse_conf_data( boolean_fix(value), tomlfy=True, box_settings=obj ) for key, value in environ.items() if key.startswith(env_) and not ( # Ignore environment variables that haven't been # pre-defined in settings space. ignore_unknown 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=source_metadata, validate=validate ) def write(settings_path, settings_data, **kwargs): """Write data to .env file""" if not DOTENV_IMPORTED: # pragma: no cover return for key, value in settings_data.items(): quote_mode = ( isinstance(value, str) and (value.startswith("'") or value.startswith('"')) ) or isinstance(value, (list, dict)) dotenv_cli.set_key( str(settings_path), key, str(value), quote_mode="always" if quote_mode else "none", ) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/loaders/ini_loader.py0000644000000000000000000000433515156311032021450 0ustar00pbrochadpbrochadfrom __future__ import annotations from pathlib import Path from dynaconf import default_settings from dynaconf.constants import INI_EXTENSIONS from dynaconf.loaders.base import BaseLoader from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import object_merge try: from configobj import ConfigObj except ImportError: # pragma: no cover ConfigObj = None def load( obj, env=None, silent=True, key=None, filename=None, validate=False, identifier="ini", ): """ Reads and loads in to "obj" a single key or all keys from source file. :param obj: the settings instance :param env: settings current env default='development' :param silent: if errors should raise :param key: if defined load a single key, else load all in env :param filename: Optional custom filename to load :return: None """ if ConfigObj is None: # pragma: no cover BaseLoader.warn_not_installed(obj, "ini") return # when load_file function is called directly it comes with module and line number if isinstance(identifier, SourceMetadata) and identifier.loader.startswith( "load_file" ): identifier = identifier.loader loader = BaseLoader( obj=obj, env=env, identifier=identifier, extensions=INI_EXTENSIONS, file_reader=lambda fileobj: ConfigObj(fileobj).dict(), string_reader=lambda strobj: ConfigObj(strobj.split("\n")).dict(), validate=validate, ) loader.load( filename=filename, key=key, silent=silent, ) def write(settings_path, settings_data, merge=True): """Write data to a settings file. :param settings_path: the filepath :param settings_data: a dictionary with data :param merge: boolean if existing file should be merged with new data """ settings_path = Path(settings_path) if settings_path.exists() and merge: # pragma: no cover with open( str(settings_path), encoding=default_settings.ENCODING_FOR_DYNACONF ) as open_file: object_merge(ConfigObj(open_file).dict(), settings_data) new = ConfigObj() new.update(settings_data) new.write(open(str(settings_path), "bw")) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/loaders/json_loader.py0000644000000000000000000000525515156311032021644 0ustar00pbrochadpbrochadfrom __future__ import annotations import json from pathlib import Path from dynaconf import default_settings from dynaconf.constants import JSON_EXTENSIONS from dynaconf.loaders.base import BaseLoader from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import object_merge from dynaconf.utils.parse_conf import try_to_encode try: # pragma: no cover import commentjson except ImportError: # pragma: no cover commentjson = None def load( obj, env=None, silent=True, key=None, filename=None, validate=False, identifier="json", ): """ Reads and loads in to "obj" a single key or all keys from source file. :param obj: the settings instance :param env: settings current env default='development' :param silent: if errors should raise :param key: if defined load a single key, else load all in env :param filename: Optional custom filename to load :return: None """ if ( obj.get("COMMENTJSON_ENABLED_FOR_DYNACONF") and commentjson ): # pragma: no cover # noqa file_reader = commentjson.load string_reader = commentjson.loads else: file_reader = json.load string_reader = json.loads # when load_file function is called directly it comes with module and line number if isinstance(identifier, SourceMetadata) and identifier.loader.startswith( "load_file" ): identifier = identifier.loader loader = BaseLoader( obj=obj, env=env, identifier=identifier, extensions=JSON_EXTENSIONS, file_reader=file_reader, string_reader=string_reader, validate=validate, ) loader.load( filename=filename, key=key, silent=silent, ) def write(settings_path, settings_data, merge=True): """Write data to a settings file. :param settings_path: the filepath :param settings_data: a dictionary with data :param merge: boolean if existing file should be merged with new data """ settings_path = Path(settings_path) if settings_path.exists() and merge: # pragma: no cover with open( str(settings_path), encoding=default_settings.ENCODING_FOR_DYNACONF ) as open_file: object_merge(json.load(open_file), settings_data) with open( str(settings_path), "w", encoding=default_settings.ENCODING_FOR_DYNACONF, ) as open_file: json.dump(settings_data, open_file, cls=DynaconfEncoder) class DynaconfEncoder(json.JSONEncoder): """Transform Dynaconf custom types instances to json representation""" def default(self, o): return try_to_encode(o, callback=super().default) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/loaders/py_loader.py0000644000000000000000000001453615156324234021335 0ustar00pbrochadpbrochadfrom __future__ import annotations import errno import importlib import inspect import types from contextlib import suppress from pathlib import Path from dynaconf import default_settings from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import DynaconfDict from dynaconf.utils import object_merge from dynaconf.utils import upperfy from dynaconf.utils.files import find_file from dynaconf.utils.functional import empty def load( obj, settings_module, identifier="py", silent=False, key=None, validate=False, ): """ Tries to import a python module Notes: It doesn't handle environment namespaces explicitly. Eg [default], [development], etc See tests/test_nested_loading.py sample python file """ mod, loaded_from = get_module(obj, settings_module, silent) if not (mod and loaded_from): return # setup SourceMetadata (for inspecting) if isinstance(identifier, SourceMetadata): loader_identifier = SourceMetadata( identifier.loader, mod.__name__, identifier.env ) else: loader_identifier = SourceMetadata(identifier, mod.__name__, "global") load_from_python_object( obj, mod, settings_module, key, loader_identifier, validate=validate ) def load_from_python_object( obj, mod, settings_module, key=None, identifier=None, validate=False ): file_merge = getattr(mod, "dynaconf_merge", empty) if file_merge is empty: file_merge = getattr(mod, "DYNACONF_MERGE", empty) for setting in dir(mod): setting_value = getattr(mod, setting) # A setting var in a Python file should start with upper case # valid: A_value=1, ABC_value=3 A_BBB__default=1 # invalid: a_value=1, MyValue=3 # This is to avoid loading functions, classes and built-ins if setting.split("__")[0].isupper(): if key is None or key == setting: obj.set( setting, setting_value, loader_identifier=identifier, merge=file_merge, validate=validate, ) # if setting (name) starts with _dynaconf_hook # and the value is a callable # then we want to add it to the post_hooks list on the obj # we use the name instead checking on an attribute to avoid # loading a lazy object early in the process elif setting.startswith("_dynaconf_hook") and callable(setting_value): if setting_value not in obj._post_hooks: obj._post_hooks.append(setting_value) obj._loaded_py_modules.append(mod.__name__) obj._loaded_files.append(mod.__file__) def try_to_load_from_py_module_name( obj, name, key=None, identifier="py", silent=False, validate=False ): """Try to load module by its string name. Arguments: obj {LAzySettings} -- Dynaconf settings instance name {str} -- Name of the module e.g: foo.bar.zaz Keyword Arguments: key {str} -- Single key to be loaded (default: {None}) identifier {str} -- Name of identifier to store (default: 'py') silent {bool} -- Weather to raise or silence exceptions. """ ctx = suppress(ImportError, TypeError) if silent else suppress() # setup SourceMetadata (for inspecting) if isinstance(identifier, SourceMetadata): loader_identifier = identifier else: loader_identifier = SourceMetadata(identifier, name, "global") with ctx: mod = importlib.import_module(str(name)) load_from_python_object( obj, mod, name, key, loader_identifier, validate=validate ) return True # loaded ok! # if it reaches this point that means exception occurred, module not found. return False def get_module(obj, filename, silent=False): try: mod = importlib.import_module(filename) loaded_from = "module" mod.is_error = False except (ImportError, TypeError): mod = import_from_filename(obj, filename, silent=silent) if mod and not mod._is_error: loaded_from = "filename" else: # it is important to return None in case of not loaded loaded_from = None return mod, loaded_from def import_from_filename(obj, filename, silent=False): # pragma: no cover """If settings_module is a filename path import it.""" if filename in [item.filename for item in inspect.stack()]: raise ImportError( "Looks like you are loading dynaconf " f"from inside the {filename} file and then it is trying " "to load itself entering in a circular reference " "problem. To solve it you have to " "invoke your program from another root folder " "or rename your program file." ) _find_file = getattr(obj, "find_file", find_file) if not filename.endswith(".py"): filename = f"{filename}.py" if filename in default_settings.SETTINGS_FILE_FOR_DYNACONF: silent = True mod = types.ModuleType(filename.rstrip(".py")) mod.__file__ = filename mod._is_error = False mod._error = None try: with open( _find_file(filename), encoding=default_settings.ENCODING_FOR_DYNACONF, ) as config_file: exec(compile(config_file.read(), filename, "exec"), mod.__dict__) except OSError as e: e.strerror = ( f"py_loader: error loading file " f"({e.strerror} {filename})\n" ) if silent and e.errno in (errno.ENOENT, errno.EISDIR): return mod._is_error = True mod._error = e return mod def write(settings_path, settings_data, merge=True): """Write data to a settings file. :param settings_path: the filepath :param settings_data: a dictionary with data :param merge: boolean if existing file should be merged with new data """ settings_path = Path(settings_path) if settings_path.exists() and merge: # pragma: no cover existing = DynaconfDict() load(existing, str(settings_path)) object_merge(existing, settings_data) with open( str(settings_path), "w", encoding=default_settings.ENCODING_FOR_DYNACONF, ) as f: f.writelines( [f"{upperfy(k)} = {repr(v)}\n" for k, v in settings_data.items()] ) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/loaders/redis_loader.py0000644000000000000000000001001215156324234021774 0ustar00pbrochadpbrochadfrom __future__ import annotations from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import build_env_list from dynaconf.utils import upperfy from dynaconf.utils.parse_conf import parse_conf_data from dynaconf.utils.parse_conf import unparse_conf_data try: from redis import StrictRedis except ImportError: StrictRedis = None IDENTIFIER = "redis" def load(obj, env=None, silent=True, key=None, validate=False): """Reads and loads in to "settings" a single key or all keys from redis :param obj: the settings instance :param env: settings env default='DYNACONF' :param silent: if errors should raise :param key: if defined load a single key, else load all in env :return: None """ if StrictRedis is None: raise ImportError( "redis package is not installed in your environment. " "`pip install dynaconf[redis]` or disable the redis loader with " "export REDIS_ENABLED_FOR_DYNACONF=false" ) redis = StrictRedis(**obj.get("REDIS_FOR_DYNACONF")) prefix = obj.get("ENVVAR_PREFIX_FOR_DYNACONF") env_list = build_env_list(obj, env or obj.current_env) # prefix is added to env_list to keep backwards compatibility if prefix: env_list.insert(0, prefix) for env_name in env_list: if prefix: holder = f"{prefix.upper()}_{env_name.upper()}" else: holder = env_name.upper() try: source_metadata = SourceMetadata(IDENTIFIER, "unique", env_name) if key: value = redis.hget(holder.upper(), key) if value: parsed_value = parse_conf_data( value, tomlfy=True, box_settings=obj ) if parsed_value: obj.set( key, parsed_value, validate=validate, loader_identifier=source_metadata, ) else: data = { key: parse_conf_data(value, tomlfy=True, box_settings=obj) for key, value in redis.hgetall(holder.upper()).items() } if data: obj.update( data, loader_identifier=source_metadata, validate=validate, ) except Exception: if silent: return False raise def write(obj, data=None, **kwargs): """Write a value in to loader source :param obj: settings object :param data: vars to be stored :param kwargs: vars to be stored :return: """ if obj.REDIS_ENABLED_FOR_DYNACONF is False: raise RuntimeError( "Redis is not configured \n" "export REDIS_ENABLED_FOR_DYNACONF=true\n" "and configure the REDIS_*_FOR_DYNACONF variables" ) client = StrictRedis(**obj.REDIS_FOR_DYNACONF) holder = obj.get("ENVVAR_PREFIX_FOR_DYNACONF").upper() # add env to holder holder = f"{holder}_{obj.current_env.upper()}" data = data or {} data.update(kwargs) if not data: raise AttributeError("Data must be provided") redis_data = { upperfy(key): unparse_conf_data(value) for key, value in data.items() } client.hset(holder.upper(), mapping=redis_data) load(obj) def delete(obj, key=None): """ Delete a single key if specified, or all env if key is none :param obj: settings object :param key: key to delete from store location :return: None """ client = StrictRedis(**obj.REDIS_FOR_DYNACONF) holder = obj.get("ENVVAR_PREFIX_FOR_DYNACONF").upper() # add env to holder holder = f"{holder}_{obj.current_env.upper()}" if key: client.hdel(holder.upper(), upperfy(key)) obj.unset(key) else: keys = client.hkeys(holder.upper()) client.delete(holder.upper()) obj.unset_all(keys) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/loaders/toml_loader.py0000644000000000000000000001102515156324234021646 0ustar00pbrochadpbrochadfrom __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.loaders.base import SourceMetadata from dynaconf.utils import object_merge 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, validate=False, identifier="toml", ): """ Reads and loads in to "obj" a single key or all keys from source file. :param obj: the settings instance :param env: settings current env default='development' :param silent: if errors should raise :param key: if defined load a single key, else load all in env :param filename: Optional custom filename to load :return: None """ # when load_file function is called directly it comes with module and line number if isinstance(identifier, SourceMetadata) and identifier.loader.startswith( "load_file" ): identifier = identifier.loader try: loader = BaseLoader( obj=obj, env=env, identifier=identifier, extensions=TOML_EXTENSIONS, file_reader=tomllib.load, string_reader=tomllib.loads, opener_params={"mode": "rb"}, validate=validate, ) 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=identifier, extensions=TOML_EXTENSIONS, file_reader=toml.load, string_reader=toml.loads, validate=validate, ) 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): """Write data to a settings file. :param settings_path: the filepath :param settings_data: a dictionary with data :param merge: boolean if existing file should be merged with new data """ settings_path = Path(settings_path) if settings_path.exists() and merge: # pragma: no cover 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: 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): """TOML does not support `None` so this function transforms to '@none '.""" if data is None: return "@none " if isinstance(data, dict): return {key: encode_nulls(value) for key, value in data.items()} elif isinstance(data, (list, tuple)): return [encode_nulls(item) for item in data] return data ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/loaders/vault_loader.py0000644000000000000000000001643315156311032022026 0ustar00pbrochadpbrochad# docker run -e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' -p 8200:8200 vault # pip install hvac from __future__ import annotations from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import build_env_list from dynaconf.utils.parse_conf import parse_conf_data try: import boto3 except ImportError: boto3 = None try: from hvac import Client from hvac.exceptions import Forbidden from hvac.exceptions import InvalidPath except ImportError: raise ImportError( "vault package is not installed in your environment. " "`pip install dynaconf[vault]` or disable the vault loader with " "export VAULT_ENABLED_FOR_DYNACONF=false" ) IDENTIFIER = "vault" # backwards compatibility _get_env_list = build_env_list def get_client(obj): client = Client( **{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.login( role_id=obj.VAULT_ROLE_ID_FOR_DYNACONF, secret_id=obj.get("VAULT_SECRET_ID_FOR_DYNACONF"), ) elif obj.VAULT_ROOT_TOKEN_FOR_DYNACONF is not None: client.token = obj.VAULT_ROOT_TOKEN_FOR_DYNACONF elif obj.VAULT_USERNAME_FOR_DYNACONF is not None: client.auth.userpass.login( username=obj.VAULT_USERNAME_FOR_DYNACONF, password=obj.VAULT_PASSWORD_FOR_DYNACONF, ) elif obj.VAULT_AUTH_WITH_IAM_FOR_DYNACONF: if boto3 is None: raise ImportError( "boto3 package is not installed in your environment. " "`pip install boto3` or disable the VAULT_AUTH_WITH_IAM" ) session = boto3.Session() credentials = session.get_credentials() client.auth.aws.iam_login( credentials.access_key, credentials.secret_key, credentials.token, role=obj.VAULT_AUTH_ROLE_FOR_DYNACONF, ) if obj.VAULT_TOKEN_RENEW_FOR_DYNACONF: client.auth.token.renew_self() assert client.is_authenticated(), ( "Vault authentication error: is VAULT_TOKEN_FOR_DYNACONF or " "VAULT_ROLE_ID_FOR_DYNACONF defined?" ) client.secrets.kv.default_kv_version = obj.VAULT_KV_VERSION_FOR_DYNACONF return client def load(obj, env=None, silent=None, key=None, validate=False): """Reads and loads in to "settings" a single key or all keys from vault :param obj: the settings instance :param env: settings env default='DYNACONF' :param silent: if errors should raise :param key: if defined load a single key, else load all in env :return: None """ client = get_client(obj) try: 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 = [] except Forbidden: # The given token does not have permission to list the given path dirs = [] # 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: if obj.VAULT_KV_VERSION_FOR_DYNACONF == 2: data = client.secrets.kv.v2.read_secret_version( path, mount_point=obj.VAULT_MOUNT_POINT_FOR_DYNACONF, raise_on_deleted_version=True, # keep default behavior ) else: data = client.secrets.kv.read_secret( "data/" + path, mount_point=obj.VAULT_MOUNT_POINT_FOR_DYNACONF, ) except InvalidPath: # If the path doesn't exist, ignore it and set data to None data = None except Forbidden: data = None if data: # There seems to be a data dict within a data dict, # extract the inner data data = data.get("data", {}).get("data", {}) try: source_metadata = SourceMetadata(IDENTIFIER, "unique", env) 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( data.get(key), tomlfy=True, box_settings=obj ) if value: obj.set( key, value, validate=validate, loader_identifier=source_metadata, ) elif data: obj.update( data, loader_identifier=source_metadata, tomlfy=True, validate=validate, ) except Exception: if silent: return False raise def write(obj, data=None, **kwargs): """Write a value in to loader source :param obj: settings object :param data: vars to be stored :param kwargs: vars to be stored :return: """ if obj.VAULT_ENABLED_FOR_DYNACONF is False: raise RuntimeError( "Vault is not configured \n" "export VAULT_ENABLED_FOR_DYNACONF=true\n" "and configure the VAULT_FOR_DYNACONF_* variables" ) data = data or {} data.update(kwargs) if not data: raise AttributeError("Data must be provided") data = {"data": data} client = get_client(obj) if obj.VAULT_KV_VERSION_FOR_DYNACONF == 1: mount_point = obj.VAULT_MOUNT_POINT_FOR_DYNACONF + "/data" else: mount_point = obj.VAULT_MOUNT_POINT_FOR_DYNACONF path = "/".join([obj.VAULT_PATH_FOR_DYNACONF, obj.current_env.lower()]) client.secrets.kv.create_or_update_secret( path, secret=data, mount_point=mount_point ) load(obj) def list_envs(obj, path=""): """ This function is a helper to get a list of all the existing envs in the source of data, the use case is: existing_envs = vault_loader.list_envs(settings) for env in exiting_envs: with settings.using_env(env): # switch to the env # do something with a key of that env :param obj: settings object :param path: path to the vault secrets :return: list containing all the keys at the given path """ client = get_client(obj) path = path or obj.get("VAULT_PATH_FOR_DYNACONF") try: return client.list(f"/secret/metadata/{path}")["data"]["keys"] except TypeError: return [] ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/loaders/yaml_loader.py0000644000000000000000000001104515156324234021637 0ustar00pbrochadpbrochadfrom __future__ import annotations from pathlib import Path from warnings import warn from dynaconf import default_settings from dynaconf.constants import YAML_EXTENSIONS from dynaconf.loaders.base import BaseLoader from dynaconf.loaders.base import SourceMetadata from dynaconf.utils import object_merge from dynaconf.utils.parse_conf import try_to_encode from dynaconf.vendor.ruamel import yaml # Add support for Dynaconf Lazy values to YAML dumper yaml.SafeDumper.yaml_representers[None] = ( lambda self, data: yaml.representer.SafeRepresenter.represent_str( self, try_to_encode(data) ) ) class AllLoader(BaseLoader): """YAML Loader to load multi doc files""" @staticmethod def _assign_data(data, source_file, content): """Helper to iterate through all docs in a file""" content = tuple(content) if len(content) == 1: data[source_file] = content[0] elif len(content) > 1: for i, doc in enumerate(content): data[f"{source_file}[{i}]"] = doc def get_source_data(self, files): data = {} for source_file in files: if source_file.endswith(self.extensions): try: with open(source_file, **self.opener_params) as open_file: content = self.file_reader(open_file) self.obj._loaded_files.append(source_file) self._assign_data(data, source_file, content) except OSError as e: if ".local." not in source_file: warn( f"{self.identifier}_loader: {source_file} " f":{str(e)}" ) else: # for tests it is possible to pass string content = self.string_reader(source_file) self._assign_data(data, source_file, content) return data def load( obj, env=None, silent=True, key=None, filename=None, validate=False, identifier="yaml", ): """ Reads and loads in to "obj" a single key or all keys from source file. :param obj: the settings instance :param env: settings current env default='development' :param silent: if errors should raise :param key: if defined load a single key, else load all in env :param filename: Optional custom filename to load :return: None """ # Resolve the loaders # https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation # Possible values are: # `safe_load, full_load, unsafe_load, load, safe_load_all` yaml_reader = getattr( yaml, obj.get("YAML_LOADER_FOR_DYNACONF"), yaml.safe_load ) if yaml_reader.__name__ == "unsafe_load": # pragma: no cover warn( "yaml.unsafe_load is deprecated." " Please read https://msg.pyyaml.org/load for full details." " Try to use full_load or safe_load." ) _loader = BaseLoader if yaml_reader.__name__.endswith("_all"): _loader = AllLoader # when load_file function is called directly it comes with module and line number if isinstance(identifier, SourceMetadata) and identifier.loader.startswith( "load_file" ): identifier = identifier.loader loader = _loader( obj=obj, env=env, identifier=identifier, extensions=YAML_EXTENSIONS, file_reader=yaml_reader, string_reader=yaml_reader, validate=validate, ) loader.load( filename=filename, key=key, silent=silent, ) def write(settings_path, settings_data, merge=True): """Write data to a settings file. :param settings_path: the filepath :param settings_data: a dictionary with data :param merge: boolean if existing file should be merged with new data :param stdout: boolean if should output to stdout instead of file """ settings_path = Path(settings_path) if settings_path.exists() and merge: # pragma: no cover with open( str(settings_path), encoding=default_settings.ENCODING_FOR_DYNACONF ) as open_file: object_merge(yaml.safe_load(open_file), settings_data) with open( str(settings_path), "w", encoding=default_settings.ENCODING_FOR_DYNACONF, ) as open_file: yaml.dump( settings_data, open_file, Dumper=yaml.dumper.SafeDumper, explicit_start=True, indent=2, default_flow_style=False, ) ././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9142458 dynaconf-3.2.13/dynaconf/strategies/0000755000000000000000000000000015156326623017521 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/strategies/__init__.py0000644000000000000000000000000015156311032021604 0ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/strategies/filtering.py0000644000000000000000000000152315156311032022043 0ustar00pbrochadpbrochadfrom __future__ import annotations from dynaconf.utils import upperfy class PrefixFilter: """Filter environment variables by prefix. Examples: Please see [Prefix filtering][prefix-filtering] in the advanced usage section. """ def __init__(self, prefix: str): """Initialises the PrefixFilter class. Args: prefix: The prefix to filter on. """ if not isinstance(prefix, str): raise TypeError("`SETTINGS_FILE_PREFIX` must be str") self.prefix = f"{upperfy(prefix)}_" def __call__(self, data): """Filter incoming data by prefix.""" len_prefix = len(self.prefix) return { upperfy(key[len_prefix:]): value for key, value in data.items() if upperfy(key[:len_prefix]) == self.prefix } ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/test_settings.py0000644000000000000000000000025215156311032020603 0ustar00pbrochadpbrochad# pragma: no cover from __future__ import annotations TESTING = True LOADERS_FOR_DYNACONF = [ "dynaconf.loaders.env_loader", # 'dynaconf.loaders.redis_loader' ] ././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9146261 dynaconf-3.2.13/dynaconf/utils/0000755000000000000000000000000015156326623016507 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/utils/__init__.py0000644000000000000000000004224715156324234020625 0ustar00pbrochadpbrochadfrom __future__ import annotations import os import warnings from collections import defaultdict from functools import lru_cache from json import JSONDecoder from pathlib import Path from typing import Any from typing import Iterator from typing import TYPE_CHECKING from typing import TypeVar if TYPE_CHECKING: # pragma: no cover from dynaconf.base import LazySettings from dynaconf.base import Settings from dynaconf.utils.boxing import DynaBox BANNER = """ ██████╗ ██╗ ██╗███╗ ██╗ █████╗ ██████╗ ██████╗ ███╗ ██╗███████╗ ██╔══██╗╚██╗ ██╔╝████╗ ██║██╔══██╗██╔════╝██╔═══██╗████╗ ██║██╔════╝ ██║ ██║ ╚████╔╝ ██╔██╗ ██║███████║██║ ██║ ██║██╔██╗ ██║█████╗ ██║ ██║ ╚██╔╝ ██║╚██╗██║██╔══██║██║ ██║ ██║██║╚██╗██║██╔══╝ ██████╔╝ ██║ ██║ ╚████║██║ ██║╚██████╗╚██████╔╝██║ ╚████║██║ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ """ if os.name == "nt": # pragma: no cover # windows can't handle the above charmap BANNER = "DYNACONF" def object_merge( old: Any, new: Any, unique: bool = False, full_path: list[str] = None ) -> Any: """ Recursively merge two data structures, new is mutated in-place. :param old: The existing data. :param new: The new data to get old values merged in to. :param unique: When set to True existing list items are not set. :param full_path: Indicates the elements of a tree. """ if full_path is None: full_path = [] if old == new or old is None or new is None: # Nothing to 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) # doesn't handle None # Need to make every `None` on `_store` to be an wrapped `LazyNone` # 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()): all_keys = tuple(old.keys()) correct_case_key = find_the_correct_casing(new_key, all_keys) if correct_case_key: new[correct_case_key] = new.pop(new_key) def safe_items(data): """ Get items from DynaBox without triggering recursive evaluation """ if data.__class__.__name__ == "DynaBox": return data.items(bypass_eval=True) else: return data.items() # TODO @rochacbruno: Make the above line work with 2 dynaconf_merge # https://github.com/dynaconf/dynaconf/issues/todo # {"a": ["d", "e"], "dynaconf_merge": True}, # local mark may set dynaconf_merge=False should_merge = new.pop("dynaconf_merge", True) if should_merge: for old_key, value in safe_items(old): # 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 old_key.lower() == full_path[-1].lower() and existing_value is value ): # Here Be The Dragons # This comparison needs to be smarter continue if old_key not in new: new[old_key] = value else: object_merge( value, new[old_key], full_path=full_path[1:] if full_path else None, ) handle_metavalues(old, new) return new def recursive_get( 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 recursively all names one by one obj.foo.bar.zaz. """ if not names: return head, *tail = names result = getattr(obj, head, None) if not tail: return result return recursive_get(result, tail) def handle_metavalues( old: DynaBox | dict[str, int] | dict[str, str | int], new: Any ) -> None: """Cleanup of MetaValues on new dict""" for key in list(new.keys()): # MetaValue instances if getattr(new[key], "_dynaconf_reset", False): # pragma: no cover # a Reset on `new` triggers reasign of existing data new[key] = new[key].unwrap() elif getattr(new[key], "_dynaconf_del", False): # a Del on `new` triggers deletion of existing data new.pop(key, None) old.pop(key, None) elif getattr(new[key], "_dynaconf_merge", False): # a Merge on `new` triggers merge with existing data new[key] = object_merge( old.get(key), new[key].unwrap(), unique=new[key].unique ) elif getattr(new[key], "_dynaconf_insert", False): # Insert on `new` triggers insert with existing data # if existing is a list it inserts at specified .index # if existing is not a list it creates a new list with the value existing = old.get(key) # keep the same reference if isinstance(existing, list): # perform insert on it existing.insert(new[key].index, new[key].unwrap()) new[key] = existing else: new[key] = [new[key].unwrap()] # Data structures containing merge tokens if isinstance(new.get(key), (list, tuple)): has_merge = "dynaconf_merge" in new[key] has_merge_unique = "dynaconf_merge_unique" in new[key] if has_merge or has_merge_unique: value = list(new[key]) unique = False try: value.remove("dynaconf_merge") except ValueError: value.remove("dynaconf_merge_unique") unique = True for item in old.get(key)[::-1]: if unique and item in value: continue value.insert(0, item) new[key] = value elif isinstance(new.get(key), dict): local_merge = new[key].pop( "dynaconf_merge", new[key].pop("dynaconf_merge_unique", None) ) if local_merge not in (True, False, None) and not new[key]: # In case `dynaconf_merge:` holds value not boolean - ref #241 new[key] = local_merge if local_merge: new[key] = object_merge(old.get(key), new[key]) class DynaconfDict(dict): """A dict representing en empty Dynaconf object useful to run loaders in to a dict for testing""" def __init__(self, *args, **kwargs): self._fresh = False self._loaded_envs = [] self._loaded_hooks = defaultdict(dict) self._loaded_py_modules = [] self._loaded_files = [] self._deleted = set() self._store = {} self._env_cache = {} self._loaded_by_loaders = {} self._loaders = [] self._defaults = {} self.environ = os.environ self.SETTINGS_MODULE = None self.filter_strategy = kwargs.get("filter_strategy", None) self._not_installed_warnings = [] self._validate_only = kwargs.pop("validate_only", None) self._validate_exclude = kwargs.pop("validate_exclude", None) super().__init__(*args, **kwargs) def set(self, key: str, value: str, *args, **kwargs) -> None: self[key] = value @staticmethod def get_environ(key, default=None): # pragma: no cover return os.environ.get(key, default) def exists(self, key: str, **kwargs) -> bool: return self.get(key, missing) is not missing RENAMED_VARS = { # old: new "DYNACONF_NAMESPACE": "ENV_FOR_DYNACONF", "NAMESPACE_FOR_DYNACONF": "ENV_FOR_DYNACONF", "DYNACONF_SETTINGS_MODULE": "SETTINGS_FILE_FOR_DYNACONF", "DYNACONF_SETTINGS": "SETTINGS_FILE_FOR_DYNACONF", "SETTINGS_MODULE": "SETTINGS_FILE_FOR_DYNACONF", "SETTINGS_MODULE_FOR_DYNACONF": "SETTINGS_FILE_FOR_DYNACONF", "PROJECT_ROOT": "ROOT_PATH_FOR_DYNACONF", "PROJECT_ROOT_FOR_DYNACONF": "ROOT_PATH_FOR_DYNACONF", "DYNACONF_SILENT_ERRORS": "SILENT_ERRORS_FOR_DYNACONF", "DYNACONF_ALWAYS_FRESH_VARS": "FRESH_VARS_FOR_DYNACONF", "BASE_NAMESPACE_FOR_DYNACONF": "DEFAULT_ENV_FOR_DYNACONF", "GLOBAL_ENV_FOR_DYNACONF": "ENVVAR_PREFIX_FOR_DYNACONF", } 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(): if old in kwargs: kwargs[new] = kwargs[old] # update cross references for c_old, c_new in RENAMED_VARS.items(): if c_new == new: kwargs[c_old] = kwargs[new] class Missing: """ Sentinel value object/singleton used to differentiate between ambiguous situations where `None` is a valid value. """ def __bool__(self) -> bool: """Respond to boolean duck-typing.""" return False def __eq__(self, other: DynaBox | Missing) -> bool: """Equality check for a singleton.""" return isinstance(other, self.__class__) # Ensure compatibility with Python 2.x __nonzero__ = __bool__ def __repr__(self) -> str: """ Unambiguously identify this string-based representation of Missing, used as a singleton. """ return "" missing = Missing() def deduplicate(list_object: list[str]) -> list[str]: """Rebuild `list_object` removing duplicated and keeping order""" new = [] for item in list_object: if item not in new: new.append(item) return new def warn_deprecations(data: Any) -> None: for old, new in RENAMED_VARS.items(): if old in data: warnings.warn( f"You are using {old} which is a deprecated settings " f"replace it with {new}", DeprecationWarning, ) def trimmed_split( 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: continue data = [item.strip() for item in s.strip().split(sep)] return data return [s] # raw un-split T = TypeVar("T") def ensure_a_list(data: T | list[T]) -> list[T]: """Ensure data is a list or wrap it in a list""" if not data: return [] if isinstance(data, (list, tuple, set)): return list(data) if isinstance(data, str): data = trimmed_split(data) # settings.toml,other.yaml return data return [data] def build_env_list(obj: Settings | LazySettings, env: str | None) -> list[str]: """Build env list for loaders to iterate. Arguments: obj {LazySettings} -- A Dynaconf settings instance env {str} -- The current env to be loaded Returns: [str] -- A list of string names of the envs to load. """ # add the [default] env 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").lower() if global_env not in env_list: env_list.append(global_env) # add the current env counting on the case where it is a comma separated list current_env = obj.current_env if current_env and isinstance(current_env, str): for item in current_env.split(","): if item and (_name := item.strip().lower()) not in env_list: env_list.append(_name) # add a manually set env counting on the case where it is a comma separated list if env and isinstance(env, str): for item in env.split(","): if item and (_name := item.strip().lower()) not in env_list: env_list.append(_name) # add the [global] env env_list.append("global") return env_list def upperfy(key: str) -> str: """Receive a string key and returns its upper version. Example: input: foo output: FOO input: foo_bar output: FOO_BAR input: foo__bar__ZAZ output: FOO__bar__ZAZ Arguments: key {str} -- A string key that may contain dunders `__` Returns: The key as upper case but keeping the nested elements. """ key = str(key) if "__" in key: parts = key.split("__") return "__".join([parts[0].upper()] + parts[1:]) return key.upper() def multi_replace(text: str, patterns: dict[str, str]) -> str: """Replaces multiple pairs in a string Arguments: text {str} -- A "string text" patterns {dict} -- A dict of {"old text": "new text"} Returns: text -- str """ for old, new in patterns.items(): text = text.replace(old, new) return text def extract_json_objects( text: str, decoder: JSONDecoder = JSONDecoder() ) -> 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 of a parent JSON object. """ pos = 0 while True: match = text.find("{", pos) if match == -1: break try: result, index = decoder.raw_decode(text[match:]) yield result pos = match + index except ValueError: pos = match + 1 def recursively_evaluate_lazy_format( value: Any, settings: Settings | LazySettings ) -> Any: """Given a value as a data structure, traverse all its members to find Lazy values and evaluate it. For example: Evaluate values inside lists and dicts """ if getattr(value, "_dynaconf_lazy_format", None): value = value(settings) if isinstance(value, list): # This must be the right way of doing it, but breaks validators # To be changed on 4.0.0 # for idx, item in enumerate(value): # value[idx] = _recursively_evaluate_lazy_format(item, settings) value = value.__class__( [ recursively_evaluate_lazy_format(item, settings) for item in value ] ) return value def isnamedtupleinstance(value): """Check if value is a namedtuple instance stackoverflow.com/questions/2166818/ how-to-check-if-an-object-is-an-instance-of-a-namedtuple """ t = type(value) b = t.__bases__ if len(b) != 1 or b[0] != tuple: return False f = getattr(t, "_fields", None) if not isinstance(f, tuple): return False return all(isinstance(n, str) for n in f) @lru_cache def find_the_correct_casing( key: str, data_keys: tuple[Any, ...] ) -> str | None: """Given a key, find the proper casing in data. Return 'None' for non-str key types. Arguments: key {str} -- A key to be searched in data_keys data_keys {tuple} -- A tuple with all the dict keys Returns: str -- The proper casing of the key in data """ if not isinstance(key, str) or key in data_keys: return key for k in data_keys: if not isinstance(k, str): return None if k.lower() == key.lower(): return k if k.replace(" ", "_").lower() == key.lower(): return k return None def prepare_json(data: Any) -> Any: """Takes a data dict and transforms unserializable values to str for JSON. {1: PosixPath("/foo")} -> {"1": "/foo"} """ unserializable_types = (Path,) if isinstance(data, dict): return_data = {} for key, val in data.items(): value = str(val) if isinstance(val, unserializable_types) else val return_data[str(key)] = value return return_data elif isinstance(data, (list, tuple)): return_data = [] for val in data: value = str(val) if isinstance(val, unserializable_types) else val return_data.append(value) return return_data return data ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/utils/boxing.py0000644000000000000000000000575415156324234020356 0ustar00pbrochadpbrochadfrom __future__ import annotations import inspect from typing import Any from dynaconf.utils import find_the_correct_casing from dynaconf.utils import recursively_evaluate_lazy_format from dynaconf.utils.functional import empty from dynaconf.vendor.box import Box class DynaBox(Box): """Specialized Box for dynaconf it allows items/attrs to be found both in upper or lower case""" def __getattr__(self, item, *args, **kwargs): try: result = super().__getattr__(item, *args, **kwargs) except (AttributeError, KeyError): all_keys = tuple(self.keys()) n_item = find_the_correct_casing(item, all_keys) or item result = super().__getattr__(n_item, *args, **kwargs) return self.__evaluate_lazy__(result) def __getitem__(self, item, *args, **kwargs): try: result = super().__getitem__(item, *args, **kwargs) except (AttributeError, KeyError): all_keys = tuple(self.keys()) n_item = find_the_correct_casing(item, all_keys) or item result = super().__getitem__(n_item, *args, **kwargs) return self.__evaluate_lazy__(result) def get( self, item, default=None, bypass_eval=False, *args, **kwargs ) -> Any: # _TODO(pbrochad): refactor all these getter methods to make consistency easier if not bypass_eval: all_keys = tuple(self.keys()) n_item = find_the_correct_casing(item, all_keys) or item result = super().get(n_item, empty, *args, **kwargs) result = result if result is not empty else default return self.__evaluate_lazy__(result) try: return super().__getitem__(item, *args, **kwargs) except (AttributeError, KeyError): all_keys = tuple(self.keys()) n_item = find_the_correct_casing(item, all_keys) or item return super().__getitem__(n_item, *args, **kwargs) def __evaluate_lazy__(self, result): settings = self._box_config["box_settings"] return recursively_evaluate_lazy_format(result, settings) def __copy__(self): return self.__class__( super(Box, self).copy(), box_settings=self._box_config.get("box_settings"), ) def copy(self, bypass_eval=False): if not bypass_eval: return self.__class__( super(Box, self).copy(), box_settings=self._box_config.get("box_settings"), ) return self.__class__( {k: self.get(k, bypass_eval=True) for k in self.keys()}, box_settings=self._box_config.get("box_settings"), ) def __dir__(self): keys = list(self.keys()) reserved = [ item[0] for item in inspect.getmembers(DynaBox) if not item[0].startswith("__") ] return ( keys + [k.lower() for k in keys] + [k.upper() for k in keys] + reserved ) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/dynaconf/utils/files.py0000644000000000000000000001016315156311032020150 0ustar00pbrochadpbrochadfrom __future__ import annotations import inspect import os import re import sys from glob import glob as python_glob from dynaconf.utils import deduplicate 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 OSError("Starting path not found") if os.path.isfile(path): # pragma: no cover path = os.path.dirname(path) last_dir = None current_dir = os.path.abspath(path) paths = [] while last_dir != current_dir: paths.append(current_dir) paths.append(os.path.join(current_dir, "config")) if break_at and current_dir == os.path.abspath(break_at): # noqa break parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir)) last_dir, current_dir = current_dir, parent_dir return paths SEARCHTREE = [] def find_file(filename=".env", project_root=None, skip_files=None, **kwargs): """Search in increasingly higher folders for the given file Returns path to the file if found, or an empty string otherwise. This function will build a `search_tree` based on: - Project_root if specified - Invoked script location and its parents until root - Current working directory For each path in the `search_tree` it will also look for an additional `./config` folder. """ # If filename is an absolute path and exists, just return it # if the absolute path does not exist, return empty string so # that it can be joined and avoid IoError if os.path.isabs(filename): return filename if os.path.exists(filename) else "" search_tree = [] try: work_dir = os.getcwd() except FileNotFoundError: # pragma: no cover return "" skip_files = skip_files or [] if project_root is not None: search_tree.extend(_walk_to_root(project_root, break_at=work_dir)) script_dir = os.path.dirname(os.path.abspath(inspect.stack()[-1].filename)) # Path to invoked script and recursively to root with its ./config dirs search_tree.extend(_walk_to_root(script_dir)) # Path to where Python interpreter was invoked and recursively to root search_tree.extend(_walk_to_root(work_dir)) # Don't look the same place twice search_tree = deduplicate(search_tree) global SEARCHTREE SEARCHTREE[:] = search_tree for dirname in search_tree: check_path = os.path.join(dirname, filename) if check_path in skip_files: continue if os.path.exists(check_path): return check_path # First found will return # return empty string if not found so it can still be joined in os.path return "" def read_file(path, **kwargs): content = "" with open(path, **kwargs) as open_file: content = open_file.read().strip() return content def get_local_filename(filename): """Takes a filename like `settings.toml` and returns `settings.local.toml` Arguments: filename {str} -- The filename or complete path Returns: [str] -- The same name or path with `.local.` added. """ name, _, extension = os.path.basename(str(filename)).rpartition( os.path.extsep ) return os.path.join( os.path.dirname(str(filename)), f"{name}.local.{extension}" ) magic_check = re.compile("([*?[])") magic_check_bytes = re.compile(b"([*?[])") def has_magic(s): """Taken from python glob module""" if isinstance(s, bytes): match = magic_check_bytes.search(s) else: match = magic_check.search(s) return match is not None def glob( pathname, *, root_dir=None, dir_fd=None, recursive=True, include_hidden=True, ): """Redefined std glob assuming some defaults. and fallback for diffente python versions.""" glob_args = {"recursive": recursive} if sys.version_info >= (3, 10): glob_args["root_dir"] = root_dir glob_args["dir_fd"] = dir_fd if sys.version_info >= (3, 11): glob_args["include_hidden"] = include_hidden return python_glob(pathname, **glob_args) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/utils/functional.py0000644000000000000000000001074615156324234021227 0ustar00pbrochadpbrochadfrom __future__ import annotations import copy import operator class Empty: def __str__(self): return "EMPTY" empty = Empty() def new_method_proxy(func): def inner(self, *args): if self._wrapped is empty: self._setup() return func(self._wrapped, *args) return inner class LazyObject: """ A wrapper for another class that can be used to delay instantiation of the wrapped class. By subclassing, you have the opportunity to intercept and alter the instantiation. """ # Avoid infinite recursion when tracing __init__. _wrapped = None _kwargs = None _django_override = False def __init__(self): # Note: if a subclass overrides __init__(), it will likely need to # override __copy__() and __deepcopy__() as well. self._wrapped = empty __getattr__ = new_method_proxy(getattr) def __setattr__(self, name, value): if name in [ "_wrapped", "_kwargs", "_warn_dynaconf_global_settings", "_wrapper_class", ]: # Assign to __dict__ to avoid infinite __setattr__ loops. self.__dict__[name] = value else: if self._wrapped is empty: self._setup() setattr(self._wrapped, name, value) def __delattr__(self, name): if name in ["_wrapped", "_kwargs"]: raise TypeError(f"can't delete {name}.") if self._wrapped is empty: self._setup() delattr(self._wrapped, name) def _setup(self): """ Must be implemented by subclasses to initialize the wrapped object. """ raise NotImplementedError( "subclasses of LazyObject must provide a _setup() method" ) # Because we have messed with __class__ below, we confuse pickle as to what # class we are pickling. We're going to have to initialize the wrapped # object to successfully pickle it, so we might as well just pickle the # wrapped object since they're supposed to act the same way. # # Unfortunately, if we try to simply act like the wrapped object, the ruse # will break down when pickle gets our id(). Thus we end up with pickle # thinking, in effect, that we are a distinct object from the wrapped # object, but with the same __dict__. This can cause problems (see #25389). # # So instead, we define our own __reduce__ method and custom unpickler. We # pickle the wrapped object as the unpickler's argument, so that pickle # will pickle it normally, and then the unpickler simply returns its # argument. def __reduce__(self): if self._wrapped is empty: self._setup() return (unpickle_lazyobject, (self._wrapped,)) def __copy__(self): if self._wrapped is empty: # If uninitialized, copy the wrapper. Use type(self), not # self.__class__, because the latter is proxied. return type(self)() else: # If initialized, return a copy of the wrapped object. return copy.copy(self._wrapped) def __deepcopy__(self, memo): if self._wrapped is empty: # We have to use type(self), not self.__class__, because the # latter is proxied. result = type(self)() memo[id(self)] = result return result return copy.deepcopy(self._wrapped, memo) __bytes__ = new_method_proxy(bytes) __str__ = new_method_proxy(str) __bool__ = new_method_proxy(bool) # Introspection support __dir__ = new_method_proxy(dir) # Need to pretend to be the wrapped class, for the sake of objects that # care about this (especially in equality tests) __class__ = property(new_method_proxy(operator.attrgetter("__class__"))) __eq__ = new_method_proxy(operator.eq) __lt__ = new_method_proxy(operator.lt) __gt__ = new_method_proxy(operator.gt) __ne__ = new_method_proxy(operator.ne) __hash__ = new_method_proxy(hash) # List/Tuple/Dictionary methods support __getitem__ = new_method_proxy(operator.getitem) __setitem__ = new_method_proxy(operator.setitem) __delitem__ = new_method_proxy(operator.delitem) __iter__ = new_method_proxy(iter) __len__ = new_method_proxy(len) __contains__ = new_method_proxy(operator.contains) def unpickle_lazyobject(wrapped): """ Used to unpickle lazy objects. Just return its argument, which will be the wrapped object. """ return wrapped ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/utils/inspect.py0000644000000000000000000003500615156324234020526 0ustar00pbrochadpbrochad"""Inspecting module""" from __future__ import annotations import json import sys from contextlib import suppress from functools import partial from importlib.metadata import PackageNotFoundError from importlib.metadata import version from pathlib import PosixPath from typing import Any from typing import Callable from typing import Literal from typing import Protocol from typing import TextIO from typing import TYPE_CHECKING from typing import Union from dynaconf.loaders.base import SourceMetadata from dynaconf.utils.boxing import DynaBox from dynaconf.utils.functional import empty from dynaconf.utils.parse_conf import Lazy from dynaconf.vendor.box.box_list import BoxList from dynaconf.vendor.ruamel.yaml import YAML if TYPE_CHECKING: # pragma: no cover from dynaconf.base import LazySettings from dynaconf.base import Settings # Dumpers config json_pretty = partial(json.dump, indent=2, default=str) json_compact = partial(json.dump, default=str) def yaml_dumper_with_defaults(data: dict, text_stream: TextIO) -> None: """Easier way to get YAML dumper to handle unseralizable types""" yaml = YAML() # let JSON handle unserializable types and then load it back data = json.loads(json.dumps(data, default=str)) yaml.default_flow_style = False yaml.dump(data, text_stream) builtin_dumpers = { "yaml": yaml_dumper_with_defaults, "json": json_pretty, "json-compact": json_compact, } DumperPreset = Union[Literal["yaml"], Literal["json"], Literal["json-compact"]] class DumperType(Protocol): def __call__( self, data: dict, text_stream: TextIO ) -> None: # pragma: no cover ... class ReportBuilderType(Protocol): def __call__( self, *, key: str | None, env: str | None, new_first: bool | None, include_internal: bool | None, history_limit: int | None, current: Any, history: list[dict] | None, ) -> dict: # pragma: no cover ... class KeyNotFoundError(Exception): pass class EnvNotFoundError(Exception): pass class OutputFormatError(Exception): pass def inspect_settings( settings: Settings | LazySettings, key: str | None = None, env: str | None = None, *, new_first: bool = True, history_limit: int | None = None, include_internal: bool = False, to_file: str | PosixPath | None = None, print_report: bool = False, dumper: DumperPreset | DumperType | None = None, report_builder: ReportBuilderType | None = None, ): """ Print and return the loading history of a settings object. Optional arguments must be provided as kwargs. :param settings: A Dynaconf instance :param key: String dotted path. E.g "path.to.key" :param env: Filter by this env :param new_first: If True, uses newest to oldest loading order :param history_limit: Limits how many entries are shown :param include_internal: If True, include internal loaders (e.g. defaults). This has effect only if key is not provided. :param to_file: If specified, write to this filename :param print_report: If true, prints the dumped report to stdout :param dumper: Accepts preset strings (e.g. "yaml", "json") or custom dumper callable ``(dict, TextIO) -> None``. Defaults to "yaml" :param report_builder: if provided, it is used to generate the report :return: Dict with a dict containing report data :rtype: dict """ # choose dumper and report builder if dumper is None: _dumper = builtin_dumpers["yaml"] elif isinstance(dumper, str): _dumper = builtin_dumpers.get(dumper) if _dumper is None: raise OutputFormatError( f"The desired format is not available: {dumper!r}" ) else: _dumper = dumper _report_builder = report_builder or _default_report_builder # get history and apply optional arguments original_settings = settings env_filter = None # type: ignore if env: settings = settings.from_env(env) registered_envs = { src_meta.env for src_meta in settings._loaded_by_loaders.keys() } if env.lower() not in registered_envs: raise EnvNotFoundError(f"The requested env is not valid: {env!r}") def env_filter(src: SourceMetadata) -> bool: # noqa: F811 return src.env.lower() == env.lower() history = get_history( original_settings, key=key, filter_callable=env_filter, include_internal=include_internal, ) if new_first: history.reverse() if history_limit: history = history[:history_limit] if key: current_value = settings.get(key) else: current_value = settings.as_dict() # format output dict_report = _report_builder( history=history, current=current_value, key=key, env=env, new_first=new_first, history_limit=history_limit, include_internal=include_internal, ) dict_report["current"] = _ensure_serializable(dict_report["current"]) # write to stdout AND/OR to file AND return if to_file is not None: _encoding = settings.get("ENCODER_FOR_DYNACONF") with open(to_file, "w", encoding=_encoding) as file: _dumper(dict_report, file) if print_report is True: _dumper(dict_report, sys.stdout) return dict_report def _default_report_builder(**kwargs) -> dict: """ Default inspect report builder. Accept the kwargs passed inside `inspect_settings` and returns a dict with {header, current, history} as top-level keys. """ return { "header": { "env_filter": str(kwargs.get("env")), "key_filter": str(kwargs.get("key")), "new_first": str(kwargs.get("new_first")), "history_limit": str(kwargs.get("history_limit")), "include_internal": str(kwargs.get("include_internal")), }, "current": kwargs.get("current"), "history": kwargs.get("history"), } def get_history( obj: Settings | LazySettings, key: str | None = None, *, filter_callable: Callable[[SourceMetadata], bool] | None = None, include_internal: bool = False, history_limit: int | None = None, ) -> list[dict]: """ Gets data from `settings.loaded_by_loaders` in order of loading with optional filtering options. Returns a list of dict in new-first order, where the dict contains the data and it's source metadata. :param obj: Setting object which contain the data :param key: Key path to desired key. Use all if not provided :param filter_callable: Takes SourceMetadata and returns a boolean :param include_internal: If True, include internal loaders (e.g. defaults). This has effect only if key is not provided. history_limit: limits how many entries are shown Example: >>> settings = Dynaconf(...) >>> _get_history(settings) [ { "loader": "yaml" "identifier": "path/to/file.yml" "env": "default" "data": {"foo": 123, "spam": "eggs"} }, ... ] """ if filter_callable is None: filter_callable = lambda x: True # noqa sep = obj.get("NESTED_SEPARATOR_FOR_DYNACONF", "__") # trigger key based hooks if key: obj.get(key) # noqa internal_identifiers = ["default_settings", "_root_path"] result = [] for source_metadata, data in obj._loaded_by_loaders.items(): # filter by source_metadata if filter_callable(source_metadata) is False: continue # filter by internal identifiers if ( not key and include_internal is False and source_metadata.identifier in internal_identifiers ): continue # skip: internal loaders # filter by key path try: data = _get_data_by_key(data, key, sep=sep) if key else data except KeyError: continue # skip: source doesn't contain the requested key # Normalize output data = _ensure_serializable(data) result.append({**source_metadata._asdict(), "value": data}) if key and not result: # Key may be set in obj but history not tracked if (data := obj.get(key, empty)) is not empty: generic_source_metadata = SourceMetadata( loader="undefined", identifier="undefined", ) data = _ensure_serializable(data) result.append({**generic_source_metadata._asdict(), "value": data}) # Raise if still not found if key and not result: raise KeyNotFoundError(f"The requested key was not found: {key!r}") return result def _ensure_serializable(data: BoxList | DynaBox) -> dict | list: """ Converts box dict or list types to regular python dict or list Bypasses other values. { "foo": [1,2,3, {"a": "A", "b": "B"}], "bar": {"a": "A", "b": [1,2,3]}, } """ if isinstance(data, (BoxList, list)): return [_ensure_serializable(v) for v in data] elif isinstance(data, (DynaBox, dict)): return { k: _ensure_serializable(v) for k, v in data.items() # type: ignore } else: return data if isinstance(data, (int, bool, float)) else str(data) def _get_data_by_key( data: dict, key_dotted_path: str, default: Any = None, sep="__", ) -> Any: """ Returns value found in data[key] using dot-path str (e.g, "path.to.key"). Raises KeyError if not found """ if not isinstance(data, DynaBox): data = DynaBox(data) # DynaBox can handle insensitive keys if sep in key_dotted_path: key_dotted_path = key_dotted_path.replace(sep, ".") def handle_repr(value): # lazy values shouldnt be evaluated for inspecting if isinstance(value, Lazy): return value._dynaconf_encode() return value def traverse_data(data, path): # transform `a.b.c` in successive calls to `data['a']['b']['c']` path = path.split(".") root_key, nested_keys = path[0], path[1:] result = data.get(root_key, bypass_eval=True) for key in nested_keys: result = handle_repr(result.get(key, bypass_eval=True)) return result try: return traverse_data(data, key_dotted_path) except KeyError: if not default: raise KeyError(f"Path not found in data: {key_dotted_path!r}") return default def get_debug_info( settings: Settings | LazySettings, verbosity: int = 0, key: str | None = None, ) -> dict: """Returns a dict with debug info about the settings object""" if key: verbosity = 2 def filter_by_key(data: dict) -> dict: """If key is not None, filter dict keeping only the key""" if key and data: try: return {key: _get_data_by_key(data, key)} except KeyError: return {} return data def build_loading_history() -> list[dict]: _data = [] for ( source_metadata, source_data, ) in settings._loaded_by_loaders.items(): _real_data = filter_by_key(source_data) if verbosity == 0: _data.append( { "loader": source_metadata.loader, "identifier": source_metadata.identifier, "data": len(_real_data), } ) elif verbosity == 1: _data.append( { "loader": source_metadata.loader, "identifier": source_metadata.identifier, "data": list(_real_data.keys()), } ) else: _data.append( { "loader": source_metadata.loader, "identifier": source_metadata.identifier, "data": _real_data, } ) return _data def build_loaded_hooks(): _data = [] for hook, hook_data in settings._loaded_hooks.items(): _real_data = filter_by_key(hook_data.get("post", {})) or {} if verbosity == 0: _data.append( { "hook": str(hook), "data": len(_real_data), } ) elif verbosity == 1: _data.append( { "hook": str(hook), "data": list(_real_data.keys()), } ) else: _data.append( { "hook": str(hook), "data": _real_data, } ) return _data data = { "versions": { "dynaconf": version("dynaconf"), }, "root_path": settings._root_path, "validators": [str(v) for v in settings.validators], "core_loaders": settings._loaders, "loaded_files": settings._loaded_files, "history": build_loading_history(), "post_hooks": [str(h) for h in settings._post_hooks], "loaded_hooks": build_loaded_hooks(), } for name in ["django", "flask", "fastapi", "starlette"]: with suppress(PackageNotFoundError): data["versions"][name] = version(name) if settings.get("ENVIRONMENTS_FOR_DYNACONF"): environments = settings.get("ENVIRONMENTS_FOR_DYNACONF") if isinstance(environments, (list, tuple)): data["environments"] = list(environments) else: data["environments"] = environments data["loaded_envs"] = settings._loaded_envs if key: data["current"] = {key: settings.get(key)} return data def print_debug_info( settings: Settings | LazySettings, *, dumper: DumperPreset | DumperType | None = None, verbosity: int = 0, key: str | None = None, ): """Calls dumper with settings debug info""" dumper = dumper or "yaml" if isinstance(dumper, str): dumper = builtin_dumpers.get(dumper) if dumper is None: raise OutputFormatError( f"The desired format is not available: {dumper!r}" ) data = get_debug_info(settings, verbosity, key) dumper(data, sys.stdout) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/utils/parse_conf.py0000644000000000000000000004463215156324234021205 0ustar00pbrochadpbrochadfrom __future__ import annotations import json import os import re import string import warnings from functools import wraps from dynaconf.utils import extract_json_objects 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: import jinja2 from jinja2.sandbox import SandboxedEnvironment jinja_env = SandboxedEnvironment() for p_method in ("abspath", "realpath", "relpath", "dirname", "basename"): jinja_env.filters[p_method] = getattr(os.path, p_method) except ImportError: # pragma: no cover jinja_env = None true_values = ("t", "true", "enabled", "1", "on", "yes", "True") false_values = ("f", "false", "disabled", "0", "off", "no", "False", "") KV_PATTERN = re.compile(r"([a-zA-Z0-9 ]*=[a-zA-Z0-9\- :]*)") """matches `a=b, c=d, e=f` used on `VALUE='@merge foo=bar'` variables.""" class DynaconfFormatError(Exception): """Error to raise when formatting a lazy variable fails""" class DynaconfParseError(Exception): """Error to raise when parsing @casts""" class MetaValue: """A Marker to trigger specific actions on `set` and `object_merge`""" _meta_value = True def __init__(self, value, box_settings): self.box_settings = box_settings self.value = parse_conf_data( value, tomlfy=True, box_settings=box_settings ) def __repr__(self): return f"{self.__class__.__name__}({self.value}) on {id(self)}" def unwrap(self): return self.value class Reset(MetaValue): """Triggers an existing key to be reset to its value NOTE: DEPRECATED on v3.0.0 """ _dynaconf_reset = True def __init__(self, value, box_settings): self.box_settings = box_settings self.value = parse_conf_data( value, tomlfy=True, box_settings=self.box_settings ) warnings.warn(f"{self.value} does not need `@reset` anymore.") class Del(MetaValue): """Triggers an existing key to be deleted""" _dynaconf_del = True def unwrap(self): raise ValueError("Del object has no value") class Merge(MetaValue): """Triggers an existing key to be merged""" _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( value, tomlfy=True, box_settings=box_settings ) if isinstance(self.value, (int, float, bool)): # @merge 1, @merge 1.1, @merge False self.value = [self.value] elif isinstance(self.value, str): # @merge {"valid": "json"} json_object = list( extract_json_objects( multi_replace( self.value, { ": True": ": true", ":True": ": true", ": False": ": false", ":False": ": false", ": None": ": null", ":None": ": null", }, ) ) ) if len(json_object) == 1: self.value = json_object[0] else: matches = KV_PATTERN.findall(self.value) # a=b, c=d if matches: self.value = { k.strip(): parse_conf_data( v, tomlfy=True, box_settings=box_settings ) for k, v in ( match.strip().split("=") for match in matches ) } elif "," in self.value: # @merge foo,bar self.value = [ parse_conf_data( v, tomlfy=True, box_settings=box_settings ) for v in self.value.split(",") ] else: # @merge foo self.value = [self.value] self.unique = unique class Insert(MetaValue): """Triggers the value to be inserted into a list at specific index""" _dynaconf_insert = True def __init__(self, value, box_settings): """ normally value will be a string like `0 foo` or `-1 foo` and needs to get split but value can also be just a single string with or without space like `foo` and in this case it will be treated as `0 foo` but it can also be `foo bar` and in this case it will be treated as `0 foo bar` we need to check if the first part is a number if it is not a number then we will treat it as `0 value` if it is a number then we will split it as `index, value` this must use a regex to match value, examples: -1 foo -> index = -1, value = foo 0 foo -> index = 0, value = foo 0 foo bar -> index = 0, value = foo bar 0 42 -> index = 0, value = 42 0 42 foo -> index = 0, value = 42 foo foo -> index = 0, value = foo foo bar -> index = 0, value = foo bar 42 -> index = 0, value = 42 42 foo -> index = 42, value = foo 42 foo bar -> index = 42, value = foo bar """ self.box_settings = box_settings try: if value.lstrip("-+")[0].isdigit(): # `0 foo` or `-1 foo` or `42 foo` or `42`(raise ValueError) index, value = value.split(" ", 1) else: # `foo` or `foo bar` index, value = 0, value except ValueError: # `42` or any other non split able value index, value = 0, value self.index = int(index) self.value = parse_conf_data( value, tomlfy=True, box_settings=box_settings ) class BaseFormatter: def __init__(self, function, token): self.function = function self.token = token def __call__(self, value, **context): try: return self.function(value, **context) except (KeyError, AttributeError) as exc: # A template like `{this.KEY}` failed with AttributeError # Or KeyError in the case of `{env[KEY]}` raise DynaconfFormatError( f"Dynaconf can't interpolate variable because {exc}" ) from exc def __str__(self): return str(self.token) def _jinja_formatter(value: str, **context) -> str: if jinja_env is None: # pragma: no cover raise ImportError( "jinja2 must be installed to enable '@jinja' settings in dynaconf" ) try: return jinja_env.from_string(value).render(**context) except jinja2.exceptions.SecurityError: warnings.warn(f"Unsafe access attempt to: {value}") return "" def _get_formatter(value, **context): """ Invokes settings.get from the annotation in value. value can be one of the following: @get KEY @get KEY @int @get KEY default_value @get KEY @int default_value @marker KEY_TO_LOOKUP @OPTIONAL_CAST OPTIONAL_DEFAULT_VALUE key group will match the key cast group will match anything provided after @ the default group will match anything between single or double quotes """ tokens = value.strip().split() if not tokens or len(tokens) > 3: raise DynaconfFormatError(f"Error parsing {value} malformed syntax.") params = { "key": tokens[0], "cast": None, "default": None, } for token in tokens[1:]: if token.startswith("@"): params["cast"] = token else: params["default"] = token if not params["default"] and params["key"] not in context["this"]: raise DynaconfParseError( f"Key {params['key']} not found in settings and no default value provided." ) return context["this"].get(**params) class SafeFormatter(string.Formatter): def get_field(self, field_name, args, context): self._validate_key_exists(field_name, context) return super().get_field(field_name, args, context) def _validate_key_exists(self, field_name: str, context): if not field_name.lower().startswith("this"): return from dynaconf.base import _PUBLIC_PROPERTIES field_name = field_name.replace("[", ".") field_name = field_name.replace("]", "") context_name, _, key = field_name.partition(".") # these are accessible by the user, but are not considered setting keys # e.g, settings.current_env if key in _PUBLIC_PROPERTIES: return # allow only existing setting keys if key not in context[context_name]: raise AttributeError(key) def _format_formatter(input: str, **context) -> str: return SafeFormatter().format(input, **context) class Formatters: """Dynaconf builtin formatters""" python_formatter = BaseFormatter(_format_formatter, "format") jinja_formatter = BaseFormatter(_jinja_formatter, "jinja") get_formatter = BaseFormatter(_get_formatter, "get") class Lazy: """Holds data to format lazily.""" _dynaconf_lazy_format = True def __init__( self, value=empty, formatter=Formatters.python_formatter, casting=None ): self.value = value self.casting = casting # Sometimes a simple function is passed to the formatter. # but on evaluation-time, we may need to access `formatter.token` # so we are wrapping the fn to comply with this interface. if isinstance(formatter, BaseFormatter): self.formatter = formatter else: self.formatter = BaseFormatter(formatter, "lambda") @property def context(self): """Builds a context for formatting.""" return {"env": os.environ, "this": self.settings} def __call__(self, settings, validator_object=None): """LazyValue triggers format lazily.""" self.settings = settings 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.""" return str(self.value) def __repr__(self): """Give the quoted str representation""" return f"'@{self.formatter} {self.value}'" def _dynaconf_encode(self): """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`""" try: return value._dynaconf_encode() except (AttributeError, TypeError): return callback(value) def evaluate_lazy_format(f): """Marks a method on Settings instance to lazily evaluate LazyFormat objects upon access.""" @wraps(f) def evaluate(settings, *args, **kwargs): value = f(settings, *args, **kwargs) return recursively_evaluate_lazy_format(value, settings) return evaluate converters = { "@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 "@reset": Reset, # @reset is DEPRECATED on v3.0.0 "@del": Del, "@merge": Merge, "@merge_unique": lambda value, box_settings: Merge( value, box_settings, unique=True ), "@insert": Insert, "@get": lambda value: Lazy(value, formatter=Formatters.get_formatter), # Special markers to be used as placeholders e.g: in prefilled forms # will always return None when evaluated "@note": lambda value: None, "@comment": lambda value: None, "@null": lambda value: None, "@none": lambda value: None, "@empty": lambda value: empty, } def apply_converter(converter_key, value, box_settings): """ Get converter and apply it to @value. Lazy converters will return Lazy objects for later evaluation. """ converter = converters[converter_key] try: converted_value = converter(value, box_settings=box_settings) except TypeError: converted_value = converter(value) return converted_value def add_converter(converter_key, func): """Adds a new converter to the converters dict""" if not converter_key.startswith("@"): converter_key = f"@{converter_key}" converters[converter_key] = wraps(func)( lambda value: value.set_casting(func) if isinstance(value, Lazy) else Lazy( value, casting=func, formatter=BaseFormatter(lambda x, **_: x, converter_key), ) ) def parse_with_toml(data): """Uses TOML syntax to parse 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): """ @int @bool @float @json (for lists and dicts) strings does not need converters export DYNACONF_DEFAULT_THEME='material' export DYNACONF_DEBUG='@bool True' export DYNACONF_DEBUG_TOOLBAR_ENABLED='@bool False' export DYNACONF_PAGINATION_PER_PAGE='@int 20' export DYNACONF_MONGODB_SETTINGS='@json {"DB": "quokka_db"}' export DYNACONF_ALLOWED_EXTENSIONS='@json ["jpg", "png"]' """ # not enforced to not break backwards compatibility with custom loaders box_settings = box_settings or {} 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 and data and isinstance(data, str) and data.startswith(tuple(converters.keys())) ): # 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 = apply_converter(converter_key, value, box_settings) else: value = parse_with_toml(data) if tomlfy else data if isinstance(value, dict): value = DynaBox(value, box_settings=box_settings) return value def parse_conf_data(data, tomlfy=False, box_settings=None): """ Apply parsing tokens recursively and return transformed data. Strings with lazy parser (e.g, @format) will become Lazy objects. """ # fix for https://github.com/dynaconf/dynaconf/issues/595 if isnamedtupleinstance(data): return data # not enforced to not break backwards compatibility with custom loaders 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) for item in data ] if isinstance(data, DynaBox): # recursively parse inner dict items # It is important to keep the same object id because # of mutability for k, v in data.items(bypass_eval=True): data[k] = parse_conf_data( v, tomlfy=tomlfy, box_settings=box_settings ) return data if isinstance(data, dict): # recursively parse inner dict items for k, v in data.items(): data[k] = parse_conf_data( v, tomlfy=tomlfy, box_settings=box_settings ) return data # return parsed string value return _parse_conf_data(data, tomlfy=tomlfy, box_settings=box_settings) def unparse_conf_data(value): if isinstance(value, bool): return f"@bool {value}" if isinstance(value, int): return f"@int {value}" if isinstance(value, float): return f"@float {value}" if isinstance(value, (list, dict)): return f"@json {json.dumps(value)}" if isinstance(value, Lazy): return try_to_encode(value) if value is None: return "@none " return value def boolean_fix(value: str | None): """Gets a value like `True/False` and turns to `true/false` This function exists because of issue #976 Toml parser casts booleans from true/false lower case however envvars are usually exported as True/False capitalized by mistake, this helper fixes it for envvars only. Assume envvars are always str. """ if value and value.strip() in ("True", "False"): return value.lower() return value ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/validator.py0000644000000000000000000004410015156324234017701 0ustar00pbrochadpbrochadfrom __future__ import annotations from collections import defaultdict from itertools import chain from types import MappingProxyType from typing import Any from typing import Callable from typing import Sequence from typing import TYPE_CHECKING from dynaconf import validator_conditions from dynaconf.utils import ensure_a_list from dynaconf.utils.functional import empty from dynaconf.utils.parse_conf import Lazy if TYPE_CHECKING: from dynaconf.base import LazySettings # noqa: F401 from dynaconf.base import Settings EQUALITY_ATTRS = ( "names", "must_exist", "when", "condition", "operations", "envs", ) class ValidationError(Exception): """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: """Validators are conditions attached to settings variables names or patterns:: Validator('MESSAGE', must_exist=True, eq='Hello World') The above ensure MESSAGE is available in default env and is equal to 'Hello World' `names` are a one (or more) names or patterns:: Validator('NAME') Validator('NAME', 'OTHER_NAME', 'EVEN_OTHER') Validator(r'^NAME', r'OTHER./*') The `operations` are:: eq: value == other ne: value != other gt: value > other lt: value < other gte: value >= other lte: value <= other is_type_of: isinstance(value, type) is_in: value in sequence is_not_in: value not in sequence identity: value is other cont: contain value in len_eq: len(value) == other len_ne: len(value) != other len_min: len(value) > other len_max: len(value) < other `env` is which env to be checked, can be a list or default is used. `when` holds a validator and its return decides if validator runs or not:: Validator('NAME', must_exist=True, when=Validator('OTHER', eq=2)) # NAME is required only if OTHER eq to 2 # When the very first thing to be performed when passed. # if no env is passed to `when` it is inherited `must_exist` is alias to `required` requirement. (executed after when):: settings.get(value, empty) returns non empty condition is a callable to be executed and return boolean:: Validator('NAME', condition=lambda x: x == 1) # it is executed before operations. """ default_messages = MappingProxyType( { "must_exist_true": "{name} is required in env {env}", "must_exist_false": "{name} cannot exists in env {env}", "condition": "{name} invalid for {function}({value}) in env {env}", "operations": ( "{name} must {operation} {op_value} " "but it is {value} in env {env}" ), "combined": "combined validators failed {errors}", } ) def __init__( self, *names: str, 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 self.messages = dict(self.default_messages) if messages: self.messages.update(messages) if when is not None and not isinstance(when, Validator): raise TypeError("when must be Validator instance") if condition is not None and not callable(condition): raise TypeError("condition must be callable") # in the case that: # * default is a Lazy object AND # * there isnt any validate operation to perform (that would require knowing the lazy value) # Then we shouldnt trigger the Lazy evaluation self.should_call_lazy = not all( ( default, isinstance(default, Lazy), not must_exist, not required, not cast, not operations, ) ) self.names = names self.must_exist = must_exist if must_exist is not None else required self.condition = condition self.when = when self.cast = cast or (lambda value: value) self.operations = operations self.default = default self.description = description 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] elif isinstance(env, (list, tuple)): self.envs = env def __or__(self, other: Validator) -> CombinedValidator: return OrValidator(self, other, description=self.description) def __and__(self, other: Validator) -> CombinedValidator: return AndValidator(self, other, description=self.description) def __eq__(self, other: object) -> bool: if self is other: return True if type(self).__name__ != type(other).__name__: return False identical_attrs = ( getattr(self, attr) == getattr(other, attr) for attr in EQUALITY_ATTRS ) if all(identical_attrs): return True return False def validate( self, settings: Settings, 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 only = ensure_a_list(only or [""]) if only and not isinstance(only[0], str): raise ValueError("'only' must be a string or list of strings.") exclude = ensure_a_list(exclude) if exclude and not isinstance(exclude[0], str): raise ValueError("'exclude' must be a string or list of strings.") if self.envs is None: self.envs = [settings.current_env] if self.when is not None: try: # inherit env if not defined if self.when.envs is None: self.when.envs = self.envs self.when.validate(settings, only=only, exclude=exclude) except ValidationError: # 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 and self.envs[0].upper() == settings.current_env.upper() ): self._validate_items( settings, settings.current_env, only=only, exclude=exclude ) return for env in self.envs: env_settings: Settings = settings.from_env(env) self._validate_items(env_settings, only=only, exclude=exclude) # merge source metadata into original settings for history inspect settings._loaded_by_loaders.update(env_settings._loaded_by_loaders) def _validate_items( self, settings: Settings, 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: # Skip if only is set and name isn't in the only list if only and not any(name.startswith(sub) for sub in only): continue # Skip if exclude is set and name is in the exclude list if exclude and any(name.startswith(sub) for sub in exclude): continue if self.default is not empty: if callable(self.default) and self.should_call_lazy: default_value = self.default(settings, self) else: default_value = self.default else: default_value = empty # 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, env=env, ) # is name required but not exists? if self.must_exist is True and value is empty: _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: _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 # value or default value already set # by settings.setdefault above # however we need to cast it # so we call .set again if self.should_call_lazy: value = self.cast(settings.get(name)) settings.set( name, value, validate=False, loader_identifier="validator" ) # is there a callable condition? if self.condition is not None: if not self.condition(value): _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) op_succeeded = False # 'is_type_of' special error handling - related to #879 if op_name == "is_type_of": # auto transform quoted types if isinstance(op_value, str): op_value = __builtins__.get( # type: ignore op_value, op_value ) # invalid type (not in __builtins__) may raise TypeError try: op_succeeded = op_function(value, op_value) except TypeError: raise ValidationError( f"Invalid type '{op_value}' for condition " "'is_type_of'. Should provide a valid type" ) else: op_succeeded = op_function(value, op_value) if not op_succeeded: _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): def __init__( self, validator_a: Validator, validator_b: Validator, *args: Any, **kwargs: Any, ) -> None: """Takes 2 validators and combines the validation""" self.validators = (validator_a, validator_b) super().__init__(*args, **kwargs) for attr in EQUALITY_ATTRS: if not getattr(self, attr, None): value = tuple( getattr(validator, attr) for validator in self.validators ) setattr(self, attr, value) def validate( self, settings: Any, 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" ) class OrValidator(CombinedValidator): """Evaluates on Validator() | Validator()""" def validate( self, settings: Any, 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, only_current_env=only_current_env, ) except ValidationError as e: errors.append(e) continue else: return _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): """Evaluates on Validator() & Validator()""" def validate( self, settings: Any, 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, only_current_env=only_current_env, ) except ValidationError as e: errors.append(e) continue if 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: Settings, validators: Sequence[Validator] | None = None, *args: Validator, **kwargs: Any, ) -> None: if isinstance(validators, (list, tuple)): args = list(args) + list(validators) # type: ignore self._only = kwargs.pop("validate_only", None) self._exclude = kwargs.pop("validate_exclude", None) super().__init__(args, **kwargs) # type: ignore self.settings = settings def register(self, *args: Validator, **kwargs: Validator): validators: list[Validator] = list( chain.from_iterable(kwargs.values()) # type: ignore ) validators.extend(args) for validator in validators: if validator and validator not in self: self.append(validator) def descriptions(self, flat: bool = False) -> dict[str, str | list[str]]: if flat: descriptions: dict[str, str | list[str]] = {} else: descriptions = defaultdict(list) for validator in self: for name in validator.names: if isinstance(name, tuple) and len(name) > 0: name = name[0] if flat: descriptions.setdefault(name, validator.description) else: descriptions[name].append( # type: ignore validator.description ) return descriptions def validate( self, 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: 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 ) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/dynaconf/validator_conditions.py0000644000000000000000000000276715156324234022147 0ustar00pbrochadpbrochad# pragma: no cover """ Implement basic assertions to be used in assertion action """ from __future__ import annotations def eq(value, other): """Equal""" return value == other def ne(value, other): """Not equal""" return value != other def gt(value, other): """Greater than""" return value > other def lt(value, other): """Lower than""" return value < other def gte(value, other): """Greater than or equal""" return value >= other def lte(value, other): """Lower than or equal""" return value <= other def identity(value, other): """Identity check using ID""" return value is other def is_type_of(value, other): """Type check""" return isinstance(value, other) def is_in(value, other): """Existence""" return value in other def is_not_in(value, other): """Inexistence""" return value not in other def cont(value, other): """Contains""" return other in value def len_eq(value, other): """Length Equal""" return len(value) == other def len_ne(value, other): """Length Not equal""" return len(value) != other def len_min(value, other): """Minimum length""" return len(value) >= other def len_max(value, other): """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) ././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9146926 dynaconf-3.2.13/dynaconf/vendor/0000755000000000000000000000000015156326623016644 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/__init__.py0000644000000000000000000000000015156326615020744 0ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9152076 dynaconf-3.2.13/dynaconf/vendor/box/0000755000000000000000000000000015156326623017434 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/box/__init__.py0000644000000000000000000000040615156326615021546 0ustar00pbrochadpbrochad#!/usr/bin/env python __author__='Chris Griffith' __version__='4.2.3' from.box import Box from.box_list import BoxList from.config_box import ConfigBox from.shorthand_box import SBox from.exceptions import BoxError,BoxKeyError from.from_file import box_from_file././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/box/box.py0000644000000000000000000003332515156326615020605 0ustar00pbrochadpbrochad#!/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='camel_killer_box' _F='conversion_box' _E=True _D='frozen_box' _C='__safe_keys' _B=False _A=None import copy,re,string,warnings from collections.abc import Iterable,Mapping,Callable from keyword import kwlist from pathlib import Path from typing import Any,Union,Tuple,List,Dict from dynaconf.vendor import box from.converters import _to_json,_from_json,_from_toml,_to_toml,_from_yaml,_to_yaml,BOX_PARAMETERS from.exceptions import BoxError,BoxKeyError,BoxTypeError,BoxValueError,BoxWarning __all__=['Box'] _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):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: if isinstance(A,dict):B.append(C(A,**E)) elif isinstance(A,list)or D and isinstance(A,tuple):B.append(_recursive_tuples(A,C,D,**E)) else:B.append(A) return tuple(B) def _parse_box_dots(item): A=item for(B,C)in enumerate(A): if C=='[':return A[:B],A[B:] elif C=='.':return A[:B],A[B+1:] raise BoxError('Could not split box dots properly') 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('_')] def __new__(A,*D,box_settings=_A,default_box=_B,default_box_attr=NO_DEFAULT,default_box_none_transform=_E,frozen_box=_B,camel_killer_box=_B,conversion_box=_E,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,_F:conversion_box,_S:box_safe_prefix,_D:frozen_box,_G: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=_E,frozen_box=_B,camel_killer_box=_B,conversion_box=_E,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,_F:conversion_box,_S:box_safe_prefix,_D:frozen_box,_G: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[_F]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[_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) else:raise BoxValueError('First argument must be mapping or iterable') elif B:raise BoxTypeError(f"Box expected at most 1 argument, got {len(B)}") 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[_Q]=_E 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.") B.merge_update(A);return B def __hash__(A): if A._box_config[_D]: B=54321 for C in A.items():B^=hash(C) return B raise BoxTypeError('unhashable type: "Box"') def __dir__(B): 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: for E in A: if E not in D:break else:C.add(A) for A in B.keys(): if A not in C: if B._box_config[_F]: A=B._safe_attr(A) if A:C.add(A) return list(C) def get(B,key,default=NO_DEFAULT): C=key;A=default if C not in B: if A is NO_DEFAULT: if B._box_config[_J]and B._box_config[_R]:return B.__get_default(C) else:return 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] def copy(A):return Box(super().copy(),**A.__box_config()) def __copy__(A):return Box(super().copy(),**A.__box_config()) def __deepcopy__(A,memodict=_A): B=memodict;E=A._box_config[_D];D=A.__box_config();D[_D]=_B;C=A.__class__(**D);B=B or{};B[id(A)]=C for(F,G)in A.items():C[copy.deepcopy(F,B)]=copy.deepcopy(G,B) C._box_config[_D]=E;return C def __setstate__(A,state):B=state;A._box_config=B[_H];A.__dict__.update(B) def keys(A):return super().keys() def values(A):return[A[B]for B in A.keys()] def items(A,bypass_eval=_B): if not bypass_eval:return[(B,A[B])for B in A.keys()] return[(B,A.get(B,bypass_eval=_E))for B in A.keys()] def __get_default(B,item): 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()) elif isinstance(A,Callable):C=A() elif hasattr(A,'copy'):C=A.copy() else:C=A B.__convert_and_store(item,C);return C def __box_config(C): A={} for(B,D)in C._box_config.copy().items(): if not B.startswith('__'):A[B]=D return A def __recast(A,item,value): C=value;B=item 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[_F]: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[_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[_T],**B.__box_config()) else:A=box.BoxList(A,box_class=B.__class__,**B.__box_config()) elif B._box_config[_T]and isinstance(A,tuple):A=_recursive_tuples(A,B.__class__,recreate_tuples=_E,**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[_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[_G]and isinstance(A,str): D=_camel_killer(A) if D in B.keys():return super().__getitem__(D) 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 try: try:C=A.__getitem__(B,_ignore_default=_E) except KeyError:C=object.__getattribute__(A,B) except AttributeError as E: if B=='__getstate__':raise BoxKeyError(B)from _A if B==_H:raise BoxError('_box_config key must exist')from _A if A._box_config[_F]: D=A._safe_attr(B) if D in A._box_config[_C]:return A.__getitem__(A._box_config[_C][D]) 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[_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[_G]: if A._box_config[_G]and isinstance(B,str):B=_camel_killer(B) if A._box_config[_F]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[_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) if D in A._box_config[_C]:B=A._box_config[_C][D] A.__setitem__(B,C) def __delitem__(A,key): B=key 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[_G]: if A._box_config[_G]and isinstance(B,str): for D in A: if _camel_killer(B)==D:B=D;break super().__delitem__(B) def __delattr__(A,item): B=item 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) except KeyError as D: if A._box_config[_F]: C=A._safe_attr(B) if C in A._box_config[_C]:A.__delitem__(A._box_config[_C][C]);del A._box_config[_C][C];return raise BoxKeyError(D) def pop(B,key,*C): A=key if C: if len(C)!=1:raise BoxError('pop() takes only one optional argument "default"') try:D=B[A] except KeyError:return C[0] else:del B[A];return D try:D=B[A] except KeyError:raise BoxKeyError('{0}'.format(A))from _A else:del B[A];return D def clear(A):super().clear();A._box_config[_C].clear() def popitem(A): try:B=next(A.__iter__()) except StopIteration:raise BoxKeyError('Empty box')from _A return B,A.pop(B) def __repr__(A):return f"" def __str__(A):return str(A.to_dict()) def __iter__(A): for B in A.keys():yield B def __reversed__(A): for B in reversed(list(A.keys())):yield B def to_dict(D): A=dict(D) for(C,B)in A.items(): if B is D:A[C]=A elif isinstance(B,Box):A[C]=B.to_dict() elif isinstance(B,box.BoxList):A[C]=B.to_list() return A def update(C,__m=_A,**D): B=__m if B: 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) for A in D:C.__convert_and_store(A,D[A]) def merge_update(A,__m=_A,**E): C=__m def D(k,v): 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): if isinstance(A[k],Box):A[k].merge_update(v) else:A[k].update(v) return if isinstance(v,list)and not B:v=box.BoxList(v,**A.__box_config()) A.__setitem__(k,v) if C: if hasattr(C,'keys'): for B in C:D(B,C[B]) else: for(B,F)in C:D(B,F) for B in E:D(B,E[B]) def setdefault(B,item,default=_A): C=item;A=default if C in B:return B[C] if isinstance(A,dict):A=B.__class__(A,**B.__box_config()) 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+'_' 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()[_G]: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('_') A=''.join(A)[:D+1] try:int(A[0]) except(ValueError,IndexError):pass 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[_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=_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=_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=_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=_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=_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=_M,**B): C={} for A in B.copy(): if A in BOX_PARAMETERS:C[A]=B.pop(A) E=_from_toml(toml_string=toml_string,filename=filename,encoding=encoding,errors=errors);return D(E,**C)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/box/box_list.py0000644000000000000000000001261615156326615021640 0ustar00pbrochadpbrochad#!/usr/bin/env python _G='box_dots' _F='BoxList is frozen' _E='frozen_box' _D=False _C='strict' _B='utf-8' _A=None import copy,re from typing import Iterable,Optional from dynaconf.vendor import box from.converters import _to_yaml,_from_yaml,_to_json,_from_json,_to_toml,_from_toml,_to_csv,_from_csv,BOX_PARAMETERS from.exceptions import BoxError,BoxTypeError,BoxKeyError _list_pos_re=re.compile('\\[(\\d+)\\]') DYNABOX_CLASS=_A def get_dynabox_class_avoiding_circular_import(): global DYNABOX_CLASS if DYNABOX_CLASS is _A:from dynaconf.utils.boxing import DynaBox as A;DYNABOX_CLASS=A return DYNABOX_CLASS class BoxList(list): def __init__(A,iterable=_A,box_class=_A,**C): B=iterable;A.box_class=box_class or get_dynabox_class_avoiding_circular_import();A.box_options=C;A.box_org_ref=A.box_org_ref=id(B)if B else 0 if B: for D in B:A.append(D) if C.get(_E): def E(*A,**B):raise BoxError(_F) for F in['append','extend','insert','pop','remove','reverse','sort']:A.__setattr__(F,E) def __getitem__(B,item): A=item if B.box_options.get(_G)and isinstance(A,str)and A.startswith('['): C=_list_pos_re.search(A);D=super(BoxList,B).__getitem__(int(C.groups()[0])) if len(C.group())==len(A):return D return D.__getitem__(A[len(C.group()):].lstrip('.')) return super(BoxList,B).__getitem__(A) def __delitem__(A,key): if A.box_options.get(_E):raise BoxError(_F) super(BoxList,A).__delitem__(key) def __setitem__(B,key,value): C=value;A=key if B.box_options.get(_E):raise BoxError(_F) if B.box_options.get(_G)and isinstance(A,str)and A.startswith('['): D=_list_pos_re.search(A);E=int(D.groups()[0]) if len(D.group())==len(A):return super(BoxList,B).__setitem__(E,C) 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): B='box_intact_types' try: 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 if isinstance(B,dict)and not A._is_intact_type(B): try:B=A.box_class(B,**A.box_options) except AttributeError as C: if'box_class'in A.__dict__:raise BoxKeyError(C) elif isinstance(B,list)and not A._is_intact_type(B): try:B=A if id(B)==A.box_org_ref else BoxList(B,**A.box_options) except AttributeError as C: if'box_org_ref'in A.__dict__:raise BoxKeyError(C) super(BoxList,A).append(B) def extend(A,iterable): for B in iterable:A.append(B) def insert(B,index,p_object): A=p_object if isinstance(A,dict)and not B._is_intact_type(A):A=B.box_class(A,**B.box_options) elif isinstance(A,list)and not B._is_intact_type(A):A=B if id(A)==B.box_org_ref else BoxList(A) super(BoxList,B).insert(index,A) def __repr__(A):return f"" def __str__(A):return str(A.to_list()) def __copy__(A):return BoxList((A for A in A),A.box_class,**A.box_options) def __deepcopy__(B,memo=_A): A=memo;C=B.__class__();A=A or{};A[id(B)]=C for D in B:C.append(copy.deepcopy(D,memo=A)) return C def __hash__(A): if A.box_options.get(_E):B=98765;B^=hash(tuple(A));return B raise BoxTypeError("unhashable type: 'BoxList'") def to_list(C): A=[] for B in C: if B is C:A.append(A) elif isinstance(B,box.Box):A.append(B.to_dict()) elif isinstance(B,BoxList):A.append(B.to_list()) else:A.append(B) return A def to_json(D,filename=_A,encoding=_B,errors=_C,multiline=_D,**E): C=errors;B=encoding;A=filename if A and multiline: F=[_to_json(A,filename=_D,encoding=B,errors=C,**E)for A in D] with open(A,'w',encoding=B,errors=C)as G:G.write('\n'.join(F)) else:return _to_json(D.to_list(),filename=A,encoding=B,errors=C,**E) @classmethod def from_json(E,json_string=_A,filename=_A,encoding=_B,errors=_C,multiline=_D,**A): D={} for B in list(A.keys()): if B in BOX_PARAMETERS:D[B]=A.pop(B) C=_from_json(json_string,filename=filename,encoding=encoding,errors=errors,multiline=multiline,**A) if not isinstance(C,list):raise BoxError(f"json data not returned as a list, but rather a {type(C).__name__}") return E(C,**D) def to_yaml(A,filename=_A,default_flow_style=_D,encoding=_B,errors=_C,**B):return _to_yaml(A.to_list(),filename=filename,default_flow_style=default_flow_style,encoding=encoding,errors=errors,**B) @classmethod def from_yaml(E,yaml_string=_A,filename=_A,encoding=_B,errors=_C,**A): D={} for B in list(A.keys()): 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,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='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='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) E=_from_toml(toml_string=toml_string,filename=filename,encoding=encoding,errors=errors) if A not in E:raise BoxError(f"{A} was not found.") return F(E[A],**D) def to_csv(A,filename,encoding=_B,errors=_C):_to_csv(A,filename=filename,encoding=encoding,errors=errors) @classmethod def from_csv(A,filename,encoding=_B,errors=_C):return A(_from_csv(filename=filename,encoding=encoding,errors=errors))././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/box/config_box.py0000644000000000000000000000321615156326615022126 0ustar00pbrochadpbrochad#!/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)+['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__()+['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 E if isinstance(A,(bool,int)):return bool(A) 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) except AttributeError as D: if B is not _A:return B raise D return int(A) def float(C,item,default=_A): B=default;A=item try:A=C.__getattr__(A) except AttributeError as D: if B is not _A:return B raise D return float(A) def list(E,item,default=_A,spliter=',',strip=True,mod=_A): C=strip;B=default;A=item try:A=E.__getattr__(A) except AttributeError as F: if B is not _A:return B raise F if C:A=A.lstrip('[').rstrip(']') D=[A.strip()if C else A for A in A.split(spliter)] if mod:return list(map(mod,D)) return D def getboolean(A,item,default=_A):return A.bool(item,default) def getint(A,item,default=_A):return A.int(item,default) def getfloat(A,item,default=_A):return A.float(item,default) def __repr__(A):return''.format(str(A.to_dict())) def copy(A):return ConfigBox(super().copy()) def __copy__(A):return ConfigBox(super().copy())././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/box/converters.py0000644000000000000000000000617015156326615022205 0ustar00pbrochadpbrochad#!/usr/bin/env python _E=False _D=True _C='strict' _B='utf-8' _A=None 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 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) if create: try:B.touch(exist_ok=_D) except OSError as C:raise BoxError(f"Could not create file {A} - {C}") else:return if not B.exists():raise BoxError(f'File "{A}" does not exist') if not B.is_file():raise BoxError(f"{A} is not a file") 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,'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,'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) else:raise BoxError('from_json requires a string or filename') return C 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,'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): E='Loader';C=yaml_string;B=filename if E not in A:A[E]=yaml.SafeLoader if B: _exists(B) 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 def _to_toml(obj,filename=_A,encoding=_B,errors=_C): A=filename if A: _exists(A,create=_D) 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,'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 def _to_csv(box_list,filename,encoding=_B,errors=_C): B=filename;A=box_list;C=list(A[0].keys()) for E in A: 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,'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,'r',encoding=encoding,errors=errors,newline='')as B:C=csv.DictReader(B);return[A for A in C]././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/box/exceptions.py0000644000000000000000000000033315156326615022167 0ustar00pbrochadpbrochad#!/usr/bin/env python class BoxError(Exception):0 class BoxKeyError(BoxError,KeyError,AttributeError):0 class BoxTypeError(BoxError,TypeError):0 class BoxValueError(BoxError,ValueError):0 class BoxWarning(UserWarning):0././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/box/from_file.py0000644000000000000000000000271015156326615021751 0ustar00pbrochadpbrochad#!/usr/bin/env python from json import JSONDecodeError from pathlib import Path from typing import Union from dynaconf.vendor.tomllib import TOMLDecodeError from dynaconf.vendor.ruamel.yaml import YAMLError from.exceptions import BoxError from.box import Box from.box_list import BoxList __all__=['box_from_file'] def _to_json(data): try:return Box.from_json(data) except JSONDecodeError:raise BoxError('File is not JSON as expected') except BoxError:return BoxList.from_json(data) def _to_yaml(data): try:return Box.from_yaml(data) except YAMLError:raise BoxError('File is not YAML as expected') 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') 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) if not A.exists():raise BoxError(f'file "{A}" does not exist') B=A.read_text(encoding=encoding,errors=errors) if C: if C.lower()=='json':return _to_json(B) if C.lower()=='yaml':return _to_yaml(B) if C.lower()=='toml':return _to_toml(B) raise BoxError(f'"{C}" is an unknown type, please use either toml, yaml or json') if A.suffix in('.json','.jsn'):return _to_json(B) if A.suffix in('.yaml','.yml'):return _to_yaml(B) if A.suffix in('.tml','.toml'):return _to_toml(B) raise BoxError(f"Could not determine file type based off extension, please provide file_type")././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/box/shorthand_box.py0000644000000000000000000000107515156326615022654 0ustar00pbrochadpbrochad#!/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'] @property def dict(self):return self.to_dict() @property def json(self):return self.to_json() @property def yaml(self):return self.to_yaml() @property def toml(self):return self.to_toml() def __repr__(A):return''.format(str(A.to_dict())) def copy(A):return SBox(super(SBox,A).copy()) def __copy__(A):return SBox(super(SBox,A).copy())././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9163451 dynaconf-3.2.13/dynaconf/vendor/click/0000755000000000000000000000000015156326623017731 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/__init__.py0000644000000000000000000000357015156326616022051 0ustar00pbrochadpbrochadfrom.core import Argument,BaseCommand,Command,CommandCollection,Context,Group,MultiCommand,Option,Parameter from.decorators import argument from.decorators import command from.decorators import confirmation_option from.decorators import group from.decorators import help_option from.decorators import make_pass_decorator from.decorators import option from.decorators import pass_context from.decorators import pass_obj from.decorators import password_option from.decorators import version_option from.exceptions import Abort from.exceptions import BadArgumentUsage from.exceptions import BadOptionUsage from.exceptions import BadParameter from.exceptions import ClickException from.exceptions import FileError from.exceptions import MissingParameter from.exceptions import NoSuchOption from.exceptions import UsageError from.formatting import HelpFormatter from.formatting import wrap_text from.globals import get_current_context from.parser import OptionParser from.termui import clear from.termui import confirm from.termui import echo_via_pager from.termui import edit from.termui import get_terminal_size from.termui import getchar from.termui import launch from.termui import pause from.termui import progressbar from.termui import prompt from.termui import secho from.termui import style from.termui import unstyle from.types import BOOL from.types import Choice from.types import DateTime from.types import File from.types import FLOAT from.types import FloatRange from.types import INT from.types import IntRange from.types import ParamType from.types import Path from.types import STRING from.types import Tuple from.types import UNPROCESSED from.types import UUID from.utils import echo from.utils import format_filename from.utils import get_app_dir from.utils import get_binary_stream from.utils import get_os_args from.utils import get_text_stream from.utils import open_file __version__='8.0.0.dev'././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/dynaconf/vendor/click/_bashcomplete.py0000644000000000000000000001437615156326615023124 0ustar00pbrochadpbrochad_G='COMP_CWORD' _F='COMP_WORDS' _E='zsh' _D='bash' _C=False _B=None _A=True import copy,os,re from collections import abc from.core import Argument from.core import MultiCommand from.core import Option from.parser import split_arg_string from.types import Choice from.utils import echo 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={_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('-','_'));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: if isinstance(A.command,MultiCommand): if not A.command.chain: E,C,B=A.command.resolve_command(A,B) if C is _B:return A A=C.make_context(E,B,parent=A,resilient_parsing=_A);B=A.protected_args+A.args else: while B: E,C,B=A.command.resolve_command(A,B) if C is _B:return A D=C.make_context(E,B,parent=A,allow_extra_args=_A,allow_interspersed_args=_C,resilient_parsing=_A);B=D.args A=D;B=D.protected_args+D.args else:break return A def start_of_option(param_str):A=param_str;return A and A[:1]=='-' def is_incomplete_option(all_args,cmd_param): A=cmd_param if not isinstance(A,Option):return _C if A.is_flag:return _C B=_B for(D,C)in enumerate(reversed([A for A in all_args if A!=WORDBREAK])): if D+1>A.nargs:break if start_of_option(C):B=C return _A if B and B in A.opts else _C def is_incomplete_argument(current_params,cmd_param): A=cmd_param if not isinstance(A,Argument):return _C B=current_params[A.name] if B is _B:return _A if A.nargs==-1:return _A if isinstance(B,abc.Iterable)and A.nargs>1 and len(B)0:return f"{A}d {E:02}:{D:02}:{C:02}" else:return f"{E:02}:{D:02}:{C:02}" return'' def format_pos(A): B=str(A.pos) if A.length_known:B+=f"/{A.length}" return B def format_pct(A):return f"{int(A.pct*100): 4}%"[1:] def format_bar(A): if A.length_known:C=int(A.pct*A.width);B=A.fill_char*C;B+=A.empty_char*(A.width-C) elif A.finished:B=A.fill_char*A.width else: B=list(A.empty_char*(A.width or 1)) if A.time_per_iteration!=0:B[int((math.cos(A.pos*A.time_per_iteration)/2.+.5)*A.width)]=A.fill_char B=''.join(B) return B def format_progress_line(A): C=A.show_percent;B=[] if A.length_known and C is _A:C=not A.show_pos if A.show_pos:B.append(A.format_pos()) if C:B.append(A.format_pct()) if A.show_eta and A.eta_known and not A.finished:B.append(A.format_eta()) if A.item_show_func is not _A: D=A.item_show_func(A.current_item) if D is not _A:B.append(D) return(A.bar_template%{'label':A.label,'bar':A.format_bar(),'info':A.info_sep.join(B)}).rstrip() def render_progress(A): from.termui import get_terminal_size as G if A.is_hidden:return 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=A.length:A.finished=_B if time.time()-A.last_eta<1.:return A.last_eta=time.time() if A.pos:B=(time.time()-A.start)/A.pos else:B=time.time()-A.start A.avg=A.avg[-6:]+[B];A.eta_known=A.length_known def update(A,n_steps,current_item=_A): B=current_item;A.make_step(n_steps) if B is not _A:A.current_item=B 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(_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): 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: if WIN:return _tempfilepager(A,D,B) 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,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,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): 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);I=get_best_encoding(B.stdin) try: for D in generator: if not A:D=strip_ansi(D) B.stdin.write(D.encode(I,_F)) except(OSError,KeyboardInterrupt):pass else:B.stdin.close() while _B: try:B.wait() except KeyboardInterrupt:pass else:break def _tempfilepager(generator,cmd,color): import tempfile as C;A=C.mktemp();B=''.join(generator) if not color:B=strip_ansi(B) D=get_best_encoding(sys.stdout) with open_stream(A,'wb')[0]as E:E.write(B.encode(D)) try:os.system(f'{cmd} "{A}"') finally:os.unlink(A) def _nullpager(stream,generator,color): for A in generator: if not color:A=strip_ansi(A) stream.write(A) class Editor: def __init__(A,editor=_A,env=_A,require_save=_B,extension='.txt'):A.editor=editor;A.env=env;A.require_save=require_save;A.extension=extension def get_editor(A): if A.editor is not _A:return A.editor for D in('VISUAL','EDITOR'): B=os.environ.get(D) if B:return B if WIN:return'notepad' for C in('sensible-editor','vim','nano'): if os.system(f"which {C} >/dev/null 2>&1")==0:return C return'vi' def edit_file(A,filename): import subprocess as D;B=A.get_editor() if A.env:C=os.environ.copy();C.update(A.env) else:C=_A try: E=D.Popen(f'{B} "{filename}"',env=C,shell=_B);F=E.wait() if F!=0:raise ClickException(f"{B}: Editing failed!") except OSError as G:raise ClickException(f"{B}: Editing failed: {G}") def edit(D,text): 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 K,B=J.mkstemp(prefix='editor-',suffix=D.extension) try: if not E: if WIN:F=H;A=A.replace(_D,I) else:F='utf-8' A=A.encode(F) 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 C=open(B,'rb') try:G=C.read() finally:C.close() if E:return G 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 def E(url): A=url;import urllib as B if A.startswith('file://'):A=B.unquote(A[7:]) return A if sys.platform=='darwin': B=['open'] if C:B.append('-W') if D:B.append('-R') B.append(E(A));H=open('/dev/null','w') try:return G.Popen(B,stderr=H).wait() finally:H.close() elif WIN: if D:A=E(A.replace(F,''));B=f'explorer /select,"{A}"' else:A=A.replace(F,'');C='/WAIT'if C else'';B=f'start {C} "" "{A}"' return os.system(B) elif CYGWIN: if D:A=os.path.dirname(E(A).replace(F,''));B=f'cygstart "{A}"' else:A=A.replace(F,'');C='-w'if C else'';B=f'cygstart {C} "{A}"' return os.system(B) try: if D:A=os.path.dirname(E(A))or'.' else:A=E(A) I=G.Popen(['xdg-open',A]) if C:return I.wait() return 0 except OSError: if A.startswith(('http://','https://'))and not D and not C:import webbrowser as J;J.open(A);return 0 return 1 def _translate_ch_to_exc(ch): if ch=='\x03':raise KeyboardInterrupt if ch=='\x04'and not WIN:raise EOFError if ch=='\x1a'and WIN:raise EOFError if WIN: import msvcrt @contextlib.contextmanager def raw_terminal():yield def getchar(echo): if echo:B=msvcrt.getwche else:B=msvcrt.getwch A=B() if A in('\x00','à'):A+=B() _translate_ch_to_exc(A);return A else: import tty,termios @contextlib.contextmanager def raw_terminal(): if not isatty(sys.stdin):B=open('/dev/tty');A=B.fileno() else:A=sys.stdin.fileno();B=_A try: C=termios.tcgetattr(A) try:tty.setraw(A);yield A finally: termios.tcsetattr(A,termios.TCSADRAIN,C);sys.stdout.flush() if B is not _A:B.close() 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),_F) if echo and isatty(sys.stdout):sys.stdout.write(A) _translate_ch_to_exc(A);return A././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/_textwrap.py0000644000000000000000000000127515156326616022327 0ustar00pbrochadpbrochadimport textwrap from contextlib import contextmanager class TextWrapper(textwrap.TextWrapper): def _handle_long_word(E,reversed_chunks,cur_line,cur_len,width): B=cur_line;A=reversed_chunks;C=max(width-cur_len,1) if E.break_long_words:D=A[-1];F=D[:C];G=D[C:];B.append(F);A[-1]=G elif not B:B.append(A.pop()) @contextmanager def extra_indent(self,indent): B=indent;A=self;C=A.initial_indent;D=A.subsequent_indent;A.initial_indent+=B;A.subsequent_indent+=B try:yield finally:A.initial_indent=C;A.subsequent_indent=D def indent_only(A,text): B=[] for(D,E)in enumerate(text.splitlines()): C=A.initial_indent if D>0:C=A.subsequent_indent B.append(f"{C}{E}") return'\n'.join(B)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/_unicodefun.py0000644000000000000000000000317215156326616022606 0ustar00pbrochadpbrochadimport codecs,os def _verify_python_env(): 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();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'):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 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=H for A in(os.environ.get('LC_ALL'),os.environ.get('LANG')): 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}")././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/click/_winconsole.py0000644000000000000000000001133415156326617022627 0ustar00pbrochadpbrochad_E=False _D='strict' _C='utf-16-le' _B=True _A=None import ctypes,io,time from ctypes import byref,c_char,c_char_p,c_int,c_ssize_t,c_ulong,c_void_p,POINTER,py_object,windll,WINFUNCTYPE from ctypes.wintypes import DWORD from ctypes.wintypes import HANDLE from ctypes.wintypes import LPCWSTR from ctypes.wintypes import LPWSTR import msvcrt from._compat import _NonClosingTextIOWrapper try:from ctypes import pythonapi except ImportError:pythonapi=_A else:PyObject_GetBuffer=pythonapi.PyObject_GetBuffer;PyBuffer_Release=pythonapi.PyBuffer_Release c_ssize_p=POINTER(c_ssize_t) kernel32=windll.kernel32 GetStdHandle=kernel32.GetStdHandle ReadConsoleW=kernel32.ReadConsoleW WriteConsoleW=kernel32.WriteConsoleW GetConsoleMode=kernel32.GetConsoleMode GetLastError=kernel32.GetLastError GetCommandLineW=WINFUNCTYPE(LPWSTR)(('GetCommandLineW',windll.kernel32)) CommandLineToArgvW=WINFUNCTYPE(POINTER(LPWSTR),LPCWSTR,POINTER(c_int))(('CommandLineToArgvW',windll.shell32)) LocalFree=WINFUNCTYPE(ctypes.c_void_p,ctypes.c_void_p)(('LocalFree',windll.kernel32)) STDIN_HANDLE=GetStdHandle(-10) STDOUT_HANDLE=GetStdHandle(-11) STDERR_HANDLE=GetStdHandle(-12) PyBUF_SIMPLE=0 PyBUF_WRITABLE=1 ERROR_SUCCESS=0 ERROR_NOT_ENOUGH_MEMORY=8 ERROR_OPERATION_ABORTED=995 STDIN_FILENO=0 STDOUT_FILENO=1 STDERR_FILENO=2 EOF=b'\x1a' MAX_BYTES_WRITTEN=32767 class Py_buffer(ctypes.Structure):_fields_=[('buf',c_void_p),('obj',py_object),('len',c_ssize_t),('itemsize',c_ssize_t),('readonly',c_int),('ndim',c_int),('format',c_char_p),('shape',c_ssize_p),('strides',c_ssize_p),('suboffsets',c_ssize_p),('internal',c_void_p)] if pythonapi is _A:get_buffer=_A else: def get_buffer(obj,writable=_E): A=Py_buffer();B=PyBUF_WRITABLE if writable else PyBUF_SIMPLE;PyObject_GetBuffer(py_object(obj),byref(A),B) try:C=c_char*A.len;return C.from_address(A.buf) finally:PyBuffer_Release(byref(A)) class _WindowsConsoleRawIOBase(io.RawIOBase): def __init__(A,handle):A.handle=handle def isatty(A):io.RawIOBase.isatty(A);return _B class _WindowsConsoleReader(_WindowsConsoleRawIOBase): def readable(A):return _B def readinto(D,b): A=len(b) if not A:return 0 elif A%2:raise ValueError('cannot read odd number of bytes from UTF-16-LE encoded console') B=get_buffer(b,writable=_B);E=A//2;C=c_ulong();F=ReadConsoleW(HANDLE(D.handle),B,E,byref(C),_A) if GetLastError()==ERROR_OPERATION_ABORTED:time.sleep(.1) if not F:raise OSError(f"Windows error: {GetLastError()}") if B[0]==EOF:return 0 return 2*C.value class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): def writable(A):return _B @staticmethod def _get_error_message(errno): A=errno if A==ERROR_SUCCESS:return'ERROR_SUCCESS' elif A==ERROR_NOT_ENOUGH_MEMORY:return'ERROR_NOT_ENOUGH_MEMORY' return f"Windows error {A}" def write(A,b): B=len(b);E=get_buffer(b);F=min(B,MAX_BYTES_WRITTEN)//2;C=c_ulong();WriteConsoleW(HANDLE(A.handle),E,F,byref(C),_A);D=2*C.value if D==0 and B>0:raise OSError(A._get_error_message(GetLastError())) return D class ConsoleStream: def __init__(A,text_stream,byte_stream):A._text_stream=text_stream;A.buffer=byte_stream @property def name(self):return self.buffer.name def write(A,x): if isinstance(x,str):return A._text_stream.write(x) try:A.flush() except Exception:pass return A.buffer.write(x) def writelines(A,lines): for B in lines:A.write(B) def __getattr__(A,name):return getattr(A._text_stream,name) def isatty(A):return A.buffer.isatty() def __repr__(A):return f"" class WindowsChunkedWriter: def __init__(A,wrapped):A.__wrapped=wrapped def __getattr__(A,name):return getattr(A.__wrapped,name) def write(D,text): B=len(text);A=0 while A" def get_usage(A,ctx):raise NotImplementedError('Base commands cannot get usage') def get_help(A,ctx):raise NotImplementedError('Base commands cannot get help') def make_context(A,info_name,args,parent=_A,**B): for(D,E)in A.context_settings.items(): if D not in B:B[D]=E C=Context(A,info_name=info_name,parent=parent,**B) with C.scope(cleanup=_B):A.parse_args(C,args) return C def parse_args(A,ctx,args):raise NotImplementedError('Base commands do not know how to parse arguments.') def invoke(A,ctx):raise NotImplementedError('Base commands are not invokable by default') def main(E,args=_A,prog_name=_A,complete_var=_A,standalone_mode=_C,**G): D=standalone_mode;C=prog_name;B=args;_verify_python_env() if B is _A:B=sys.argv[1:] else:B=list(B) if C is _A:C=make_str(os.path.basename(sys.argv[0]if sys.argv else __file__)) _bashcomplete(E,C,complete_var) try: try: with E.make_context(C,B,**G)as F: H=E.invoke(F) if not D:return H F.exit() except(EOFError,KeyboardInterrupt):echo(file=sys.stderr);raise Abort() except ClickException as A: if not D:raise A.show();sys.exit(A.exit_code) except OSError as A: if A.errno==errno.EPIPE:sys.stdout=PacifyFlushWrapper(sys.stdout);sys.stderr=PacifyFlushWrapper(sys.stderr);sys.exit(1) else:raise except Exit as A: if D:sys.exit(A.exit_code) else:return A.exit_code except Abort: if not D:raise echo('Aborted!',file=sys.stderr);sys.exit(1) 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[] if help and B in help:help=help.split(B,1)[0] A.help=help;A.epilog=epilog;A.options_metavar=options_metavar;A.short_help=short_help;A.add_help_option=add_help_option;A.no_args_is_help=no_args_is_help;A.hidden=hidden;A.deprecated=deprecated def __repr__(A):return f"<{A.__class__.__name__} {A.name}>" def get_usage(B,ctx):A=ctx.make_formatter();B.format_usage(ctx,A);return A.getvalue().rstrip('\n') def get_params(B,ctx): A=B.params;C=B.get_help_option(ctx) if C is not _A:A=A+[C] return A def format_usage(A,ctx,formatter):B=A.collect_usage_pieces(ctx);formatter.write_usage(ctx.command_path,' '.join(B)) def collect_usage_pieces(A,ctx): B=[A.options_metavar] for C in A.get_params(ctx):B.extend(C.get_usage_pieces(ctx)) return B def get_help_option_names(C,ctx): A=set(ctx.help_option_names) for B in C.params:A.difference_update(B.opts);A.difference_update(B.secondary_opts) return A def get_help_option(A,ctx): B=A.get_help_option_names(ctx) if not B or not A.add_help_option:return def C(ctx,param,value): A=ctx if value and not A.resilient_parsing:echo(A.get_help(),color=A.color);A.exit() return Option(B,is_flag=_C,is_eager=_C,expose_value=_B,callback=C,help='Show this message and exit.') def make_parser(C,ctx): A=ctx;B=OptionParser(A) for D in C.get_params(A):D.add_to_parser(B,A) return B def get_help(B,ctx):A=ctx.make_formatter();B.format_help(ctx,A);return A.getvalue().rstrip('\n') def get_short_help_str(A,limit=45):return A.short_help or A.help and make_default_short_help(A.help,limit)or'' def format_help(A,ctx,formatter):C=formatter;B=ctx;A.format_usage(B,C);A.format_help_text(B,C);A.format_options(B,C);A.format_epilog(B,C) def format_help_text(B,ctx,formatter): A=formatter if B.help: A.write_paragraph() with A.indentation(): C=B.help if B.deprecated:C+=DEPRECATED_HELP_NOTICE A.write_text(C) elif B.deprecated: A.write_paragraph() with A.indentation():A.write_text(DEPRECATED_HELP_NOTICE) def format_options(D,ctx,formatter): B=formatter;A=[] for E in D.get_params(ctx): C=E.get_help_record(ctx) if C is not _A:A.append(C) if A: with B.section('Options'):B.write_dl(A) def format_epilog(B,ctx,formatter): A=formatter if B.epilog: A.write_paragraph() with A.indentation():A.write_text(B.epilog) def parse_args(C,ctx,args): B=args;A=ctx if not B and C.no_args_is_help and not A.resilient_parsing:echo(A.get_help(),color=A.color);A.exit() D=C.make_parser(A);E,B,F=D.parse_args(args=B) for G in iter_params_for_processing(F,C.get_params(A)):H,B=G.handle_parse_result(A,E,B) if B and not A.allow_extra_args and not A.resilient_parsing:A.fail(f"Got unexpected extra argument{'s'if len(B)!=1 else''} ({' '.join(map(make_str,B))})") A.args=B;return B def invoke(A,ctx): _maybe_show_deprecated_notice(A) if A.callback is not _A:return ctx.invoke(A.callback,**ctx.params) class MultiCommand(Command): allow_extra_args=_C;allow_interspersed_args=_B def __init__(A,name=_A,invoke_without_command=_B,no_args_is_help=_A,subcommand_metavar=_A,chain=_B,result_callback=_A,**G): E=chain;D=invoke_without_command;C=no_args_is_help;B=subcommand_metavar;Command.__init__(A,name,**G) if C is _A:C=not D A.no_args_is_help=C;A.invoke_without_command=D if B is _A: if E:B=SUBCOMMANDS_METAVAR else:B=SUBCOMMAND_METAVAR A.subcommand_metavar=B;A.chain=E;A.result_callback=result_callback if A.chain: for F in A.params: if isinstance(F,Argument)and not F.required:raise RuntimeError('Multi commands in chain mode cannot have optional arguments.') def collect_usage_pieces(A,ctx):B=Command.collect_usage_pieces(A,ctx);B.append(A.subcommand_metavar);return B def format_options(A,ctx,formatter):B=formatter;Command.format_options(A,ctx,B);A.format_commands(ctx,B) def resultcallback(A,replace=_B): 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) A.result_callback=D=update_wrapper(C,f);return D return B def format_commands(F,ctx,formatter): D=formatter;B=[] for C in F.list_commands(ctx): A=F.get_command(ctx,C) if A is _A:continue if A.hidden:continue B.append((C,A)) if len(B): G=D.width-6-max(len(A[0])for A in B);E=[] for(C,A)in B:help=A.get_short_help_str(G);E.append((C,help)) if E: with D.section('Commands'):D.write_dl(E) def parse_args(C,ctx,args): A=ctx if not args and C.no_args_is_help and not A.resilient_parsing:echo(A.get_help(),color=A.color);A.exit() B=Command.parse_args(C,A,args) if C.chain:A.protected_args=B;A.args=[] elif B:A.protected_args,A.args=B[:1],B[1:] return A.args def invoke(B,ctx): A=ctx def F(value): C=value if B.result_callback is not _A:C=A.invoke(B.result_callback,C,**A.params) return C if not A.protected_args: if B.invoke_without_command: if not B.chain:return Command.invoke(B,A) with A:Command.invoke(B,A);return F([]) A.fail('Missing command.') D=A.protected_args+A.args;A.args=[];A.protected_args=[] if not B.chain: with A: E,G,D=B.resolve_command(A,D);A.invoked_subcommand=E;Command.invoke(B,A);C=G.make_context(E,D,parent=A) with C:return F(C.command.invoke(C)) with A: A.invoked_subcommand='*'if D else _A;Command.invoke(B,A);H=[] while D:E,G,D=B.resolve_command(A,D);C=G.make_context(E,D,parent=A,allow_extra_args=_C,allow_interspersed_args=_B);H.append(C);D,C.args=C.args,[] I=[] for C in H: with C:I.append(C.command.invoke(C)) return F(I) def resolve_command(D,ctx,args): A=ctx;B=make_str(args[0]);E=B;C=D.get_command(A,B) if C is _A and A.token_normalize_func is not _A:B=A.token_normalize_func(B);C=D.get_command(A,B) if C is _A and not A.resilient_parsing: if split_opt(B)[0]:D.parse_args(A,A.args) A.fail(f"No such command '{E}'.") return B,C,args[1:] def get_command(A,ctx,cmd_name):raise NotImplementedError def list_commands(A,ctx):return[] class Group(MultiCommand): def __init__(A,name=_A,commands=_A,**B):MultiCommand.__init__(A,name,**B);A.commands=commands or{} def add_command(C,cmd,name=_A): B=cmd;A=name;A=A or B.name if A is _A:raise TypeError('Command has no name.') _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 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 return A def get_command(A,ctx,cmd_name):return A.commands.get(cmd_name) def list_commands(A,ctx):return sorted(A.commands) class CommandCollection(MultiCommand): def __init__(A,name=_A,sources=_A,**B):MultiCommand.__init__(A,name,**B);A.sources=sources or[] def add_source(A,multi_cmd):A.sources.append(multi_cmd) def get_command(A,ctx,cmd_name): C=cmd_name for D in A.sources: B=D.get_command(ctx,C) if B is not _A: if A.chain:_check_multicommand(A,C,B) return B def list_commands(B,ctx): A=set() for C in B.sources:A.update(C.list_commands(ctx)) return sorted(A) class Parameter: param_type_name='parameter' def __init__(A,param_decls=_A,type=_A,required=_B,default=_A,callback=_A,nargs=_A,metavar=_A,expose_value=_C,is_eager=_B,envvar=_A,autocompletion=_A): D=expose_value;C=default;B=nargs;A.name,A.opts,A.secondary_opts=A._parse_decls(param_decls or(),D);A.type=convert_type(type,C) if B is _A: if A.type.is_composite:B=A.type.arity else:B=1 A.required=required;A.callback=callback;A.nargs=B;A.multiple=_B;A.expose_value=D;A.default=C;A.is_eager=is_eager;A.metavar=metavar;A.envvar=envvar;A.autocompletion=autocompletion def __repr__(A):return f"<{A.__class__.__name__} {A.name}>" @property def human_readable_name(self):return self.name def make_metavar(A): 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+='...' return B def get_default(A,ctx): if callable(A.default):B=A.default() else:B=A.default return A.type_cast_value(ctx,B) def add_to_parser(A,parser,ctx):0 def consume_value(B,ctx,opts): C=ctx;A=opts.get(B.name);D=ParameterSource.COMMANDLINE if A is _A:A=B.value_from_envvar(C);D=ParameterSource.ENVIRONMENT if A is _A:A=C.lookup_default(B.name);D=ParameterSource.DEFAULT_MAP if A is not _A:C.set_parameter_source(B.name,D) return A def type_cast_value(A,ctx,value): C=value;B=ctx if A.type.is_composite: if A.nargs<=1:raise TypeError(f"Attempted to invoke composite type but nargs has been set to {A.nargs}. This is not supported; nargs needs to be set to a fixed value > 1.") if A.multiple:return tuple(A.type(C or(),A,B)for C in C or()) return A.type(C or(),A,B) def D(value,level): E=level;C=value if E==0:return A.type(C,A,B) return tuple(D(A,E-1)for A in C or()) return D(C,(A.nargs!=1)+bool(A.multiple)) def process_value(B,ctx,value): A=value if A is not _A:return B.type_cast_value(ctx,A) def value_is_missing(A,value): B=value if B is _A:return _C if(A.nargs!=1 or A.multiple)and B==():return _C return _B def full_process_value(B,ctx,value): C=ctx;A=value;A=B.process_value(C,A) if A is _A and not C.resilient_parsing: A=B.get_default(C) if A is not _A:C.set_parameter_source(B.name,ParameterSource.DEFAULT) if B.required and B.value_is_missing(A):raise MissingParameter(ctx=C,param=B) return A def resolve_envvar_value(B,ctx): if B.envvar is _A:return if isinstance(B.envvar,(tuple,list)): for C in B.envvar: A=os.environ.get(C) if A is not _A:return A else: A=os.environ.get(B.envvar) if A!='':return A def value_from_envvar(B,ctx): A=B.resolve_envvar_value(ctx) if A is not _A and B.nargs!=1:A=B.type.split_envvar_value(A) return A def handle_parse_result(A,ctx,opts,args): B=ctx with augment_usage_errors(B,param=A): C=A.consume_value(B,opts) try:C=A.full_process_value(B,C) except Exception: if not B.resilient_parsing:raise C=_A if A.callback is not _A: try:C=A.callback(B,A,C) except Exception: if not B.resilient_parsing:raise if A.expose_value:B.params[A.name]=C 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' / '.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(_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 if B is _A: if C is not _A:B=_C else:B=bool(A.secondary_opts) if B and H:A.default=_B if C is _A:C=not A.default A.is_flag=B;A.flag_value=C if A.is_flag and isinstance(A.flag_value,bool)and type in[_A,bool]:A.type=BOOL;A.is_bool_flag=_C else:A.is_bool_flag=_B A.count=F if F: if type is _A:A.type=IntRange(min=0) if H:A.default=0 A.multiple=multiple;A.allow_from_autoenv=allow_from_autoenv;A.help=help;A.show_default=show_default;A.show_choices=show_choices;A.show_envvar=show_envvar if __debug__: if A.nargs<0:raise TypeError('Options cannot have nargs < 0') if A.prompt and A.is_flag and not A.is_bool_flag:raise TypeError('Cannot prompt for flags that are not bools.') if not A.is_bool_flag and A.secondary_opts:raise TypeError('Got secondary option for non boolean flag.') if A.is_bool_flag and A.hide_input and A.prompt is not _A:raise TypeError('Hidden input does not work with boolean flag prompts.') 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(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]=='/'else'/' if H in B: E,G=B.split(H,1);E=E.rstrip() if E:D.append(split_opt(E));C.append(E) G=G.lstrip() 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('-','_').lower() if not A.isidentifier():A=_A if A is _A: if not expose_value:return _A,C,F raise TypeError('Could not determine name for option') 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,_D:A.nargs,'obj':A} if A.multiple:D='append' elif A.count:D='count' else:D='store' if A.is_flag: 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): E=ctx if A.hidden:return F=[] def G(opts): B,C=join_options(opts) if C:F[:]=[_C] if not A.is_flag and not A.count:B+=f" {A.make_metavar()}" return B H=[G(A.opts)] if A.secondary_opts:H.append(G(A.secondary_opts)) help=A.help or'';C=[] if A.show_envvar: 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=', '.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=', '.join(str(A)for A 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' / ').join(H),help def get_default(A,ctx): if A.is_flag and not A.is_bool_flag: for B in ctx.command.params: if B.name==A.name and B.default:return B.flag_value return return Parameter.get_default(A,ctx) def prompt_for_value(A,ctx): B=A.get_default(ctx) if A.is_bool_flag:return confirm(A.prompt,B) return prompt(A.prompt,default=B,type=A.type,hide_input=A.hide_input,show_choices=A.show_choices,confirmation_prompt=A.confirmation_prompt,value_proc=lambda x:A.process_value(ctx,x)) def resolve_envvar_value(A,ctx): B=ctx;C=Parameter.resolve_envvar_value(A,B) if C is not _A:return C if A.allow_from_autoenv and B.auto_envvar_prefix is not _A:D=f"{B.auto_envvar_prefix}_{A.name.upper()}";return os.environ.get(D) def value_from_envvar(A,ctx): B=A.resolve_envvar_value(ctx) if B is _A:return C=(A.nargs!=1)+bool(A.multiple) if C>0 and B is not _A: B=A.type.split_envvar_value(B) if A.multiple and A.nargs!=1:B=batch(B,A.nargs) return B def full_process_value(A,ctx,value): C=value;B=ctx if C is _A and A.prompt is not _A and not B.resilient_parsing:return A.prompt_for_value(B) return Parameter.full_process_value(A,B,C) class Argument(Parameter): param_type_name='argument' def __init__(B,param_decls,required=_A,**C): A=required if A is _A: 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 def human_readable_name(self): A=self if A.metavar is not _A:return A.metavar return A.name.upper() def make_metavar(A): if A.metavar is not _A:return A.metavar 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+='...' 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('-','_').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()] def get_error_hint(A,ctx):return repr(A.make_metavar()) def add_to_parser(A,parser,ctx):parser.add_argument(dest=A.name,nargs=A.nargs,obj=A)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/decorators.py0000644000000000000000000002100515156326616022450 0ustar00pbrochadpbrochad_J='is_eager' _I='prompt' _H='expose_value' _G='callback' _F='is_flag' _E='cls' _D=False _C=True _B='help' _A=None import inspect,sys from functools import update_wrapper from.core import Argument from.core import Command from.core import Group from.core import Option 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) 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) 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 def B(f): def B(*D,**E): B=get_current_context() 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 update_wrapper(B,f) return B def _make_command(f,name,attrs,cls): A=attrs if isinstance(f,Command):raise TypeError('Attempted to convert a callback into a command twice.') try:B=f.__click_params__;B.reverse();del f.__click_params__ except AttributeError:B=[] help=A.get(_B) if help is _A: help=inspect.getdoc(f) if isinstance(help,bytes):help=help.decode('utf-8') else:help=inspect.cleandoc(help) A[_B]=help;return cls(name=name or f.__name__.lower().replace('_','-'),callback=f,params=B,**A) def command(name=_A,cls=_A,**C): 'Creates a new :class:`Command` and uses the decorated function as\n callback. This will also automatically attach all decorated\n :func:`option`\\s and :func:`argument`\\s as parameters to the command.\n\n The name of the command defaults to the name of the function with\n underscores replaced by dashes. If you want to change that, you can\n pass the intended name as the first argument.\n\n All keyword arguments are forwarded to the underlying command class.\n\n Once decorated the function turns into a :class:`Command` instance\n that can be invoked as a command line utility or be attached to a\n command :class:`Group`.\n\n :param name: the name of the command. This defaults to the function\n name with underscores replaced by dashes.\n :param cls: the command class to instantiate. This defaults to\n :class:`Command`.\n ';A=cls if A is _A:A=Command def B(f):B=_make_command(f,name,C,A);B.__doc__=f.__doc__;return B return B def group(name=_A,**A):'Creates a new :class:`Group` with a function as callback. This\n works otherwise the same as :func:`command` just that the `cls`\n parameter is set to :class:`Group`.\n ';A.setdefault(_E,Group);return command(name,**A) def _param_memo(f,param): A=param if isinstance(f,Command):f.params.append(A) else: if not hasattr(f,'__click_params__'):f.__click_params__=[] f.__click_params__.append(A) def argument(*B,**A): 'Attaches an argument to the command. All positional arguments are\n passed as parameter declarations to :class:`Argument`; all keyword\n arguments are forwarded unchanged (except ``cls``).\n This is equivalent to creating an :class:`Argument` instance manually\n and attaching it to the :attr:`Command.params` list.\n\n :param cls: the argument class to instantiate. This defaults to\n :class:`Argument`.\n ' def C(f):C=A.pop(_E,Argument);_param_memo(f,C(B,**A));return f return C def option(*B,**C): 'Attaches an option to the command. All positional arguments are\n passed as parameter declarations to :class:`Option`; all keyword\n arguments are forwarded unchanged (except ``cls``).\n This is equivalent to creating an :class:`Option` instance manually\n and attaching it to the :attr:`Command.params` list.\n\n :param cls: the option class to instantiate. This defaults to\n :class:`Option`.\n ' def A(f): A=C.copy() if _B in A:A[_B]=inspect.cleandoc(A[_B]) D=A.pop(_E,Option);_param_memo(f,D(B,**A));return f return A def confirmation_option(*B,**A): "Shortcut for confirmation prompts that can be ignored by passing\n ``--yes`` as parameter.\n\n This is equivalent to decorating a function with :func:`option` with\n the following parameters::\n\n def callback(ctx, param, value):\n if not value:\n ctx.abort()\n\n @click.command()\n @click.option('--yes', is_flag=True, callback=callback,\n expose_value=False, prompt='Do you want to continue?')\n def dropdb():\n pass\n " 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) 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) 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 if D is _A: if hasattr(sys,'_getframe'):E=sys._getframe(1).f_globals.get('__name__') else:E='' def C(f): G=A.pop('prog_name',_A);H=A.pop('message','%(prog)s, version %(version)s') def C(ctx,param,value): A=ctx if not value or A.resilient_parsing:return C=G if C is _A:C=A.find_root().info_name B=D if B is _A: try:import pkg_resources as I except ImportError:pass else: for F in I.working_set: J=F.get_entry_map().get('console_scripts')or{} for K in J.values(): 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) 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 ' def C(f): 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) return C././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/exceptions.py0000644000000000000000000000633315156326616022473 0ustar00pbrochadpbrochad_A=None from._compat import filename_to_ui,get_text_stderr from.utils import echo def _join_param_hints(param_hint): A=param_hint if isinstance(A,(tuple,list)):return' / '.join(repr(A)for A in A) return A class ClickException(Exception): exit_code=1 def __init__(B,message):A=message;super().__init__(A);B.message=A def format_message(A):return A.message def __str__(A):return A.message def show(B,file=_A): A=file if A is _A:A=get_text_stderr() echo(f"Error: {B.format_message()}",file=A) class UsageError(ClickException): exit_code=2 def __init__(A,message,ctx=_A):ClickException.__init__(A,message);A.ctx=ctx;A.cmd=A.ctx.command if A.ctx else _A def show(A,file=_A): B=file if B is _A:B=get_text_stderr() C=_A;D='' if A.cmd is not _A and A.cmd.get_help_option(A.ctx)is not _A:D=f"Try '{A.ctx.command_path} {A.ctx.help_option_names[0]}' for help.\n" if A.ctx is not _A:C=A.ctx.color;echo(f"{A.ctx.get_usage()}\n{D}",file=B,color=C) echo(f"Error: {A.format_message()}",file=B,color=C) class BadParameter(UsageError): def __init__(A,message,ctx=_A,param=_A,param_hint=_A):UsageError.__init__(A,message,ctx);A.param=param;A.param_hint=param_hint def format_message(A): if A.param_hint is not _A:B=A.param_hint elif A.param is not _A:B=A.param.get_error_hint(A.ctx) else:return f"Invalid value: {A.message}" B=_join_param_hints(B);return f"Invalid value for {B}: {A.message}" class MissingParameter(BadParameter): def __init__(A,message=_A,ctx=_A,param=_A,param_hint=_A,param_type=_A):BadParameter.__init__(A,message,ctx,param,param_hint);A.param_type=param_type def format_message(A): if A.param_hint is not _A:B=A.param_hint elif A.param is not _A:B=A.param.get_error_hint(A.ctx) else:B=_A B=_join_param_hints(B);D=A.param_type if D is _A and A.param is not _A:D=A.param.param_type_name C=A.message if A.param is not _A: E=A.param.type.get_missing_message(A.param) if E: if C:C+=f". {E}" else:C=E F=f" {B}"if B else'';return f"Missing {D}{F}.{' 'if C else''}{C or''}" def __str__(A): if A.message is _A:B=A.param.name if A.param else _A;return f"missing parameter: {B}" else:return A.message class NoSuchOption(UsageError): def __init__(A,option_name,message=_A,possibilities=_A,ctx=_A): C=option_name;B=message if B is _A:B=f"no such option: {C}" UsageError.__init__(A,B,ctx);A.option_name=C;A.possibilities=possibilities def format_message(A): B=[A.message] if A.possibilities: if len(A.possibilities)==1:B.append(f"Did you mean {A.possibilities[0]}?") else:C=sorted(A.possibilities);B.append(f"(Possible options: {', '.join(C)})") return' '.join(B) class BadOptionUsage(UsageError): def __init__(A,option_name,message,ctx=_A):UsageError.__init__(A,message,ctx);A.option_name=option_name class BadArgumentUsage(UsageError): def __init__(A,message,ctx=_A):UsageError.__init__(A,message,ctx) class FileError(ClickException): def __init__(A,filename,hint=_A): C=filename;B=hint;D=filename_to_ui(C) if B is _A:B='unknown error' ClickException.__init__(A,B);A.ui_filename=D;A.filename=C def format_message(A):return f"Could not open file {A.ui_filename}: {A.message}" class Abort(RuntimeError):0 class Exit(RuntimeError): __slots__='exit_code', def __init__(A,code=0):A.exit_code=code././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/formatting.py0000644000000000000000000000645715156326616022473 0ustar00pbrochadpbrochad_E=True _D=False _C=' ' _B='\n' _A=None from contextlib import contextmanager from._compat import term_len from.parser import split_opt from.termui import get_terminal_size FORCED_WIDTH=_A def measure_table(rows): A={} for C in rows: for(B,D)in enumerate(C):A[B]=max(A.get(B,0),term_len(D)) return tuple(A for(B,A)in sorted(A.items())) def iter_rows(rows,col_count): for A in rows:A=tuple(A);yield A+('',)*(col_count-len(A)) def wrap_text(text,width=78,initial_indent='',subsequent_indent='',preserve_paragraphs=_D): A=text;from._textwrap import TextWrapper as I;A=A.expandtabs();E=I(width,initial_indent=initial_indent,subsequent_indent=subsequent_indent,replace_whitespace=_D) if not preserve_paragraphs:return E.fill(A) F=[];C=[];B=_A def H(): if not C:return if C[0].strip()=='\x08':F.append((B or 0,_E,_B.join(C[1:]))) else:F.append((B or 0,_D,_C.join(C))) del C[:] for D in A.splitlines(): if not D:H();B=_A else: if B is _A:J=term_len(D);D=D.lstrip();B=J-term_len(D) C.append(D) H();G=[] for(B,K,A)in F: with E.extra_indent(_C*B): if K:G.append(E.indent_only(A)) else:G.append(E.fill(A)) return'\n\n'.join(G) class HelpFormatter: def __init__(B,indent_increment=2,width=_A,max_width=_A): C=max_width;A=width;B.indent_increment=indent_increment if C is _A:C=80 if A is _A: A=FORCED_WIDTH if A is _A:A=max(min(get_terminal_size()[0],C)-2,50) B.width=A;B.current_indent=0;B.buffer=[] def write(A,string):A.buffer.append(string) def indent(A):A.current_indent+=A.indent_increment def dedent(A):A.current_indent-=A.indent_increment def write_usage(A,prog,args='',prefix='Usage: '): E=prefix;B=f"{E:>{A.current_indent}}{prog} ";D=A.width-A.current_indent if D>=term_len(B)+20:C=_C*term_len(B);A.write(wrap_text(args,D,initial_indent=B,subsequent_indent=C)) else:A.write(B);A.write(_B);C=_C*(max(A.current_indent,term_len(E))+4);A.write(wrap_text(args,D,initial_indent=C,subsequent_indent=C)) A.write(_B) def write_heading(A,heading):A.write(f"{'':>{A.current_indent}}{heading}:\n") def write_paragraph(A): if A.buffer:A.write(_B) def write_text(A,text):C=max(A.width-A.current_indent,11);B=_C*A.current_indent;A.write(wrap_text(text,C,initial_indent=B,subsequent_indent=B,preserve_paragraphs=_E));A.write(_B) def write_dl(A,rows,col_max=30,col_spacing=2): G=col_spacing;C=rows;C=list(C);E=measure_table(C) if len(E)!=2:raise TypeError('Expected two columns for definition list') B=min(E[0],col_max)+G for(F,H)in iter_rows(C,len(E)): A.write(f"{'':>{A.current_indent}}{F}") if not H:A.write(_B);continue if term_len(F)<=B-G:A.write(_C*(B-term_len(F))) else:A.write(_B);A.write(_C*(B+A.current_indent)) I=max(A.width-B-2,10);J=wrap_text(H,I,preserve_paragraphs=_E);D=J.splitlines() if D: A.write(f"{D[0]}\n") for K in D[1:]:A.write(f"{'':>{B+A.current_indent}}{K}\n") if len(D)>1:A.write(_B) else:A.write(_B) @contextmanager def section(self,name): A=self;A.write_paragraph();A.write_heading(name);A.indent() try:yield finally:A.dedent() @contextmanager def indentation(self): self.indent() try:yield finally:self.dedent() def getvalue(A):return''.join(A.buffer) def join_options(options): A=[];B=_D for C in options: D=split_opt(C)[0] if D=='/':B=_E A.append((len(D),C)) A.sort(key=lambda x:x[0]);A=', '.join(A[1]for A in A);return A,B././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/globals.py0000644000000000000000000000073015156326616021730 0ustar00pbrochadpbrochadfrom threading import local _local=local() def get_current_context(silent=False): try:return _local.stack[-1] except(AttributeError,IndexError): 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=None): A=color if A is not None:return A B=get_current_context(silent=True) if B is not None:return B.color././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/parser.py0000644000000000000000000001312015156326616021576 0ustar00pbrochadpbrochad_C='append' _B='store' _A=None import re from collections import deque from.exceptions import BadArgumentUsage from.exceptions import BadOptionUsage from.exceptions import NoSuchOption from.exceptions import UsageError def _unpack_args(args,nargs_spec): D=nargs_spec;C=args;C=deque(C);D=deque(D);A=[];B=_A def F(c): try: if B is _A:return c.popleft() else:return c.pop() except IndexError:return while D: E=F(D) if E==1:A.append(F(C)) elif E>1: G=[F(C)for A in range(E)] if B is not _A:G.reverse() A.append(tuple(G)) elif E<0: if B is not _A:raise TypeError('Cannot have two nargs < 0') B=len(A);A.append(_A) if B is not _A:A[B]=tuple(C);C=[];A[B+1:]=reversed(A[B+1:]) return tuple(A),list(C) def _error_opt_args(nargs,opt): B=nargs;A=opt if B==1:raise BadOptionUsage(A,f"{A} option requires an argument") raise BadOptionUsage(A,f"{A} option requires {B} arguments") def split_opt(opt): A=opt;B=A[:1] if B.isalnum():return'',A if A[1:2]==B:return A[:2],A[2:] return B,A[1:] def normalize_opt(opt,ctx): B=ctx;A=opt if B is _A or B.token_normalize_func is _A:return A C,A=split_opt(A);return f"{C}{B.token_normalize_func(A)}" def split_arg_string(string): B=string;C=[] for D in re.finditer('(\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|\\"([^\\"\\\\]*(?:\\\\.[^\\"\\\\]*)*)\\"|\\S+)\\s*',B,re.S): A=D.group().strip() if A[:1]==A[-1:]and A[:1]in'"\'':A=A[1:-1].encode('ascii','backslashreplace').decode('unicode-escape') try:A=type(B)(A) except UnicodeError:pass C.append(A) return C class Option: def __init__(A,opts,dest,action=_A,nargs=1,const=_A,obj=_A): D=action;A._short_opts=[];A._long_opts=[];A.prefixes=set() for B in opts: C,E=split_opt(B) if not C:raise ValueError(f"Invalid start character for option ({B})") A.prefixes.add(C[0]) if len(C)==1 and len(E)==1:A._short_opts.append(B) else:A._long_opts.append(B);A.prefixes.add(C) if D is _A:D=_B A.dest=dest;A.action=D;A.nargs=nargs;A.const=const;A.obj=obj @property def takes_value(self):return self.action in(_B,_C) def process(A,value,state): C=value;B=state if A.action==_B:B.opts[A.dest]=C elif A.action=='store_const':B.opts[A.dest]=A.const elif A.action==_C:B.opts.setdefault(A.dest,[]).append(C) elif A.action=='append_const':B.opts.setdefault(A.dest,[]).append(A.const) elif A.action=='count':B.opts[A.dest]=B.opts.get(A.dest,0)+1 else:raise ValueError(f"unknown action '{A.action}'") B.order.append(A.obj) class Argument: def __init__(A,dest,nargs=1,obj=_A):A.dest=dest;A.nargs=nargs;A.obj=obj def process(A,value,state): C=state;B=value if A.nargs>1: D=sum(1 for A in B if A is _A) if D==len(B):B=_A elif D!=0:raise BadArgumentUsage(f"argument {A.dest} takes {A.nargs} values") C.opts[A.dest]=B;C.order.append(A.obj) 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=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): D=obj;C=opts if D is _A:D=dest C=[normalize_opt(A,B.ctx)for A in C];A=Option(C,dest,action=action,nargs=nargs,const=const,obj=D);B._opt_prefixes.update(A.prefixes) for E in A._short_opts:B._short_opt[E]=A for E in A._long_opts:B._long_opt[E]=A def add_argument(B,dest,nargs=1,obj=_A): A=obj if A is _A:A=dest B._args.append(Argument(dest=dest,nargs=nargs,obj=A)) def parse_args(B,args): A=ParsingState(args) try:B._process_args_for_options(A);B._process_args_for_args(A) except UsageError: if B.ctx is _A or not B.ctx.resilient_parsing:raise return A.opts,A.largs,A.order def _process_args_for_args(B,state): A=state;C,D=_unpack_args(A.largs+A.rargs,[A.nargs for A in B._args]) for(E,F)in enumerate(B._args):F.process(C[E],A) A.largs=D;A.rargs=[] def _process_args_for_options(C,state): B=state while B.rargs: A=B.rargs.pop(0);D=len(A) if A=='--':return elif A[:1]in C._opt_prefixes and D>1:C._process_opts(A,B) elif C.allow_interspersed_args:B.largs.append(A) else:B.rargs.insert(0,A);return def _match_long_opt(D,opt,explicit_value,state): E=explicit_value;B=state;A=opt if A not in D._long_opt:H=[B for B in D._long_opt if B.startswith(A)];raise NoSuchOption(A,possibilities=H,ctx=D.ctx) F=D._long_opt[A] if F.takes_value: if E is not _A:B.rargs.insert(0,E) C=F.nargs if len(B.rargs)" class CliRunner: 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=_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) if not A.mix_stderr:F=io.BytesIO();sys.stderr=io.TextIOWrapper(F,encoding=A.charset) if A.mix_stderr:sys.stderr=sys.stdout sys.stdin=input def L(prompt=_A):sys.stdout.write(prompt or'');A=input.readline().rstrip(_B);sys.stdout.write(f"{A}\n");sys.stdout.flush();return A def M(prompt=_A):sys.stdout.write(f"{prompt or''}\n");sys.stdout.flush();return input.readline().rstrip(_B) def N(echo): A=sys.stdin.read(1) if echo:sys.stdout.write(A);sys.stdout.flush() return A O=color def P(stream=_A,color=_A): A=color if A is _A:return not O return not A Q=termui.visible_prompt_func;R=termui.hidden_prompt_func;S=termui._getchar;T=utils.should_strip_ansi;termui.visible_prompt_func=L;termui.hidden_prompt_func=M;termui._getchar=N;utils.should_strip_ansi=P;G={} try: for(B,C)in D.items(): G[B]=os.environ.get(B) if C is _A: try:del os.environ[B] except Exception:pass else:os.environ[B]=C yield(E,not A.mix_stderr and F) finally: for(B,C)in G.items(): if C is _A: try:del os.environ[B] 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=_C,**G): C=args;E=_A with B.isolation(input=input,env=env,color=color)as H: F=_A;A=0 if isinstance(C,str):C=shlex.split(C) try:I=G.pop('prog_name') except KeyError:I=B.get_default_prog_name(cli) try:cli.main(args=C or(),prog_name=I,**G) except SystemExit as D: 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('\n');A=1 except Exception as D: if not catch_exceptions:raise F=D;A=1;E=sys.exc_info() finally: sys.stdout.flush();K=H[0].getvalue() if B.mix_stderr:J=_A else:J=H[1].getvalue() return Result(runner=B,stdout_bytes=K,stderr_bytes=J,exit_code=A,exception=F,exc_info=E) @contextlib.contextmanager def isolated_filesystem(self): B=os.getcwd();A=tempfile.mkdtemp();os.chdir(A) try:yield A finally: os.chdir(B) try:shutil.rmtree(A) except OSError:pass././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776270.0 dynaconf-3.2.13/dynaconf/vendor/click/types.py0000644000000000000000000002143615156326616021457 0ustar00pbrochadpbrochad_E='replace' _D='utf-8' _C=True _B=False _A=None import os,stat from datetime import datetime from._compat import _get_argv_encoding from._compat import filename_to_ui from._compat import get_filesystem_encoding from._compat import get_strerror from._compat import open_stream from.exceptions import BadParameter from.utils import LazyFile from.utils import safecall class ParamType: is_composite=_B;name=_A;envvar_list_splitter=_A def __call__(B,value,param=_A,ctx=_A): A=value if A is not _A:return B.convert(A,param,ctx) def get_metavar(A,param):0 def get_missing_message(A,param):0 def convert(A,value,param,ctx):return value def split_envvar_value(A,rv):return(rv or'').split(A.envvar_list_splitter) def fail(A,message,param=_A,ctx=_A):raise BadParameter(message,ctx=ctx,param=param) class CompositeParamType(ParamType): is_composite=_C @property def arity(self):raise NotImplementedError class FuncParamType(ParamType): def __init__(A,func):A.name=func.__name__;A.func=func def convert(B,value,param,ctx): A=value try:return B.func(A) except ValueError: try:A=str(A) except UnicodeError:A=A.decode(_D,_E) B.fail(A,param,ctx) class UnprocessedParamType(ParamType): name='text' def convert(A,value,param,ctx):return value def __repr__(A):return'UNPROCESSED' class StringParamType(ParamType): name='text' def convert(D,value,param,ctx): A=value if isinstance(A,bytes): B=_get_argv_encoding() try:A=A.decode(B) except UnicodeError: C=get_filesystem_encoding() if C!=B: try:A=A.decode(C) except UnicodeError:A=A.decode(_D,_E) else:A=A.decode(_D,_E) return A return A def __repr__(A):return'STRING' class Choice(ParamType): name='choice' def __init__(A,choices,case_sensitive=_C):A.choices=choices;A.case_sensitive=case_sensitive def get_metavar(A,param):return f"[{'|'.join(A.choices)}]" def get_missing_message(A,param):B=',\n\t'.join(A.choices);return f"Choose from:\n\t{B}" def convert(D,value,param,ctx): E=value;B=ctx;C=E;A={A:A for A in D.choices} if B is not _A and B.token_normalize_func is not _A:C=B.token_normalize_func(E);A={B.token_normalize_func(A):C for(A,C)in A.items()} if not D.case_sensitive:C=C.casefold();A={A.casefold():B for(A,B)in A.items()} if C in A:return A[C] D.fail(f"invalid choice: {E}. (choose from {', '.join(D.choices)})",param,B) def __repr__(A):return f"Choice({list(A.choices)})" class DateTime(ParamType): name='datetime' def __init__(A,formats=_A):A.formats=formats or['%Y-%m-%d','%Y-%m-%dT%H:%M:%S','%Y-%m-%d %H:%M:%S'] def get_metavar(A,param):return f"[{'|'.join(A.formats)}]" def _try_to_convert_date(A,value,format): try:return datetime.strptime(value,format) except ValueError:return def convert(A,value,param,ctx): B=value for format in A.formats: C=A._try_to_convert_date(B,format) if C:return C A.fail(f"invalid datetime format: {B}. (choose from {', '.join(A.formats)})") def __repr__(A):return'DateTime' class IntParamType(ParamType): name='integer' def convert(B,value,param,ctx): A=value try:return int(A) except ValueError:B.fail(f"{A} is not a valid integer",param,ctx) def __repr__(A):return'INT' class IntRange(IntParamType): name='integer range' def __init__(A,min=_A,max=_A,clamp=_B):A.min=min;A.max=max;A.clamp=clamp def convert(A,value,param,ctx): D=ctx;C=param;B=IntParamType.convert(A,value,C,D) if A.clamp: if A.min is not _A and BA.max:return A.max if A.min is not _A and BA.max: if A.min is _A:A.fail(f"{B} is bigger than the maximum valid value {A.max}.",C,D) elif A.max is _A:A.fail(f"{B} is smaller than the minimum valid value {A.min}.",C,D) else:A.fail(f"{B} is not in the valid range of {A.min} to {A.max}.",C,D) return B def __repr__(A):return f"IntRange({A.min}, {A.max})" class FloatParamType(ParamType): name='float' def convert(B,value,param,ctx): A=value try:return float(A) except ValueError:B.fail(f"{A} is not a valid floating point value",param,ctx) def __repr__(A):return'FLOAT' class FloatRange(FloatParamType): name='float range' def __init__(A,min=_A,max=_A,clamp=_B):A.min=min;A.max=max;A.clamp=clamp def convert(A,value,param,ctx): D=ctx;C=param;B=FloatParamType.convert(A,value,C,D) if A.clamp: if A.min is not _A and BA.max:return A.max if A.min is not _A and BA.max: if A.min is _A:A.fail(f"{B} is bigger than the maximum valid value {A.max}.",C,D) elif A.max is _A:A.fail(f"{B} is smaller than the minimum valid value {A.min}.",C,D) else:A.fail(f"{B} is not in the valid range of {A.min} to {A.max}.",C,D) return B def __repr__(A):return f"FloatRange({A.min}, {A.max})" class BoolParamType(ParamType): name='boolean' def convert(B,value,param,ctx): A=value if isinstance(A,bool):return bool(A) A=A.lower() if A in('true','t','1','yes','y'):return _C elif A in('false','f','0','no','n'):return _B B.fail(f"{A} is not a valid boolean",param,ctx) def __repr__(A):return'BOOL' class UUIDParameterType(ParamType): name='uuid' def convert(B,value,param,ctx): A=value;import uuid try:return uuid.UUID(A) except ValueError:B.fail(f"{A} is not a valid UUID value",param,ctx) def __repr__(A):return'UUID' class File(ParamType): name='filename';envvar_list_splitter=os.path.pathsep def __init__(A,mode='r',encoding=_A,errors='strict',lazy=_A,atomic=_B):A.mode=mode;A.encoding=encoding;A.errors=errors;A.lazy=lazy;A.atomic=atomic def resolve_lazy_flag(A,value): if A.lazy is not _A:return A.lazy if value=='-':return _B elif'w'in A.mode:return _C return _B def convert(A,value,param,ctx): C=ctx;B=value try: if hasattr(B,'read')or hasattr(B,'write'):return B E=A.resolve_lazy_flag(B) if E: D=LazyFile(B,A.mode,A.encoding,A.errors,atomic=A.atomic) if C is not _A:C.call_on_close(D.close_intelligently) return D D,F=open_stream(B,A.mode,A.encoding,A.errors,atomic=A.atomic) if C is not _A: if F:C.call_on_close(safecall(D.close)) else:C.call_on_close(safecall(D.flush)) return D except OSError as G:A.fail(f"Could not open file: {filename_to_ui(B)}: {get_strerror(G)}",param,C) class Path(ParamType): envvar_list_splitter=os.path.pathsep def __init__(A,exists=_B,file_okay=_C,dir_okay=_C,writable=_B,readable=_C,resolve_path=_B,allow_dash=_B,path_type=_A): A.exists=exists;A.file_okay=file_okay;A.dir_okay=dir_okay;A.writable=writable;A.readable=readable;A.resolve_path=resolve_path;A.allow_dash=allow_dash;A.type=path_type if A.file_okay and not A.dir_okay:A.name='file';A.path_type='File' elif A.dir_okay and not A.file_okay:A.name='directory';A.path_type='Directory' else:A.name='path';A.path_type='Path' def coerce_path_result(B,rv): A=rv if B.type is not _A and not isinstance(A,B.type): if B.type is str:A=A.decode(get_filesystem_encoding()) else:A=A.encode(get_filesystem_encoding()) return A def convert(A,value,param,ctx): E=ctx;D=param;B=value;C=B;G=A.file_okay and A.allow_dash and C in(b'-','-') if not G: if A.resolve_path:C=os.path.realpath(C) try:F=os.stat(C) except OSError: if not A.exists:return A.coerce_path_result(C) A.fail(f"{A.path_type} {filename_to_ui(B)!r} does not exist.",D,E) if not A.file_okay and stat.S_ISREG(F.st_mode):A.fail(f"{A.path_type} {filename_to_ui(B)!r} is a file.",D,E) if not A.dir_okay and stat.S_ISDIR(F.st_mode):A.fail(f"{A.path_type} {filename_to_ui(B)!r} is a directory.",D,E) if A.writable and not os.access(B,os.W_OK):A.fail(f"{A.path_type} {filename_to_ui(B)!r} is not writable.",D,E) if A.readable and not os.access(B,os.R_OK):A.fail(f"{A.path_type} {filename_to_ui(B)!r} is not readable.",D,E) return A.coerce_path_result(C) class Tuple(CompositeParamType): def __init__(A,types):A.types=[convert_type(A)for A in types] @property def name(self):return f"<{' '.join(A.name for A in self.types)}>" @property def arity(self):return len(self.types) def convert(A,value,param,ctx): B=value if len(B)!=len(A.types):raise TypeError('It would appear that nargs is set to conflict with the composite type arity.') return tuple(A(B,param,ctx)for(A,B)in zip(A.types,B)) def convert_type(ty,default=_A): B=default;A=ty;C=_B if A is _A and B is not _A: if isinstance(B,tuple):A=tuple(map(type,B)) else:A=type(B) C=_C if isinstance(A,tuple):return Tuple(A) if isinstance(A,ParamType):return A if A is str or A is _A:return STRING if A is int:return INT if A is bool and not C:return BOOL if A is float:return FLOAT if C:return STRING if __debug__: try: if issubclass(A,ParamType):raise AssertionError(f"Attempted to use an uninstantiated parameter type ({A}).") except TypeError:pass return FuncParamType(A) UNPROCESSED=UnprocessedParamType() STRING=StringParamType() INT=IntParamType() FLOAT=FloatParamType() BOOL=BoolParamType() UUID=UUIDParameterType()././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/click/utils.py0000644000000000000000000001040315156326617021444 0ustar00pbrochadpbrochad_D='strict' _C=True _B=False _A=None import os,sys from._compat import _default_text_stderr,_default_text_stdout,_find_binary_writer,auto_wrap_for_ansi,binary_streams,filename_to_ui,get_filesystem_encoding,get_strerror,is_bytes,open_stream,should_strip_ansi,strip_ansi,text_streams,WIN from.globals import resolve_color_default 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) except Exception:pass return A def make_str(value): A=value if isinstance(A,bytes): try:return A.decode(get_filesystem_encoding()) except UnicodeError:return A.decode('utf-8','replace') return str(A) def make_default_short_help(help,max_length=45): F=help.split();D=0;A=[];C=_B for B in F: if B[-1:]=='.':C=_C E=1+len(B)if A else len(B) if D+E>max_length:A.append('...');C=_C else: if A:A.append(' ') A.append(B) if C:break D+=E return''.join(A) class LazyFile: def __init__(A,filename,mode='r',encoding=_A,errors=_D,atomic=_B): E=errors;D=encoding;C=mode;B=filename;A.name=B;A.mode=C;A.encoding=D;A.errors=E;A.atomic=atomic if B=='-':A._f,A.should_close=open_stream(B,C,D,E) else: if'r'in C:open(B,C).close() A._f=_A;A.should_close=_C def __getattr__(A,name):return getattr(A.open(),name) def __repr__(A): if A._f is not _A:return repr(A._f) return f"" def open(A): if A._f is not _A:return A._f try:B,A.should_close=open_stream(A.name,A.mode,A.encoding,A.errors,atomic=A.atomic) except OSError as C:from.exceptions import FileError as D;raise D(A.name,hint=get_strerror(C)) A._f=B;return B def close(A): if A._f is not _A:A._f.close() def close_intelligently(A): if A.should_close:A.close() def __enter__(A):return A def __exit__(A,exc_type,exc_value,tb):A.close_intelligently() def __iter__(A):A.open();return iter(A._f) class KeepOpenFile: def __init__(A,file):A._file=file def __getattr__(A,name):return getattr(A._file,name) def __enter__(A):return A def __exit__(A,exc_type,exc_value,tb):0 def __repr__(A):return repr(A._file) def __iter__(A):return iter(A._file) def echo(message=_A,file=_A,nl=_C,err=_B,color=_A): C=color;B=file;A=message if B is _A: if err:B=_default_text_stderr() else:B=_default_text_stdout() if A is not _A and not isinstance(A,echo_native_types):A=str(A) if nl: A=A or'' if isinstance(A,str):A+='\n' else:A+=b'\n' if A and is_bytes(A): D=_find_binary_writer(B) if D is not _A:B.flush();D.write(A);D.flush();return if A and not is_bytes(A): C=resolve_color_default(C) if should_strip_ansi(B,C):A=strip_ansi(A) elif WIN: if auto_wrap_for_ansi is not _A:B=auto_wrap_for_ansi(B) elif not C:A=strip_ansi(A) if A:B.write(A) B.flush() def get_binary_stream(name): A=binary_streams.get(name) if A is _A:raise TypeError(f"Unknown standard stream '{name}'") return A() def get_text_stream(name,encoding=_A,errors=_D): A=text_streams.get(name) if A is _A:raise TypeError(f"Unknown standard stream '{name}'") return A(encoding,errors) def open_file(filename,mode='r',encoding=_A,errors=_D,lazy=_B,atomic=_B): E=atomic;D=errors;C=encoding;B=filename if lazy:return LazyFile(B,mode,C,D,atomic=E) A,F=open_stream(B,mode,C,D,atomic=E) if not F:A=KeepOpenFile(A) return A def get_os_args():import warnings as A;A.warn("'get_os_args' is deprecated and will be removed in 8.1. Access 'sys.argv[1:]' directly instead.",DeprecationWarning,stacklevel=2);return sys.argv[1:] def format_filename(filename,shorten=_B): A=filename if shorten:A=os.path.basename(A) return filename_to_ui(A) def get_app_dir(app_name,roaming=_C,force_posix=_B): A=app_name if WIN: C='APPDATA'if roaming else'LOCALAPPDATA';B=os.environ.get(C) if B is _A:B=os.path.expanduser('~') return os.path.join(B,A) if force_posix:return os.path.join(os.path.expanduser(f"~/.{_posixify(A)}")) if sys.platform=='darwin':return os.path.join(os.path.expanduser('~/Library/Application Support'),A) return os.path.join(os.environ.get('XDG_CONFIG_HOME',os.path.expanduser('~/.config')),_posixify(A)) class PacifyFlushWrapper: def __init__(A,wrapped):A.wrapped=wrapped def flush(A): try:A.wrapped.flush() except OSError as B: import errno if B.errno!=errno.EPIPE:raise def __getattr__(A,attr):return getattr(A.wrapped,attr)././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9169083 dynaconf-3.2.13/dynaconf/vendor/dotenv/0000755000000000000000000000000015156326623020143 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/dotenv/__init__.py0000644000000000000000000000127215156326617022261 0ustar00pbrochadpbrochad_A=None from.compat import IS_TYPE_CHECKING from.main import load_dotenv,get_key,set_key,unset_key,find_dotenv,dotenv_values 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): D=quote;C=action;B=value;A=['dotenv'] if D:A.append('-q %s'%D) if path:A.append('-f %s'%path) if C: A.append(C) if key: A.append(key) if B: if' 'in B:A.append('"%s"'%B) else:A.append(B) return' '.join(A).strip() __all__=['get_cli_string','load_dotenv','dotenv_values','get_key','set_key','unset_key','find_dotenv','load_ipython_extension']././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/dotenv/cli.py0000644000000000000000000000435015156326617021271 0ustar00pbrochadpbrochad_F='always' _E='key' _D='%s=%s' _C='QUOTE' _B='FILE' _A=True import os,sys from subprocess import Popen try:from dynaconf.vendor import click except ImportError:sys.stderr.write('It seems python-dotenv is not installed with cli option. \nRun pip install "python-dotenv[cli]" to fix this.');sys.exit(1) from.compat import IS_TYPE_CHECKING,to_env from.main import dotenv_values,get_key,set_key,unset_key from.version import __version__ if IS_TYPE_CHECKING:from typing import Any,List,Dict @click.group() @click.option('-f','--file',default=os.path.join(os.getcwd(),'.env'),type=click.Path(exists=_A),help='Location of the .env file, defaults to .env file in current working directory.') @click.option('-q','--quote',default=_F,type=click.Choice([_F,'never','auto']),help='Whether to quote or not the variable values. Default mode is always. This does not affect parsing.') @click.version_option(version=__version__) @click.pass_context def cli(ctx,file,quote):A=ctx;A.obj={};A.obj[_B]=file;A.obj[_C]=quote @cli.command() @click.pass_context def list(ctx): A=ctx.obj[_B];B=dotenv_values(A) for(C,D)in B.items():click.echo(_D%(C,D)) @cli.command() @click.pass_context @click.argument(_E,required=_A) @click.argument('value',required=_A) def set(ctx,key,value): B=value;A=key;C=ctx.obj[_B];D=ctx.obj[_C];E,A,B=set_key(C,A,B,D) if E:click.echo(_D%(A,B)) else:exit(1) @cli.command() @click.pass_context @click.argument(_E,required=_A) def get(ctx,key): B=ctx.obj[_B];A=get_key(B,key) if A:click.echo(_D%(key,A)) else:exit(1) @cli.command() @click.pass_context @click.argument(_E,required=_A) def unset(ctx,key): A=key;B=ctx.obj[_B];C=ctx.obj[_C];D,A=unset_key(B,A,C) if D:click.echo('Successfully removed %s'%A) else:exit(1) @cli.command(context_settings={'ignore_unknown_options':_A}) @click.pass_context @click.argument('commandline',nargs=-1,type=click.UNPROCESSED) def run(ctx,commandline): A=commandline;B=ctx.obj[_B];C={to_env(B):to_env(A)for(B,A)in dotenv_values(B).items()if A is not None} if not A:click.echo('No command given.');exit(1) D=run_command(A,C);exit(D) def run_command(command,env):A=os.environ.copy();A.update(env);B=Popen(command,universal_newlines=_A,bufsize=0,shell=False,env=A);C,C=B.communicate();return B.returncode if __name__=='__main__':cli()././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/dotenv/compat.py0000644000000000000000000000073115156326617022004 0ustar00pbrochadpbrochadimport sys PY2=sys.version_info[0]==2 if PY2:from StringIO import StringIO else:from io import StringIO def is_type_checking(): try:from typing import TYPE_CHECKING as A except ImportError:return False return A 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'utf-8') else:return text def to_text(string): A=string if PY2:return A.decode('utf-8') else:return A././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/dotenv/ipython.py0000644000000000000000000000161415156326617022214 0ustar00pbrochadpbrochadfrom __future__ import print_function _A='store_true' from IPython.core.magic import Magics,line_magic,magics_class from IPython.core.magic_arguments import argument,magic_arguments,parse_argstring from.main import find_dotenv,load_dotenv @magics_class class IPythonDotEnv(Magics): @magic_arguments() @argument('-o','--override',action=_A,help='Indicate to override existing variables') @argument('-v','--verbose',action=_A,help='Indicate function calls to be verbose') @argument('dotenv_path',nargs='?',type=str,default='.env',help='Search in increasingly higher folders for the `dotenv_path`') @line_magic def dotenv(self,line): 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)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/dotenv/main.py0000644000000000000000000001160615156326617021450 0ustar00pbrochadpbrochadfrom __future__ import absolute_import,print_function,unicode_literals _D='always' _C=True _B=False _A=None import io,logging,os,re,shutil,sys,tempfile from collections import OrderedDict from contextlib import contextmanager from.compat import IS_TYPE_CHECKING,PY2,StringIO,to_env from.parser import Binding,parse_stream logger=logging.getLogger(__name__) if IS_TYPE_CHECKING: from typing import Dict,Iterator,Match,Optional,Pattern,Union,Text,IO,Tuple if sys.version_info>=(3,6):_PathLike=os.PathLike else:_PathLike=Text if sys.version_info>=(3,0):_StringIO=StringIO else:_StringIO=StringIO[Text] __posix_variable=re.compile('\n \\$\\{\n (?P[^\\}:]*)\n (?::-\n (?P[^\\}]*)\n )?\n \\}\n ',re.VERBOSE) def with_warn_for_invalid_lines(mappings): for A in mappings: if A.error:logger.warning('Python-dotenv could not parse statement starting at line %s',A.original.line) yield A class DotEnv: def __init__(A,dotenv_path,verbose=_B,encoding=_A,interpolate=_C):A.dotenv_path=dotenv_path;A._dict=_A;A.verbose=verbose;A.encoding=encoding;A.interpolate=interpolate @contextmanager def _get_stream(self): A=self if isinstance(A.dotenv_path,StringIO):yield A.dotenv_path 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'.env') yield StringIO('') def dict(A): if A._dict:return A._dict B=OrderedDict(A.parse());A._dict=resolve_nested_variables(B)if A.interpolate else B;return A._dict def parse(B): with B._get_stream()as C: for A in with_warn_for_invalid_lines(parse_stream(C)): if A.key is not _A:yield(A.key,A.value) def set_as_environment_variables(C,override=_B): for(A,B)in C.dict().items(): if A in os.environ and not override:continue if B is not _A:os.environ[to_env(A)]=to_env(B) return _C def get(A,key): B=key;C=A.dict() if B in C:return C[B] if A.verbose:logger.warning('Key %s not found in %s.',B,A.dotenv_path) def get_key(dotenv_path,key_to_get):return DotEnv(dotenv_path,verbose=_C).get(key_to_get) @contextmanager def rewrite(path): try: with tempfile.NamedTemporaryFile(mode='w+',delete=_B)as A: with io.open(path)as B:yield(B,A) except BaseException: if os.path.isfile(A.name):os.unlink(A.name) raise else:shutil.move(A.name,path) def set_key(dotenv_path,key_to_set,value_to_set,quote_mode=_D): 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('"','\\"')) else:F=A G='{}={}\n'.format(B,F) with rewrite(C)as(J,D): H=_B for I in with_warn_for_invalid_lines(parse_stream(J)): if I.key==B:D.write(G);H=_C else:D.write(I.original.string) if not H:D.write(G) return _C,B,A def unset_key(dotenv_path,key_to_unset,quote_mode=_D): B=dotenv_path;A=key_to_unset if not os.path.exists(B):logger.warning("Can't delete from %s - it doesn't exist.",B);return _A,A C=_B with rewrite(B)as(E,F): for D in with_warn_for_invalid_lines(parse_stream(E)): if D.key==A:C=_C else:F.write(D.original.string) if not C:logger.warning("Key %s not removed from %s - key doesn't exist.",A,B);return _A,A return C,A def resolve_nested_variables(values): def C(name,default):A=default;A=A if A is not _A else'';C=os.getenv(name,B.get(name,A));return C def D(match):A=match.groupdict();return C(name=A['name'],default=A['default']) B={} for(E,A)in values.items():B[E]=__posix_variable.sub(D,A)if A is not _A else _A return B def _walk_to_root(path): A=path if not os.path.exists(A):raise IOError('Starting path not found') 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='.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(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 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'' def load_dotenv(dotenv_path=_A,stream=_A,verbose=_B,override=_B,interpolate=_C,**A):B=dotenv_path or stream or find_dotenv();return DotEnv(B,verbose=verbose,interpolate=interpolate,**A).set_as_environment_variables(override=override) def dotenv_values(dotenv_path=_A,stream=_A,verbose=_B,interpolate=_C,**A):B=dotenv_path or stream or find_dotenv();return DotEnv(B,verbose=verbose,interpolate=interpolate,**A).dict()././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/dotenv/parser.py0000644000000000000000000000746615156326617022031 0ustar00pbrochadpbrochad_E='original' _D='Binding' _C='string' _B='Original' _A=None import codecs,re from.compat import IS_TYPE_CHECKING,to_text if IS_TYPE_CHECKING:from typing import IO,Iterator,Match,NamedTuple,Optional,Pattern,Sequence,Text,Tuple def make_regex(string,extra_flags=0):return re.compile(to_text(string),re.UNICODE|extra_flags) _newline=make_regex('(\\r\\n|\\n|\\r)') _multiline_whitespace=make_regex('\\s*',extra_flags=re.MULTILINE) _whitespace=make_regex('[^\\S\\r\\n]*') _export=make_regex('(?:export[^\\S\\r\\n]+)?') _single_quoted_key=make_regex("'([^']+)'") _unquoted_key=make_regex('([^=\\#\\s]+)') _equal_sign=make_regex('(=[^\\S\\r\\n]*)') _single_quoted_value=make_regex("'((?:\\\\'|[^'])*)'") _double_quoted_value=make_regex('"((?:\\\\"|[^"])*)"') _unquoted_value_part=make_regex('([^ \\r\\n]*)') _comment=make_regex('(?:[^\\S\\r\\n]*#[^\\r\\n]*)?') _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),('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 def start(A):return A(chars=0,line=1) def set(A,other):B=other;A.chars=B.chars;A.line=B.line def advance(A,string):B=string;A.chars+=len(B);A.line+=len(re.findall(_newline,B)) class Error(Exception):0 class Reader: def __init__(A,stream):A.string=stream.read();A.position=Position.start();A.mark=Position.start() def has_next(A):return A.position.chars=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__=False from dynaconf.vendor.ruamel.yaml.main import*././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/anchor.py0000644000000000000000000000050215156326617022717 0ustar00pbrochadpbrochadif 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=False def __repr__(A):B=', (always dump)'if A.always_dump else'';return'Anchor({!r}{})'.format(A.value,B)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/comments.py0000644000000000000000000004053615156326617023305 0ustar00pbrochadpbrochadfrom __future__ import absolute_import,print_function _E='CommentedMap' _D=True _C='\n' _B=False _A=None import sys,copy from.compat import ordereddict from.compat import PY2,string_types,MutableSliceableSequence from.scalarstring import ScalarString 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',_E,'CommentedOrderedMap','CommentedSet','comment_attrib','merge_attrib'] comment_attrib='_yaml_comment' format_attrib='_yaml_format' line_col_attrib='_yaml_line_col' merge_attrib='_yaml_merge' tag_attrib='_yaml_tag' class Comment: __slots__='comment','_items','_end','_start';attrib=comment_attrib def __init__(A):A.comment=_A;A._items={};A._end=[] def __str__(A): if bool(A._end):B=',\n end='+str(A._end) else:B='' return'Comment(comment={0},\n items={1}{2})'.format(A.comment,A._items,B) @property def items(self):return self._items @property def end(self):return self._end @end.setter def end(self,value):self._end=value @property def start(self):return self._start @start.setter def start(self,value):self._start=value def NoComment():0 class Format: __slots__='_flow_style',;attrib=format_attrib def __init__(A):A._flow_style=_A def set_flow_style(A):A._flow_style=_D def set_block_style(A):A._flow_style=_B def flow_style(A,default=_A): if A._flow_style is _A:return default return A._flow_style class LineCol: attrib=line_col_attrib def __init__(A):A.line=_A;A.col=_A;A.data=_A def add_kv_line_col(A,key,data): if A.data is _A:A.data={} A.data[key]=data def key(A,k):return A._kv(k,0,1) def value(A,k):return A._kv(k,2,3) def _kv(A,k,x0,x1): if A.data is _A:return B=A.data[k];return B[x0],B[x1] def item(A,idx): if A.data is _A:return return A.data[idx][0],A.data[idx][1] def add_idx_line_col(A,key,data): if A.data is _A:A.data={} A.data[key]=data class Tag: __slots__='value',;attrib=tag_attrib def __init__(A):A.value=_A def __repr__(A):return'{0.__class__.__name__}({0.value!r})'.format(A) class CommentedBase: @property def ca(self): A=self if not hasattr(A,Comment.attrib):setattr(A,Comment.attrib,Comment()) return getattr(A,Comment.attrib) def yaml_end_comment_extend(A,comment,clear=_B): B=comment if B is _A:return if clear or A.ca.end is _A:A.ca.end=[] A.ca.end.extend(B) def yaml_key_comment_extend(C,key,comment,clear=_B): A=comment;B=C.ca._items.setdefault(key,[_A,_A,_A,_A]) if clear or B[1]is _A: if A[1]is not _A:assert isinstance(A[1],list) B[1]=A[1] else:B[1].extend(A[0]) B[0]=A[0] def yaml_value_comment_extend(C,key,comment,clear=_B): A=comment;B=C.ca._items.setdefault(key,[_A,_A,_A,_A]) if clear or B[3]is _A: if A[1]is not _A:assert isinstance(A[1],list) B[3]=A[1] else:B[3].extend(A[0]) B[2]=A[0] def yaml_set_start_comment(B,comment,indent=0): 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('# '+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(('# '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] D=I(H);C=J.ca.items.setdefault(key,[_A,[],_A,_A]) if A==_C:C[1].append(F('',D)) elif A: for G in A.split(_C):C[1].append(F(G,D)) if B: D=I(E) if C[3]is _A:C[3]=[] for G in B.split(_C):C[3].append(F(G,D)) @property def fa(self): A=self 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): 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]!='#':A='# '+A if B is _A: 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): A=self if not hasattr(A,LineCol.attrib):setattr(A,LineCol.attrib,LineCol()) return getattr(A,LineCol.attrib) def _yaml_set_line_col(A,line,col):A.lc.line=line;A.lc.col=col def _yaml_set_kv_line_col(A,key,data):A.lc.add_kv_line_col(key,data) def _yaml_set_idx_line_col(A,key,data):A.lc.add_idx_line_col(key,data) @property def anchor(self): A=self if not hasattr(A,Anchor.attrib):setattr(A,Anchor.attrib,Anchor()) return getattr(A,Anchor.attrib) def yaml_anchor(A): if not hasattr(A,Anchor.attrib):return return A.anchor def yaml_set_anchor(A,value,always_dump=_B):A.anchor.value=value;A.anchor.always_dump=always_dump @property def tag(self): A=self if not hasattr(A,Tag.attrib):setattr(A,Tag.attrib,Tag()) return getattr(A,Tag.attrib) def yaml_set_tag(A,value):A.tag.value=value def copy_attributes(B,t,memo=_A): for A in[Comment.attrib,Format.attrib,LineCol.attrib,Anchor.attrib,Tag.attrib,merge_attrib]: if hasattr(B,A): if memo is not _A:setattr(t,A,copy.deepcopy(getattr(B,A,memo))) else:setattr(t,A,getattr(B,A)) def _yaml_add_eol_comment(A,comment,key):raise NotImplementedError def _yaml_get_pre_comment(A):raise NotImplementedError 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 __getsingleitem__(A,idx):return list.__getitem__(A,idx) def __setsingleitem__(B,idx,value): C=idx;A=value if C=C:break if D not in A.ca.items:continue B=D if B is not _A:E=A._yaml_get_columnX(B) return E def _yaml_get_pre_comment(A): B=[] if A.ca.comment is _A:A.ca.comment=[_A,B] else:A.ca.comment[1]=B return B def __deepcopy__(A,memo): C=memo;B=A.__class__();C[id(A)]=B for D in A:B.append(copy.deepcopy(D,C));A.copy_attributes(B,memo=C) return B def __add__(A,other):return list.__add__(A,other) def sort(A,key=_A,reverse=_B): C=reverse if key is _A:B=sorted(zip(A,range(len(A))),reverse=C);list.__init__(A,[A[0]for A in B]) else:B=sorted(zip(map(key,list.__iter__(A)),range(len(A))),reverse=C);list.__init__(A,[list.__getitem__(A,B[1])for B in B]) D=A.ca.items;A.ca._items={} for(F,G)in enumerate(B): E=G[1] if E in D:A.ca.items[F]=D[E] def __repr__(A):return list.__repr__(A) class CommentedKeySeq(tuple,CommentedBase): def _yaml_add_comment(A,comment,key=NoComment): B=comment if key is not NoComment:A.yaml_key_comment_extend(key,B) else:A.ca.comment=B def _yaml_add_eol_comment(A,comment,key):A._yaml_add_comment(comment,key=key) def _yaml_get_columnX(A,key):return A.ca.items[key][0].start_mark.column def _yaml_get_column(A,key): C=key;E=_A;B=_A;F,G=C-1,C+1 if F in A.ca.items:B=F elif G in A.ca.items:B=G else: for(D,H)in enumerate(A): if D>=C:break if D not in A.ca.items:continue B=D if B is not _A:E=A._yaml_get_columnX(B) return E def _yaml_get_pre_comment(A): B=[] if A.ca.comment is _A:A.ca.comment=[_A,B] else:A.ca.comment[1]=B return B class CommentedMapView(Sized): __slots__='_mapping', def __init__(A,mapping):A._mapping=mapping def __len__(A):B=len(A._mapping);return B class CommentedMapKeysView(CommentedMapView,Set): __slots__=() @classmethod def _from_iterable(A,it):return set(it) def __contains__(A,key):return key in A._mapping def __iter__(A): for B in A._mapping:yield B class CommentedMapItemsView(CommentedMapView,Set): __slots__=() @classmethod def _from_iterable(A,it):return set(it) def __contains__(A,item): B,C=item try:D=A._mapping[B] except KeyError:return _B else:return D==C def __iter__(A): for B in A._mapping._keys():yield(B,A._mapping[B]) class CommentedMapValuesView(CommentedMapView): __slots__=() def __contains__(A,value): for B in A._mapping: if value==A._mapping[B]:return _D return _B def __iter__(A): 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 _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 if C is not NoComment:A.yaml_value_comment_extend(C,B) else:A.ca.comment=B def _yaml_add_eol_comment(A,comment,key):A._yaml_add_comment(comment,value=key) def _yaml_get_columnX(A,key):return A.ca.items[key][2].start_mark.column def _yaml_get_column(A,key): E=key;H=_A;B=_A;C,F,I=_A,_A,_A for D in A: if C is not _A and D!=E:F=D;break if D==E:C=I I=D if C in A.ca.items:B=C elif F in A.ca.items:B=F else: for G in A: if G>=E:break if G not in A.ca.items:continue B=G if B is not _A:H=A._yaml_get_columnX(B) return H def _yaml_get_pre_comment(A): B=[] if A.ca.comment is _A:A.ca.comment=[_A,B] else:A.ca.comment[1]=B return B def update(B,vals): A=vals try:ordereddict.update(B,A) except TypeError: for C in A:B[C]=A[C] try:B._ok.update(A.keys()) except AttributeError: for C in A:B._ok.add(C[0]) def insert(A,pos,key,value,comment=_A): C=comment;B=key;ordereddict.insert(A,pos,B,value);A._ok.add(B) if C is not _A:A.yaml_add_eol_comment(C,key=B) def mlget(C,key,default=_A,list_ok=_B): D=list_ok;B=default;A=key if not isinstance(A,list):return C.get(A,B) def E(key_list,level,d): B=level;A=key_list if not D:assert isinstance(d,dict) if B>=len(A): if B>len(A):raise IndexError return d[A[B-1]] return E(A,B+1,d[A[B-1]]) try:return E(A,1,C) except KeyError:return B except(TypeError,IndexError): if not D:raise return B def __getitem__(B,key): A=key try:return ordereddict.__getitem__(B,A) except KeyError: for C in getattr(B,merge_attrib,[]): if A in C[1]:return C[1][A] raise def __setitem__(A,key,value): C=value;B=key if B in A: if isinstance(C,string_types)and not isinstance(C,ScalarString)and isinstance(A[B],ScalarString):C=type(A[B])(C) ordereddict.__setitem__(A,B,C);A._ok.add(B) def _unmerged_contains(A,key): if key in A._ok:return _D def __contains__(A,key):return bool(ordereddict.__contains__(A,key)) def get(A,key,default=_A): try:return A.__getitem__(key) except:return default 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)) def __delitem__(A,key): B=key;A._ok.discard(B);ordereddict.__delitem__(A,B) for C in A._ref:C.update_key_value(B) def __iter__(A): for B in ordereddict.__iter__(A):yield B def _keys(A): for B in ordereddict.__iter__(A):yield B def __len__(A):return int(ordereddict.__len__(A)) def __eq__(A,other):return bool(dict(A)==other) if PY2: def keys(A):return list(A._keys()) def iterkeys(A):return A._keys() def viewkeys(A):return CommentedMapKeysView(A) else: def keys(A):return CommentedMapKeysView(A) if PY2: def _values(A): for B in ordereddict.__iter__(A):yield ordereddict.__getitem__(A,B) def values(A):return list(A._values()) def itervalues(A):return A._values() def viewvalues(A):return CommentedMapValuesView(A) else: def values(A):return CommentedMapValuesView(A) def _items(A): for B in ordereddict.__iter__(A):yield(B,ordereddict.__getitem__(A,B)) if PY2: def items(A):return list(A._items()) def iteritems(A):return A._items() def viewitems(A):return CommentedMapItemsView(A) else: def items(A):return CommentedMapItemsView(A) @property def merge(self): A=self if not hasattr(A,merge_attrib):setattr(A,merge_attrib,[]) return getattr(A,merge_attrib) def copy(A): B=type(A)() for(C,D)in A._items():B[C]=D A.copy_attributes(B);return B def add_referent(A,cm): if cm not in A._ref:A._ref.append(cm) def add_yaml_merge(A,value): C=value for B in C: B[1].add_referent(A) for(D,B)in B[1].items(): if ordereddict.__contains__(A,D):continue ordereddict.__setitem__(A,D,B) A.merge.extend(C) def update_key_value(B,key): A=key if A in B._ok:return for C in B.merge: if A in C[1]:ordereddict.__setitem__(B,A,C[1][A]);return ordereddict.__delitem__(B,A) def __deepcopy__(A,memo): C=memo;B=A.__class__();C[id(A)]=B for D in A:B[D]=copy.deepcopy(A[D],C) A.copy_attributes(B,memo=C);return B @classmethod def raise_immutable(cls,*A,**B):raise TypeError('{} objects are immutable'.format(cls.__name__)) class CommentedKeyMap(CommentedBase,Mapping): __slots__=Comment.attrib,'_od' def __init__(A,*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 __delitem__=__setitem__=clear=pop=popitem=setdefault=update=raise_immutable def __getitem__(A,index):return A._od[index] def __iter__(A): for B in A._od.__iter__():yield B def __len__(A):return len(A._od) def __hash__(A):return hash(tuple(A.items())) def __repr__(A): if not hasattr(A,merge_attrib):return A._od.__repr__() return'ordereddict('+repr(list(A._od.items()))+')' @classmethod def fromkeys(A,v=_A):return CommentedKeyMap(dict.fromkeys(A,v)) def _yaml_add_comment(A,comment,key=NoComment): B=comment if key is not NoComment:A.yaml_key_comment_extend(key,B) else:A.ca.comment=B def _yaml_add_eol_comment(A,comment,key):A._yaml_add_comment(comment,key=key) def _yaml_get_columnX(A,key):return A.ca.items[key][0].start_mark.column def _yaml_get_column(A,key): C=key;E=_A;B=_A;F,G=C-1,C+1 if F in A.ca.items:B=F elif G in A.ca.items:B=G else: for(D,H)in enumerate(A): if D>=C:break if D not in A.ca.items:continue B=D if B is not _A:E=A._yaml_get_columnX(B) return E def _yaml_get_pre_comment(A): B=[] if A.ca.comment is _A:A.ca.comment=[_A,B] else:A.ca.comment[1]=B return B class CommentedOrderedMap(CommentedMap):__slots__=Comment.attrib, class CommentedSet(MutableSet,CommentedBase): __slots__=Comment.attrib,'odict' def __init__(A,values=_A): B=values;A.odict=ordereddict();MutableSet.__init__(A) if B is not _A:A|=B 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 if C is not NoComment:A.yaml_value_comment_extend(C,B) else:A.ca.comment=B def _yaml_add_eol_comment(A,comment,key):A._yaml_add_comment(comment,value=key) def add(A,value):A.odict[value]=_A def discard(A,value):del A.odict[value] def __contains__(A,x):return x in A.odict def __iter__(A): for B in A.odict:yield B def __len__(A):return len(A.odict) def __repr__(A):return'set({0!r})'.format(A.odict.keys()) class TaggedScalar(CommentedBase): def __init__(A,value=_A,style=_A,tag=_A): A.value=value;A.style=style 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): 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,'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)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/compat.py0000644000000000000000000001055315156326617022737 0ustar00pbrochadpbrochadfrom __future__ import print_function _D='RUAMELDEBUG' _C=True _B=False _A=None import sys,os,types,traceback from abc import abstractmethod if _B:from typing import Any,Dict,Optional,List,Union,BinaryIO,IO,Text,Tuple,Optional _DEFAULT_YAML_VERSION=1,2 try:from ruamel.ordereddict import ordereddict except: try:from collections import OrderedDict except ImportError:from ordereddict import OrderedDict class ordereddict(OrderedDict): if not hasattr(OrderedDict,'insert'): def insert(A,pos,key,value): C=value if pos>=len(A):A[key]=C;return B=ordereddict();B.update(A) for E in B:del A[E] for(F,D)in enumerate(B): if pos==F:A[key]=C A[D]=B[D] PY2=sys.version_info[0]==2 PY3=sys.version_info[0]==3 if PY3: def utf8(s):return s def to_str(s):return s def to_unicode(s):return s else: if _B:unicode=str def utf8(s):return s.encode('utf-8') def to_str(s):return str(s) def to_unicode(s):return unicode(s) if PY3:string_types=str;integer_types=int;class_types=type;text_type=str;binary_type=bytes;MAXSIZE=sys.maxsize;unichr=chr;import io;StringIO=io.StringIO;BytesIO=io.BytesIO;no_limit_int=int;from collections.abc import Hashable,MutableSequence,MutableMapping,Mapping else:string_types=basestring;integer_types=int,long;class_types=type,types.ClassType;text_type=unicode;binary_type=str;unichr=unichr;from StringIO import StringIO as _StringIO;StringIO=_StringIO;import cStringIO;BytesIO=cStringIO.StringIO;no_limit_int=long;from collections import Hashable,MutableSequence,MutableMapping,Mapping if _B:StreamType=Any;StreamTextType=StreamType;VersionType=Union[List[int],str,Tuple[int,int]] if PY3:builtins_module='builtins' else:builtins_module='__builtin__' UNICODE_SIZE=4 if sys.maxunicode>65535 else 2 def with_metaclass(meta,*A):return meta('NewBase',A,{}) DBG_TOKEN=1 DBG_EVENT=2 DBG_NODE=4 _debug=_A if _D in os.environ: _debugx=os.environ.get(_D) if _debugx is _A:_debug=0 else:_debug=int(_debugx) if bool(_debug): class ObjectCounter: def __init__(A):A.map={} def __call__(A,k):A.map[k]=A.map.get(k,0)+1 def dump(A): for B in sorted(A.map):sys.stdout.write('{} -> {}'.format(B,A.map[B])) object_counter=ObjectCounter() def dbg(val=_A): global _debug if _debug is _A: A=os.environ.get('YAMLDEBUG') if A is _A:_debug=0 else:_debug=int(A) if val is _A:return _debug return _debug&val 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() if A._max_print is not _A: if A._count is _A:A._count=A._max_print A._count-=1 if A._count==0:C('forced exit\n');traceback.print_stack();B.flush();sys.exit(0) if A._file_name:B.close() def set_max_print(A,i):A._max_print=i;A._count=_A nprint=Nprint() nprintf=Nprint('/var/tmp/ruamel.yaml.log') def check_namespace_char(ch): A=ch if'!'<=A<='~':return _C if'\xa0'<=A<='\ud7ff':return _C if'\ue000'<=A<='�'and A!='\ufeff':return _C if'𐀀'<=A<='\U0010ffff':return _C return _B def check_anchorname_char(ch): if ch in',[]{}':return _B return check_namespace_char(ch) def version_tnf(t1,t2=_A): from dynaconf.vendor.ruamel.yaml import version_info as A if Alen(B):raise TypeError('not enough elements in value {} > {}'.format(E,len(B))) 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) for C in reversed(range(*B.indices(len(A)))):del A[C] @abstractmethod def __getsingleitem__(self,index):raise IndexError @abstractmethod def __setsingleitem__(self,index,value):raise IndexError @abstractmethod def __delsingleitem__(self,index):raise IndexError././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/composer.py0000644000000000000000000000772015156326617023305 0ustar00pbrochadpbrochadfrom __future__ import absolute_import,print_function _A=None import warnings from.error import MarkedYAMLError,ReusedAnchorWarning from.compat import utf8,nprint,nprintf from.events import StreamStartEvent,StreamEndEvent,MappingStartEvent,MappingEndEvent,SequenceStartEvent,SequenceEndEvent,AliasEvent,ScalarEvent from.nodes import MappingNode,ScalarNode,SequenceNode if False:from typing import Any,Dict,Optional,List __all__=['Composer','ComposerError'] class ComposerError(MarkedYAMLError):0 class Composer: def __init__(A,loader=_A): A.loader=loader if A.loader is not _A and getattr(A.loader,'_composer',_A)is _A:A.loader._composer=A A.anchors={} @property def parser(self): A=self if hasattr(A.loader,'typ'):A.loader.parser return A.loader._parser @property def resolver(self): A=self 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() return not A.parser.check_event(StreamEndEvent) def get_node(A): if not A.parser.check_event(StreamEndEvent):return A.compose_document() def get_single_node(A): A.parser.get_event();B=_A if not A.parser.check_event(StreamEndEvent):B=A.compose_document() if not A.parser.check_event(StreamEndEvent):C=A.parser.get_event();raise ComposerError('expected a single document in the stream',B.start_mark,'but found another document',C.start_mark) A.parser.get_event();return B def compose_document(A):A.parser.get_event();B=A.compose_node(_A,_A);A.parser.get_event();A.anchors={};return B def compose_node(A,parent,index): if A.parser.check_event(AliasEvent): C=A.parser.get_event();D=C.anchor if D not in A.anchors:raise ComposerError(_A,_A,'found undefined alias %r'%utf8(D),C.start_mark) return A.anchors[D] C=A.parser.peek_event();B=C.anchor if B is not _A: if B in A.anchors:F='\nfound duplicate anchor {!r}\nfirst occurrence {}\nsecond occurrence {}'.format(B,A.anchors[B].start_mark,C.start_mark);warnings.warn(F,ReusedAnchorWarning) A.resolver.descend_resolver(parent,index) if A.parser.check_event(ScalarEvent):E=A.compose_scalar_node(B) elif A.parser.check_event(SequenceStartEvent):E=A.compose_sequence_node(B) elif A.parser.check_event(MappingStartEvent):E=A.compose_mapping_node(B) A.resolver.ascend_resolver();return E def compose_scalar_node(C,anchor): D=anchor;A=C.parser.get_event();B=A.tag if B is _A or B=='!':B=C.resolver.resolve(ScalarNode,A.value,A.implicit) E=ScalarNode(B,A.value,A.start_mark,A.end_mark,style=A.style,comment=A.comment,anchor=D) if D is not _A:C.anchors[D]=E return E def compose_sequence_node(B,anchor): F=anchor;C=B.parser.get_event();D=C.tag if D is _A or D=='!':D=B.resolver.resolve(SequenceNode,_A,C.implicit) A=SequenceNode(D,[],C.start_mark,_A,flow_style=C.flow_style,comment=C.comment,anchor=F) if F is not _A:B.anchors[F]=A G=0 while not B.parser.check_event(SequenceEndEvent):A.value.append(B.compose_node(A,G));G+=1 E=B.parser.get_event() if A.flow_style is True and E.comment is not _A: if A.comment is not _A:nprint('Warning: unexpected end_event commment in sequence node {}'.format(A.flow_style)) A.comment=E.comment A.end_mark=E.end_mark;B.check_end_doc_comment(E,A);return A def compose_mapping_node(B,anchor): F=anchor;C=B.parser.get_event();D=C.tag if D is _A or D=='!':D=B.resolver.resolve(MappingNode,_A,C.implicit) A=MappingNode(D,[],C.start_mark,_A,flow_style=C.flow_style,comment=C.comment,anchor=F) if F is not _A:B.anchors[F]=A while not B.parser.check_event(MappingEndEvent):G=B.compose_node(A,_A);H=B.compose_node(A,G);A.value.append((G,H)) E=B.parser.get_event() if A.flow_style is True and E.comment is not _A:A.comment=E.comment A.end_mark=E.end_mark;B.check_end_doc_comment(E,A);return A def check_end_doc_comment(C,end_event,node): B=node;A=end_event if A.comment and A.comment[1]: if B.comment is _A:B.comment=[_A,_A] assert not isinstance(B,ScalarEvent);B.comment.append(A.comment[1]);A.comment[1]=_A././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776271.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/configobjwalker.py0000644000000000000000000000037515156326617024623 0ustar00pbrochadpbrochadimport warnings from.util import configobj_walker as new_configobj_walker if False:from typing import Any def configobj_walker(cfg):warnings.warn('configobj_walker has moved to ruamel.yaml.util, please update your code');return new_configobj_walker(cfg)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/constructor.py0000644000000000000000000011745015156326620024037 0ustar00pbrochadpbrochadfrom __future__ import print_function,absolute_import,division _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=':' _F='0' _E='while constructing a mapping' _D='_' _C=True _B=False _A=None import datetime,base64,binascii,re,sys,types,warnings from.error import MarkedYAMLError,MarkedYAMLFutureWarning,MantissaNoDotYAML1_1Warning from.nodes import* from.nodes import SequenceNode,MappingNode,ScalarNode from.compat import utf8,builtins_module,to_str,PY2,PY3,text_type,nprint,nprintf,version_tnf from.compat import ordereddict,Hashable,MutableSequence from.compat import MutableMapping from.comments import* from.comments import CommentedMap,CommentedOrderedMap,CommentedSet,CommentedKeySeq,CommentedSeq,TaggedScalar,CommentedKeyMap from.scalarstring import SingleQuotedScalarString,DoubleQuotedScalarString,LiteralScalarString,FoldedScalarString,PlainScalarString,ScalarString from.scalarint import ScalarInt,BinaryInt,OctalInt,HexInt,HexCapsInt from.scalarfloat import ScalarFloat from.scalarbool import ScalarBoolean from.timestamp import TimeStamp from.util import RegExp if _B:from typing import Any,Dict,List,Set,Generator,Union,Optional __all__=['BaseConstructor','SafeConstructor','Constructor','ConstructorError','RoundTripConstructor'] class ConstructorError(MarkedYAMLError):0 class DuplicateKeyFutureWarning(MarkedYAMLFutureWarning):0 class DuplicateKeyError(MarkedYAMLFutureWarning):0 class BaseConstructor: yaml_constructors={};yaml_multi_constructors={} def __init__(self,preserve_quotes=_A,loader=_A): self.loader=loader if self.loader is not _A and getattr(self.loader,'_constructor',_A)is _A:self.loader._constructor=self 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,'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,'typ'):return self.loader.resolver return self.loader._resolver def check_data(self):return self.composer.check_node() def get_data(self): if self.composer.check_node():return self.construct_document(self.composer.get_node()) def get_single_data(self): node=self.composer.get_single_node() if node is not _A:return self.construct_document(node) def construct_document(self,node): data=self.construct_object(node) while bool(self.state_generators): state_generators=self.state_generators;self.state_generators=[] for generator in state_generators: for _dummy in generator:0 self.constructed_objects={};self.recursive_objects={};self.deep_construct=_B;return data def construct_object(self,node,deep=_B): if node in self.constructed_objects:return self.constructed_objects[node] if deep:old_deep=self.deep_construct;self.deep_construct=_C if node in self.recursive_objects:return self.recursive_objects[node] self.recursive_objects[node]=_A;data=self.construct_non_recursive_object(node);self.constructed_objects[node]=data;del self.recursive_objects[node] if deep:self.deep_construct=old_deep return data def construct_non_recursive_object(self,node,tag=_A): constructor=_A;tag_suffix=_A if tag is _A:tag=node.tag if tag in self.yaml_constructors:constructor=self.yaml_constructors[tag] else: for tag_prefix in self.yaml_multi_constructors: if tag.startswith(tag_prefix):tag_suffix=tag[len(tag_prefix):];constructor=self.yaml_multi_constructors[tag_prefix];break else: if _A in self.yaml_multi_constructors:tag_suffix=tag;constructor=self.yaml_multi_constructors[_A] elif _A in self.yaml_constructors:constructor=self.yaml_constructors[_A] elif isinstance(node,ScalarNode):constructor=self.__class__.construct_scalar elif isinstance(node,SequenceNode):constructor=self.__class__.construct_sequence elif isinstance(node,MappingNode):constructor=self.__class__.construct_mapping if tag_suffix is _A:data=constructor(self,node) else:data=constructor(self,tag_suffix,node) if isinstance(data,types.GeneratorType): generator=data;data=next(generator) if self.deep_construct: for _dummy in generator:0 else:self.state_generators.append(generator) return data def construct_scalar(self,node): 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,_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,_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)] for(values,check)in todo: mapping=self.yaml_base_dict_type() for(key_node,value_node)in values: key=self.construct_object(key_node,deep=_C) if not isinstance(key,Hashable): if isinstance(key,list):key=tuple(key) if PY2: try:hash(key) 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 else:mapping[key]=value total_mapping.update(mapping) return total_mapping def check_mapping_key(self,node,key_node,mapping,key,value): if key in mapping: if not self.allow_duplicate_keys: mk=mapping.get(key) if PY2: 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(_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,_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 @classmethod def add_constructor(cls,tag,constructor): if'yaml_constructors'not in cls.__dict__:cls.yaml_constructors=cls.yaml_constructors.copy() cls.yaml_constructors[tag]=constructor @classmethod def add_multi_constructor(cls,tag_prefix,multi_constructor): if'yaml_multi_constructors'not in cls.__dict__:cls.yaml_multi_constructors=cls.yaml_multi_constructors.copy() cls.yaml_multi_constructors[tag_prefix]=multi_constructor class SafeConstructor(BaseConstructor): def construct_scalar(self,node): if isinstance(node,MappingNode): for(key_node,value_node)in node.value: 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[0-9][0-9][0-9][0-9])\n -(?P[0-9][0-9]?)\n -(?P[0-9][0-9]?)\n (?:((?P[Tt])|[ \\t]+) # explictly not retaining extra spaces\n (?P[0-9][0-9]?)\n :(?P[0-9][0-9])\n :(?P[0-9][0-9])\n (?:\\.(?P[0-9]*))?\n (?:[ \\t]*(?PZ|(?P[-+])(?P[0-9][0-9]?)\n (?::(?P[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,_o.format(node.value),node.start_mark) values=match.groupdict() 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[_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(_K,node.start_mark,_Z%node.id,node.start_mark) for subnode in node.value: 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,_Z%node.id,node.start_mark) for subnode in node.value: 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(_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,_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,_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 class Constructor(SafeConstructor): def construct_python_str(self,node):return utf8(self.construct_scalar(node)) 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(_Q) except UnicodeEncodeError as exc:raise ConstructorError(_A,_A,_m%exc,node.start_mark) try: if hasattr(base64,_n):return base64.decodebytes(value) else:return base64.decodestring(value) 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 return int(val) 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(_c,mark,_A3,mark) try:__import__(name) 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,_A3,mark) if _H in name: lname=name.split(_H);lmodule_name=lname;lobject_name=[] while len(lmodule_name)>1: lobject_name.insert(0,lmodule_name.pop());module_name=_H.join(lmodule_name) try:__import__(module_name);break except ImportError:continue else:module_name=builtins_module;lobject_name=[name] try:__import__(module_name) 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) obj=getattr(obj,lobject_name.pop(0)) 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,_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(_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) 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) def set_python_instance_state(self,instance,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,_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',{}) instance=self.make_python_instance(suffix,node,args,kwds,newobj) if bool(state):self.set_python_instance_state(instance,state) if bool(listitems):instance.extend(listitems) if bool(dictitems): for key in dictitems:instance[key]=dictitems[key] return instance def construct_python_object_new(self,suffix,node):return self.construct_python_object_apply(suffix,node,newobj=_C) Constructor.add_constructor('tag:yaml.org,2002:python/none',Constructor.construct_yaml_null) Constructor.add_constructor('tag:yaml.org,2002:python/bool',Constructor.construct_yaml_bool) Constructor.add_constructor('tag:yaml.org,2002:python/str',Constructor.construct_python_str) Constructor.add_constructor('tag:yaml.org,2002:python/unicode',Constructor.construct_python_unicode) if PY3:Constructor.add_constructor('tag:yaml.org,2002:python/bytes',Constructor.construct_python_bytes) Constructor.add_constructor('tag:yaml.org,2002:python/int',Constructor.construct_yaml_int) Constructor.add_constructor('tag:yaml.org,2002:python/long',Constructor.construct_python_long) Constructor.add_constructor('tag:yaml.org,2002:python/float',Constructor.construct_yaml_float) Constructor.add_constructor('tag:yaml.org,2002:python/complex',Constructor.construct_python_complex) Constructor.add_constructor('tag:yaml.org,2002:python/list',Constructor.construct_yaml_seq) Constructor.add_constructor('tag:yaml.org,2002:python/tuple',Constructor.construct_python_tuple) Constructor.add_constructor('tag:yaml.org,2002:python/dict',Constructor.construct_yaml_map) Constructor.add_multi_constructor('tag:yaml.org,2002:python/name:',Constructor.construct_python_name) Constructor.add_multi_constructor('tag:yaml.org,2002:python/module:',Constructor.construct_python_module) Constructor.add_multi_constructor('tag:yaml.org,2002:python/object:',Constructor.construct_python_object) Constructor.add_multi_constructor('tag:yaml.org,2002:python/object/apply:',Constructor.construct_python_object_apply) Constructor.add_multi_constructor('tag:yaml.org,2002:python/object/new:',Constructor.construct_python_object_new) class RoundTripConstructor(SafeConstructor): def construct_scalar(self,node): A='\x07' 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] return lss if node.style=='>'and isinstance(node.value,text_type): fold_positions=[];idx=-1 while _C: idx=node.value.find(A,idx+1) if idx<0:break fold_positions.append(idx-len(fold_positions)) fss=FoldedScalarString(node.value.replace(A,''),anchor=node.anchor) if node.comment and node.comment[1]:fss.comment=node.comment[1][0] if fold_positions:fss.fold_pos=fold_positions return fss elif bool(self._preserve_quotes)and isinstance(node.value,text_type): if node.style=="'":return SingleQuotedScalarString(node.value,anchor=node.anchor) if node.style=='"':return DoubleQuotedScalarString(node.value,anchor=node.anchor) if node.anchor:return PlainScalarString(node.value,anchor=node.anchor) return node.value def construct_yaml_int(self,node): width=_A;value_su=to_str(self.construct_scalar(node)) try:sx=value_su.rstrip(_D);underscore=[len(sx)-sx.rindex(_D)-1,_B,_B] except ValueError:underscore=_A except IndexError:underscore=_A value_s=value_su.replace(_D,'');sign=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:]) if underscore is not _A:underscore[1]=value_su[2]==_D;underscore[2]=len(value_su[2:])>1 and value_su[-1]==_D return BinaryInt(sign*int(value_s[2:],2),width=width,underscore=underscore,anchor=node.anchor) elif value_s.startswith('0x'): if self.resolver.processing_version>(1,1)and value_s[2]==_F:width=len(value_s[2:]) hex_fun=HexInt for ch in value_s[2:]: if ch in'ABCDEF':hex_fun=HexCapsInt;break if ch in'abcdef':break if underscore is not _A:underscore[1]=value_su[2]==_D;underscore[2]=len(value_su[2:])>1 and value_su[-1]==_D return hex_fun(sign*int(value_s[2:],16),width=width,underscore=underscore,anchor=node.anchor) elif value_s.startswith('0o'): if self.resolver.processing_version>(1,1)and value_s[2]==_F:width=len(value_s[2:]) if underscore is not _A:underscore[1]=value_su[2]==_D;underscore[2]=len(value_su[2:])>1 and value_su[-1]==_D return OctalInt(sign*int(value_s[2:],8),width=width,underscore=underscore,anchor=node.anchor) elif self.resolver.processing_version!=(1,2)and value_s[0]==_F:return sign*int(value_s,8) elif self.resolver.processing_version!=(1,2)and _G in value_s: digits=[int(part)for part in value_s.split(_G)];digits.reverse();base=1;value=0 for digit in digits:value+=digit*base;base*=60 return sign*value elif self.resolver.processing_version>(1,1)and value_s[0]==_F: if underscore is not _A:underscore[2]=len(value_su)>1 and value_su[-1]==_D return ScalarInt(sign*int(value_s),width=len(value_s),underscore=underscore) elif underscore:underscore[2]=len(value_su)>1 and value_su[-1]==_D;return ScalarInt(sign*int(value_s),width=_A,underscore=underscore,anchor=node.anchor) 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): def leading_zeros(v): lead0=0;idx=0 while idx2:seqtyp.yaml_end_comment_extend(node.comment[2],clear=_C) if node.anchor: from dynaconf.vendor.ruamel.yaml.serializer import templated_id if not templated_id(node.anchor):seqtyp.yaml_set_anchor(node.anchor) for(idx,child)in enumerate(node.value): if child.comment:seqtyp._yaml_add_comment(child.comment,key=idx);child.comment=_A ret_val.append(self.construct_object(child,deep=deep));seqtyp._yaml_set_idx_line_col(idx,[child.start_mark.line,child.start_mark.column]) return ret_val def flatten_mapping(self,node): def constructed(value_node): if value_node in self.constructed_objects:value=self.constructed_objects[value_node] else:value=self.construct_object(value_node,deep=_B) return value merge_map_list=[];index=0 while index2:maptyp.yaml_end_comment_extend(node.comment[2],clear=_C) if node.anchor: from dynaconf.vendor.ruamel.yaml.serializer import templated_id if not templated_id(node.anchor):maptyp.yaml_set_anchor(node.anchor) last_key,last_value=_A,self._sentinel for(key_node,value_node)in node.value: key=self.construct_object(key_node,deep=_C) if not isinstance(key,Hashable): if isinstance(key,MutableSequence): key_s=CommentedKeySeq(key) if key_node.flow_style is _C:key_s.fa.set_flow_style() elif key_node.flow_style is _B:key_s.fa.set_block_style() key=key_s elif isinstance(key,MutableMapping): key_m=CommentedKeyMap(key) if key_node.flow_style is _C:key_m.fa.set_flow_style() elif key_node.flow_style is _B:key_m.fa.set_block_style() key=key_m if PY2: try:hash(key) 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]: if last_value is _A:key_node.comment[0]=key_node.comment.pop(4);maptyp._yaml_add_comment(key_node.comment,value=last_key) else:key_node.comment[2]=key_node.comment.pop(4);maptyp._yaml_add_comment(key_node.comment,key=key) key_node.comment=_A if key_node.comment:maptyp._yaml_add_comment(key_node.comment,key=key) if value_node.comment:maptyp._yaml_add_comment(value_node.comment,value=key) 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,_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) if node.anchor: from dynaconf.vendor.ruamel.yaml.serializer import templated_id if not templated_id(node.anchor):typ.yaml_set_anchor(node.anchor) for(key_node,value_node)in node.value: key=self.construct_object(key_node,deep=_C) if not isinstance(key,Hashable): if isinstance(key,list):key=tuple(key) if PY2: try:hash(key) 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) typ.add(key) def construct_yaml_seq(self,node): data=CommentedSeq();data._yaml_set_line_col(node.start_mark.line,node.start_mark.column) if node.comment:data._yaml_add_comment(node.comment) yield data;data.extend(self.construct_rt_sequence(node,data));self.set_collection_style(data,node) def construct_yaml_map(self,node):data=CommentedMap();data._yaml_set_line_col(node.start_mark.line,node.start_mark.column);yield data;self.construct_mapping(node,data,deep=_C);self.set_collection_style(data,node) def set_collection_style(self,data,node): if len(data)==0:return if node.flow_style is _C:data.fa.set_flow_style() 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,_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) if node.flow_style is _C:omap.fa.set_flow_style() elif node.flow_style is _B:omap.fa.set_block_style() yield omap 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(_K,node.start_mark,_Z%node.id,node.start_mark) for subnode in node.value: 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) if value_node.comment:omap._yaml_add_comment(value_node.comment,value=key) omap[key]=value def construct_yaml_set(self,node):data=CommentedSet();data._yaml_set_line_col(node.start_mark.line,node.start_mark.column);yield data;self.construct_setting(node,data) def construct_undefined(self,node): try: if isinstance(node,MappingNode): data=CommentedMap();data._yaml_set_line_col(node.start_mark.line,node.start_mark.column) if node.flow_style is _C:data.fa.set_flow_style() elif node.flow_style is _B:data.fa.set_block_style() data.yaml_set_tag(node.tag);yield data if node.anchor:data.yaml_set_anchor(node.anchor) self.construct_mapping(node,data);return elif isinstance(node,ScalarNode): data2=TaggedScalar();data2.value=self.construct_scalar(node);data2.style=node.style;data2.yaml_set_tag(node.tag);yield data2 if node.anchor:data2.yaml_set_anchor(node.anchor,always_dump=_C) return elif isinstance(node,SequenceNode): data3=CommentedSeq();data3._yaml_set_line_col(node.start_mark.line,node.start_mark.column) if node.flow_style is _C:data3.fa.set_flow_style() elif node.flow_style is _B:data3.fa.set_block_style() data3.yaml_set_tag(node.tag);yield data3 if node.anchor:data3.yaml_set_anchor(node.anchor) data3.extend(self.construct_sequence(node));return except:pass 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,_o.format(node.value),node.start_mark) values=match.groupdict() 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['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[_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[_J]+values[_S] if values[_M]:tz+=_G+values[_M] data._yaml[A]=tz else: data=TimeStamp(year,month,day,hour,minute,second,fraction) if values[A]:data._yaml[A]=values[A] if values[B]:data._yaml[B]=_C return data def construct_yaml_bool(self,node): b=SafeConstructor.construct_yaml_bool(self,node) if node.anchor:return ScalarBoolean(b,anchor=node.anchor) return b 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)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/cyaml.py0000644000000000000000000000627115156326620022555 0ustar00pbrochadpbrochadfrom __future__ import absolute_import _A=None from _ruamel_yaml import CParser,CEmitter from.constructor import Constructor,BaseConstructor,SafeConstructor from.representer import Representer,SafeRepresenter,BaseRepresenter from.resolver import Resolver,BaseResolver if False:from typing import Any,Union,Optional;from.compat import StreamTextType,StreamType,VersionType __all__=['CBaseLoader','CSafeLoader','CLoader','CBaseDumper','CSafeDumper','CDumper'] class CBaseLoader(CParser,BaseConstructor,BaseResolver): def __init__(A,stream,version=_A,preserve_quotes=_A):CParser.__init__(A,stream);A._parser=A._composer=A;BaseConstructor.__init__(A,loader=A);BaseResolver.__init__(A,loadumper=A) class CSafeLoader(CParser,SafeConstructor,Resolver): def __init__(A,stream,version=_A,preserve_quotes=_A):CParser.__init__(A,stream);A._parser=A._composer=A;SafeConstructor.__init__(A,loader=A);Resolver.__init__(A,loadumper=A) class CLoader(CParser,Constructor,Resolver): def __init__(A,stream,version=_A,preserve_quotes=_A):CParser.__init__(A,stream);A._parser=A._composer=A;Constructor.__init__(A,loader=A);Resolver.__init__(A,loadumper=A) class CBaseDumper(CEmitter,BaseRepresenter,BaseResolver): def __init__(A,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__(A,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);A._emitter=A._serializer=A._representer=A;BaseRepresenter.__init__(A,default_style=default_style,default_flow_style=default_flow_style,dumper=A);BaseResolver.__init__(A,loadumper=A) class CSafeDumper(CEmitter,SafeRepresenter,Resolver): def __init__(A,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):A._emitter=A._serializer=A._representer=A;CEmitter.__init__(A,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);A._emitter=A._serializer=A._representer=A;SafeRepresenter.__init__(A,default_style=default_style,default_flow_style=default_flow_style);Resolver.__init__(A) class CDumper(CEmitter,Representer,Resolver): def __init__(A,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__(A,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);A._emitter=A._serializer=A._representer=A;Representer.__init__(A,default_style=default_style,default_flow_style=default_flow_style);Resolver.__init__(A)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/dumper.py0000644000000000000000000000667015156326620022747 0ustar00pbrochadpbrochadfrom __future__ import absolute_import _A=None from.emitter import Emitter from.serializer import Serializer from.representer import Representer,SafeRepresenter,BaseRepresenter,RoundTripRepresenter from.resolver import Resolver,BaseResolver,VersionedResolver if False:from typing import Any,Dict,List,Union,Optional;from.compat import StreamType,VersionType __all__=['BaseDumper','SafeDumper','Dumper','RoundTripDumper'] class BaseDumper(Emitter,Serializer,BaseRepresenter,BaseResolver): def __init__(A,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):Emitter.__init__(A,stream,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break,block_seq_indent=block_seq_indent,dumper=A);Serializer.__init__(A,encoding=encoding,explicit_start=explicit_start,explicit_end=explicit_end,version=version,tags=tags,dumper=A);BaseRepresenter.__init__(A,default_style=default_style,default_flow_style=default_flow_style,dumper=A);BaseResolver.__init__(A,loadumper=A) class SafeDumper(Emitter,Serializer,SafeRepresenter,Resolver): def __init__(A,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):Emitter.__init__(A,stream,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break,block_seq_indent=block_seq_indent,dumper=A);Serializer.__init__(A,encoding=encoding,explicit_start=explicit_start,explicit_end=explicit_end,version=version,tags=tags,dumper=A);SafeRepresenter.__init__(A,default_style=default_style,default_flow_style=default_flow_style,dumper=A);Resolver.__init__(A,loadumper=A) class Dumper(Emitter,Serializer,Representer,Resolver): def __init__(A,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):Emitter.__init__(A,stream,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break,block_seq_indent=block_seq_indent,dumper=A);Serializer.__init__(A,encoding=encoding,explicit_start=explicit_start,explicit_end=explicit_end,version=version,tags=tags,dumper=A);Representer.__init__(A,default_style=default_style,default_flow_style=default_flow_style,dumper=A);Resolver.__init__(A,loadumper=A) class RoundTripDumper(Emitter,Serializer,RoundTripRepresenter,VersionedResolver): def __init__(A,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):Emitter.__init__(A,stream,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break,block_seq_indent=block_seq_indent,top_level_colon_align=top_level_colon_align,prefix_colon=prefix_colon,dumper=A);Serializer.__init__(A,encoding=encoding,explicit_start=explicit_start,explicit_end=explicit_end,version=version,tags=tags,dumper=A);RoundTripRepresenter.__init__(A,default_style=default_style,default_flow_style=default_flow_style,dumper=A);VersionedResolver.__init__(A,loader=A)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/emitter.py0000644000000000000000000010576715156326620023133 0ustar00pbrochadpbrochadfrom __future__ import absolute_import,print_function _T='\ufeff' _S='\ue000' _R='\ud7ff' _Q='%%%02X' _P='version' _O="-;/?:@&=+$,_.~*'()[]" _N=' \n\x85\u2028\u2029' _M='\xa0' _L='...' _K='\\' _J="'" _I='?' _H='"' _G='!' _F='\n\x85\u2028\u2029' _E='\n' _D=' ' _C=False _B=None _A=True import sys from.error import YAMLError,YAMLStreamError from.events import* from.compat import utf8,text_type,PY2,nprint,dbg,DBG_EVENT,check_anchorname_char if _C:from typing import Any,Dict,List,Union,Text,Tuple,Optional;from.compat import StreamType __all__=['Emitter','EmitterError'] class EmitterError(YAMLError):0 class ScalarAnalysis: def __init__(self,scalar,empty,multiline,allow_flow_plain,allow_block_plain,allow_single_quoted,allow_double_quoted,allow_block):self.scalar=scalar;self.empty=empty;self.multiline=multiline;self.allow_flow_plain=allow_flow_plain;self.allow_block_plain=allow_block_plain;self.allow_single_quoted=allow_single_quoted;self.allow_double_quoted=allow_double_quoted;self.allow_block=allow_block class Indents: def __init__(self):self.values=[] def append(self,val,seq):self.values.append((val,seq)) def pop(self):return self.values.pop()[0] def last_seq(self): try:return self.values[-2][1] except IndexError:return _C def seq_flow_align(self,seq_indent,column): if len(self.values)<2 or not self.values[-1][1]:return 0 base=self.values[-1][0]if self.values[-1][0]is not _B else 0;return base+seq_indent-column-1 def __len__(self):return len(self.values) class Emitter: DEFAULT_TAG_PREFIXES={_G:_G,'tag:yaml.org,2002:':'!!'};MAX_SIMPLE_KEY_LENGTH=128 def __init__(self,stream,canonical=_B,indent=_B,width=_B,allow_unicode=_B,line_break=_B,block_seq_indent=_B,top_level_colon_align=_B,prefix_colon=_B,brace_single_entry_mapping_in_flow_sequence=_B,dumper=_B): self.dumper=dumper if self.dumper is not _B and getattr(self.dumper,'_emitter',_B)is _B:self.dumper._emitter=self self.stream=stream;self.encoding=_B;self.allow_space_break=_B;self.states=[];self.state=self.expect_stream_start;self.events=[];self.event=_B;self.indents=Indents();self.indent=_B;self.flow_context=[];self.root_context=_C;self.sequence_context=_C;self.mapping_context=_C;self.simple_key_context=_C;self.line=0;self.column=0;self.whitespace=_A;self.indention=_A;self.compact_seq_seq=_A;self.compact_seq_map=_A;self.no_newline=_B;self.open_ended=_C;self.colon=':';self.prefixed_colon=self.colon if prefix_colon is _B else prefix_colon+self.colon;self.brace_single_entry_mapping_in_flow_sequence=brace_single_entry_mapping_in_flow_sequence;self.canonical=canonical;self.allow_unicode=allow_unicode;self.unicode_supplementary=sys.maxunicode>65535;self.sequence_dash_offset=block_seq_indent if block_seq_indent else 0;self.top_level_colon_align=top_level_colon_align;self.best_sequence_indent=2;self.requested_indent=indent if indent and 1self.best_sequence_indent*2:self.best_width=width self.best_line_break=_E if line_break in['\r',_E,'\r\n']:self.best_line_break=line_break self.tag_prefixes=_B;self.prepared_anchor=_B;self.prepared_tag=_B;self.analysis=_B;self.style=_B;self.scalar_after_indicator=_A @property def stream(self): try:return self._stream except AttributeError:raise YAMLStreamError('output stream needs to specified') @stream.setter def stream(self,val): if val is _B:return if not hasattr(val,'write'):raise YAMLStreamError('stream argument needs to have a write() method') self._stream=val @property def serializer(self): try: if hasattr(self.dumper,'typ'):return self.dumper.serializer return self.dumper._serializer except AttributeError:return self @property def flow_level(self):return len(self.flow_context) def dispose(self):self.states=[];self.state=_B def emit(self,event): if dbg(DBG_EVENT):nprint(event) self.events.append(event) while not self.need_more_events():self.event=self.events.pop(0);self.state();self.event=_B def need_more_events(self): if not self.events:return _A event=self.events[0] if isinstance(event,DocumentStartEvent):return self.need_events(1) elif isinstance(event,SequenceStartEvent):return self.need_events(2) elif isinstance(event,MappingStartEvent):return self.need_events(3) else:return _C def need_events(self,count): level=0 for event in self.events[1:]: if isinstance(event,(DocumentStartEvent,CollectionStartEvent)):level+=1 elif isinstance(event,(DocumentEndEvent,CollectionEndEvent)):level-=1 elif isinstance(event,StreamEndEvent):level=-1 if level<0:return _C return len(self.events)self.best_width:self.write_indent() 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=='[' 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(',',_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]=='['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): 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() else: 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) 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(',',_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(',',_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) def expect_flow_mapping_simple_value(self):self.write_indicator(self.prefixed_colon,_C);self.states.append(self.expect_flow_mapping_key);self.expect_node(mapping=_A) def expect_flow_mapping_value(self): if self.canonical or self.column>self.best_width:self.write_indent() self.write_indicator(self.prefixed_colon,_A);self.states.append(self.expect_flow_mapping_key);self.expect_node(mapping=_A) def expect_block_sequence(self): if self.mapping_context:indentless=not self.indention else: indentless=_C if not self.compact_seq_seq and self.column!=0:self.write_line_break() self.increase_indent(flow=_C,sequence=_A,indentless=indentless);self.state=self.expect_first_block_sequence_item def expect_first_block_sequence_item(self):return self.expect_block_sequence_item(first=_A) def expect_block_sequence_item(self,first=_C): if not first and isinstance(self.event,SequenceEndEvent): if self.event.comment and self.event.comment[1]:self.write_pre_comment(self.event) self.indent=self.indents.pop();self.state=self.states.pop();self.no_newline=_C else: if self.event.comment and self.event.comment[1]:self.write_pre_comment(self.event) nonl=self.no_newline if self.column==0 else _C;self.write_indent();ind=self.sequence_dash_offset;self.write_indicator(_D*ind+'-',_A,indention=_A) if nonl or self.sequence_dash_offset+2>self.best_sequence_indent:self.no_newline=_A self.states.append(self.expect_block_sequence_item);self.expect_node(sequence=_A) def expect_block_mapping(self): if not self.mapping_context and not(self.compact_seq_map or self.column==0):self.write_line_break() self.increase_indent(flow=_C,sequence=_C);self.state=self.expect_first_block_mapping_key def expect_first_block_mapping_key(self):return self.expect_block_mapping_key(first=_A) def expect_block_mapping_key(self,first=_C): if not first and isinstance(self.event,MappingEndEvent): if self.event.comment and self.event.comment[1]:self.write_pre_comment(self.event) self.indent=self.indents.pop();self.state=self.states.pop() else: if self.event.comment and self.event.comment[1]:self.write_pre_comment(self.event) self.write_indent() if self.check_simple_key(): if not isinstance(self.event,(SequenceStartEvent,MappingStartEvent)): try: if self.event.style==_I:self.write_indicator(_I,_A,indention=_A) except AttributeError:pass self.states.append(self.expect_block_mapping_simple_value);self.expect_node(mapping=_A,simple_key=_A) if isinstance(self.event,AliasEvent):self.stream.write(_D) else:self.write_indicator(_I,_A,indention=_A);self.states.append(self.expect_block_mapping_value);self.expect_node(mapping=_A) def expect_block_mapping_simple_value(self): if getattr(self.event,'style',_B)!=_I: if self.indent==0 and self.top_level_colon_align is not _B:c=_D*(self.top_level_colon_align-self.column)+self.colon else:c=self.prefixed_colon self.write_indicator(c,_C) self.states.append(self.expect_block_mapping_key);self.expect_node(mapping=_A) def expect_block_mapping_value(self):self.write_indent();self.write_indicator(self.prefixed_colon,_A,indention=_A);self.states.append(self.expect_block_mapping_key);self.expect_node(mapping=_A) def check_empty_sequence(self):return isinstance(self.event,SequenceStartEvent)and bool(self.events)and isinstance(self.events[0],SequenceEndEvent) def check_empty_mapping(self):return isinstance(self.event,MappingStartEvent)and bool(self.events)and isinstance(self.events[0],MappingEndEvent) def check_empty_document(self): if not isinstance(self.event,DocumentStartEvent)or not self.events:return _C event=self.events[0];return isinstance(event,ScalarEvent)and event.anchor is _B and event.tag is _B and event.implicit and event.value=='' def check_simple_key(self): length=0 if isinstance(self.event,NodeEvent)and self.event.anchor is not _B: if self.prepared_anchor is _B:self.prepared_anchor=self.prepare_anchor(self.event.anchor) length+=len(self.prepared_anchor) if isinstance(self.event,(ScalarEvent,CollectionStartEvent))and self.event.tag is not _B: if self.prepared_tag is _B:self.prepared_tag=self.prepare_tag(self.event.tag) length+=len(self.prepared_tag) if isinstance(self.event,ScalarEvent): if self.analysis is _B:self.analysis=self.analyze_scalar(self.event.value) length+=len(self.analysis.scalar) return length': if not self.flow_level and not self.simple_key_context and self.analysis.allow_block:return self.event.style if not self.event.style and self.analysis.allow_double_quoted: if _J in self.event.value or _E in self.event.value:return _H if not self.event.style or self.event.style==_J: if self.analysis.allow_single_quoted and not(self.simple_key_context and self.analysis.multiline):return _J return _H def process_scalar(self): if self.analysis is _B:self.analysis=self.analyze_scalar(self.event.value) if self.style is _B:self.style=self.choose_scalar_style() split=not self.simple_key_context if self.sequence_context and not self.flow_level:self.write_indent() if self.style==_H:self.write_double_quoted(self.analysis.scalar,split) elif self.style==_J:self.write_single_quoted(self.analysis.scalar,split) elif self.style=='>':self.write_folded(self.analysis.scalar) elif self.style=='|':self.write_literal(self.analysis.scalar,self.event.comment) else:self.write_plain(self.analysis.scalar,split) self.analysis=_B;self.style=_B if self.event.comment:self.write_post_comment(self.event) def prepare_version(self,version): major,minor=version if major!=1:raise EmitterError('unsupported YAML version: %d.%d'%(major,minor)) return'%d.%d'%(major,minor) def prepare_tag_handle(self,handle): 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('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=_O if self.dumper: version=getattr(self.dumper,_P,(1,2)) if version is _B or version>=(1,2):ch_set+='#' while end=(1,2):ch_set+='#' while end'%suffix_text def prepare_anchor(self,anchor): if not anchor:raise EmitterError('anchor must not be empty') for ch in anchor: if not check_anchorname_char(ch):raise EmitterError('invalid character %r in the anchor: %r'%(utf8(ch),utf8(anchor))) return anchor def analyze_scalar(self,scalar): 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('---')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\'"%@`':flow_indicators=_A;block_indicators=_A if ch in'?:': if self.serializer.use_version==(1,1):flow_indicators=_A elif len(scalar)==1:flow_indicators=_A if followed_by_whitespace:block_indicators=_A if ch=='-'and followed_by_whitespace:flow_indicators=_A;block_indicators=_A else: if ch in',[]{}':flow_indicators=_A if ch==_I and self.serializer.use_version==(1,1):flow_indicators=_A if ch==':': if followed_by_whitespace:flow_indicators=_A;block_indicators=_A 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=='\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: if index==0:leading_space=_A if index==len(scalar)-1:trailing_space=_A if previous_break:break_space=_A previous_space=_A;previous_break=_C elif ch in _F: if index==0:leading_break=_A if index==len(scalar)-1:trailing_break=_A if previous_space:space_break=_A previous_space=_C;previous_break=_A else:previous_space=_C;previous_break=_C index+=1;preceeded_by_whitespace=ch in A;followed_by_whitespace=index+1>=len(scalar)or scalar[index+1]in A allow_flow_plain=_A;allow_block_plain=_A;allow_single_quoted=_A;allow_double_quoted=_A;allow_block=_A if leading_space or leading_break or trailing_space or trailing_break:allow_flow_plain=allow_block_plain=_C if trailing_space:allow_block=_C if break_space:allow_flow_plain=allow_block_plain=allow_single_quoted=_C if special_characters:allow_flow_plain=allow_block_plain=allow_single_quoted=allow_block=_C elif space_break: allow_flow_plain=allow_block_plain=allow_single_quoted=_C if not self.allow_space_break:allow_block=_C if line_breaks:allow_flow_plain=allow_block_plain=_C if flow_indicators:allow_flow_plain=_C if block_indicators:allow_block_plain=_C return ScalarAnalysis(scalar=scalar,empty=_C,multiline=line_breaks,allow_flow_plain=allow_flow_plain,allow_block_plain=allow_block_plain,allow_single_quoted=allow_single_quoted,allow_double_quoted=allow_double_quoted,allow_block=allow_block) 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(_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 else:data=_D+indicator self.whitespace=whitespace;self.indention=self.indention and indention;self.column+=len(data);self.open_ended=_C if bool(self.encoding):data=data.encode(self.encoding) self.stream.write(data) def write_indent(self): indent=self.indent or 0 if not self.indention or self.column>indent or self.column==indent and not self.whitespace: if bool(self.no_newline):self.no_newline=_C else:self.write_line_break() if self.columnself.best_width and split and start!=0 and end!=len(text):self.write_indent() else: data=text[start:end];self.column+=len(data) if bool(self.encoding):data=data.encode(self.encoding) self.stream.write(data) start=end elif breaks: if ch is _B or ch not in _F: if text[start]==_E:self.write_line_break() for br in text[start:end]: 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 _N or ch==_J: if start=end)and self.column+(end-start)>self.best_width and split: data=text[start:end]+_K if start-1:break if pos>0:indent=self.best_sequence_indent if text[-1]not in _F:indicator='-' elif len(text)==1 or text[-2]in _F:indicator='+' hints+=indicator;return hints,indent,indicator def write_folded(self,text): hints,_indent,_indicator=self.determine_block_hints(text);self.write_indicator('>'+hints,_A) if _indicator=='+':self.open_ended=_A self.write_line_break();leading_space=_A;spaces=_C;breaks=_A;start=end=0 while end<=len(text): ch=_B if endself.best_width:self.write_indent() else: data=text[start:end];self.column+=len(data) if bool(self.encoding):data=data.encode(self.encoding) self.stream.write(data) start=end elif ch is _B or ch in' \n\x85\u2028\u2029\x07': data=text[start:end];self.column+=len(data) if bool(self.encoding):data=data.encode(self.encoding) self.stream.write(data) if ch=='\x07': if endself.best_width and split:self.write_indent();self.whitespace=_C;self.indention=_C else: data=text[start:end];self.column+=len(data) if self.encoding:data=data.encode(self.encoding) self.stream.write(data) start=end elif breaks: if ch not in _F: if text[start]==_E:self.write_line_break() for br in text[start:end]: 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 _N: data=text[start:end];self.column+=len(data) if self.encoding:data=data.encode(self.encoding) try:self.stream.write(data) except:sys.stdout.write(repr(data)+_E);raise start=end if ch is not _B:spaces=ch==_D;breaks=ch in _F end+=1 def write_comment(self,comment,pre=_C): value=comment.value if not pre and value[-1]==_E:value=value[:-1] try: col=comment.start_mark.column if comment.value and comment.value.startswith(_E):col=self.column elif col0 and A.buffer[B-1]not in I: B-=1 if A.pointer-B>F/2-1:D=J;B+=5;break G='';C=A.pointer while CF/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=_D%(A.name,A.line+1,A.column+1) if B is not _A:C+=':\n'+B return C class CommentMark: __slots__='column', def __init__(A,column):A.column=column class YAMLError(Exception):0 class MarkedYAMLError(YAMLError): 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 def __str__(A): B=[] if A.context is not _A:B.append(A.context) if A.context_mark is not _A and(A.problem is _A or A.problem_mark is _A or A.context_mark.name!=A.problem_mark.name or A.context_mark.line!=A.problem_mark.line or A.context_mark.column!=A.problem_mark.column):B.append(str(A.context_mark)) if A.problem is not _A:B.append(A.problem) if A.problem_mark is not _A:B.append(str(A.problem_mark)) if A.note is not _A and A.note:C=textwrap.dedent(A.note);B.append(C) return _B.join(B) class YAMLStreamError(Exception):0 class YAMLWarning(Warning):0 class MarkedYAMLWarning(YAMLWarning): 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 def __str__(A): B=[] if A.context is not _A:B.append(A.context) if A.context_mark is not _A and(A.problem is _A or A.problem_mark is _A or A.context_mark.name!=A.problem_mark.name or A.context_mark.line!=A.problem_mark.line or A.context_mark.column!=A.problem_mark.column):B.append(str(A.context_mark)) if A.problem is not _A:B.append(A.problem) if A.problem_mark is not _A:B.append(str(A.problem_mark)) if A.note is not _A and A.note:C=textwrap.dedent(A.note);B.append(C) if A.warn is not _A and A.warn:D=textwrap.dedent(A.warn);B.append(D) 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('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('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 def __str__(A): B=[] if A.context is not _A:B.append(A.context) if A.context_mark is not _A and(A.problem is _A or A.problem_mark is _A or A.context_mark.name!=A.problem_mark.name or A.context_mark.line!=A.problem_mark.line or A.context_mark.column!=A.problem_mark.column):B.append(str(A.context_mark)) if A.problem is not _A:B.append(A.problem) if A.problem_mark is not _A:B.append(str(A.problem_mark)) if A.note is not _A and A.note:C=textwrap.dedent(A.note);B.append(C) if A.warn is not _A and A.warn:D=textwrap.dedent(A.warn);B.append(D) return _B.join(B)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/events.py0000644000000000000000000000442515156326620022753 0ustar00pbrochadpbrochad_A=None if False:from typing import Any,Dict,Optional,List def CommentCheck():0 class Event: __slots__='start_mark','end_mark','comment' def __init__(A,start_mark=_A,end_mark=_A,comment=CommentCheck): B=comment;A.start_mark=start_mark;A.end_mark=end_mark if B is CommentCheck:B=_A A.comment=B def __repr__(A): C=[B for B in['anchor','tag','implicit','value','flow_style','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__='anchor', 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__='tag','implicit','flow_style','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): __slots__='encoding', 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__='explicit','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__='explicit', 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__='tag','implicit','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__=() class MappingStartEvent(CollectionStartEvent):__slots__=() class MappingEndEvent(CollectionEndEvent):__slots__=()././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/loader.py0000644000000000000000000000361215156326620022712 0ustar00pbrochadpbrochadfrom __future__ import absolute_import _A=None from.reader import Reader from.scanner import Scanner,RoundTripScanner from.parser import Parser,RoundTripParser from.composer import Composer from.constructor import BaseConstructor,SafeConstructor,Constructor,RoundTripConstructor from.resolver import VersionedResolver if False:from typing import Any,Dict,List,Union,Optional;from.compat import StreamTextType,VersionType __all__=['BaseLoader','SafeLoader','Loader','RoundTripLoader'] class BaseLoader(Reader,Scanner,Parser,Composer,BaseConstructor,VersionedResolver): def __init__(A,stream,version=_A,preserve_quotes=_A):Reader.__init__(A,stream,loader=A);Scanner.__init__(A,loader=A);Parser.__init__(A,loader=A);Composer.__init__(A,loader=A);BaseConstructor.__init__(A,loader=A);VersionedResolver.__init__(A,version,loader=A) class SafeLoader(Reader,Scanner,Parser,Composer,SafeConstructor,VersionedResolver): def __init__(A,stream,version=_A,preserve_quotes=_A):Reader.__init__(A,stream,loader=A);Scanner.__init__(A,loader=A);Parser.__init__(A,loader=A);Composer.__init__(A,loader=A);SafeConstructor.__init__(A,loader=A);VersionedResolver.__init__(A,version,loader=A) class Loader(Reader,Scanner,Parser,Composer,Constructor,VersionedResolver): def __init__(A,stream,version=_A,preserve_quotes=_A):Reader.__init__(A,stream,loader=A);Scanner.__init__(A,loader=A);Parser.__init__(A,loader=A);Composer.__init__(A,loader=A);Constructor.__init__(A,loader=A);VersionedResolver.__init__(A,version,loader=A) class RoundTripLoader(Reader,RoundTripScanner,RoundTripParser,Composer,RoundTripConstructor,VersionedResolver): def __init__(A,stream,version=_A,preserve_quotes=_A):Reader.__init__(A,stream,loader=A);RoundTripScanner.__init__(A,loader=A);RoundTripParser.__init__(A,loader=A);Composer.__init__(A,loader=A);RoundTripConstructor.__init__(A,preserve_quotes=preserve_quotes,loader=A);VersionedResolver.__init__(A,version,loader=A)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/main.py0000644000000000000000000006730515156326620022401 0ustar00pbrochadpbrochadfrom __future__ import absolute_import,unicode_literals,print_function _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' _E='rt' _D='_' _C=True _B=False _A=None import sys,os,warnings,glob from importlib import import_module import dynaconf.vendor.ruamel as ruamel from.error import UnsafeLoaderWarning,YAMLError from.tokens import* from.events import* from.nodes import* from.loader import BaseLoader,SafeLoader,Loader,RoundTripLoader from.dumper import BaseDumper,SafeDumper,Dumper,RoundTripDumper from.compat import StringIO,BytesIO,with_metaclass,PY3,nprint from.resolver import VersionedResolver,Resolver from.representer import BaseRepresenter,SafeRepresenter,Representer,RoundTripRepresenter from.constructor import BaseConstructor,SafeConstructor,Constructor,RoundTripConstructor from.loader import Loader as UnsafeLoader if _B: from typing import List,Set,Dict,Union,Any,Callable,Optional,Text;from.compat import StreamType,StreamTextType,VersionType if PY3:from pathlib import Path else:Path=Any try:from _ruamel_yaml import CParser,CEmitter except:CParser=CEmitter=_A enforce=object() class YAML: def __init__(self,_kw=enforce,typ=_A,pure=_B,output=_A,plug_ins=_A): if _kw is not enforce:raise TypeError(_H.format(self.__class__.__name__,_kw)) self.typ=[_E]if typ is _A else typ if isinstance(typ,list)else[typ];self.pure=pure;self._output=output;self._context_manager=_A;self.plug_ins=[] for pu in([]if plug_ins is _A else plug_ins)+self.official_plug_ins():file_name=pu.replace(os.sep,'.');self.plug_ins.append(import_module(file_name)) 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'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='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,'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): try:return self._reader except AttributeError:self._reader=self.Reader(_A,loader=self);return self._reader @property def scanner(self): try:return self._scanner except AttributeError:self._scanner=self.Scanner(loader=self);return self._scanner @property def parser(self): 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,_I,_A)is _A:return else:setattr(self,attr,CParser(self._stream)) return getattr(self,attr) @property def composer(self): attr=_D+sys._getframe().f_code.co_name if not hasattr(self,attr):setattr(self,attr,self.Composer(loader=self)) return getattr(self,attr) @property def constructor(self): attr=_D+sys._getframe().f_code.co_name if not hasattr(self,attr):cnst=self.Constructor(preserve_quotes=self.preserve_quotes,loader=self);cnst.allow_duplicate_keys=self.allow_duplicate_keys;setattr(self,attr,cnst) return getattr(self,attr) @property def resolver(self): attr=_D+sys._getframe().f_code.co_name if not hasattr(self,attr):setattr(self,attr,self.Resolver(version=self.version,loader=self)) return getattr(self,attr) @property def emitter(self): attr=_D+sys._getframe().f_code.co_name if not hasattr(self,attr): if self.Emitter is not CEmitter: _emitter=self.Emitter(_A,canonical=self.canonical,indent=self.old_indent,width=self.width,allow_unicode=self.allow_unicode,line_break=self.line_break,prefix_colon=self.prefix_colon,brace_single_entry_mapping_in_flow_sequence=self.brace_single_entry_mapping_in_flow_sequence,dumper=self);setattr(self,attr,_emitter) if self.map_indent is not _A:_emitter.best_map_indent=self.map_indent if self.sequence_indent is not _A:_emitter.best_sequence_indent=self.sequence_indent if self.sequence_dash_offset is not _A:_emitter.sequence_dash_offset=self.sequence_dash_offset 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,_I,_A)is _A:return return return getattr(self,attr) @property def serializer(self): attr=_D+sys._getframe().f_code.co_name if not hasattr(self,attr):setattr(self,attr,self.Serializer(encoding=self.encoding,explicit_start=self.explicit_start,explicit_end=self.explicit_end,version=self.version,tags=self.tags,dumper=self)) return getattr(self,attr) @property def representer(self): attr=_D+sys._getframe().f_code.co_name if not hasattr(self,attr): repres=self.Representer(default_style=self.default_style,default_flow_style=self.default_flow_style,dumper=self) if self.sort_base_mapping_type_on_output is not _A:repres.sort_base_mapping_type_on_output=self.sort_base_mapping_type_on_output setattr(self,attr,repres) return getattr(self,attr) def load(self,stream): 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() finally: parser.dispose() try:self._reader.reset_reader() except AttributeError:pass try:self._scanner.reset_scanner() 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,'read')and hasattr(stream,_F): with stream.open('r')as fp: for d in self.load_all(fp,_kw=enforce):yield d return constructor,parser=self.get_constructor_parser(stream) try: while constructor.check_data():yield constructor.get_data() finally: parser.dispose() try:self._reader.reset_reader() except AttributeError:pass try:self._scanner.reset_scanner() except AttributeError:pass def get_constructor_parser(self,stream): if self.Parser is not CParser: if self.Reader is _A:self.Reader=ruamel.yaml.reader.Reader if self.Scanner is _A:self.Scanner=ruamel.yaml.scanner.Scanner self.reader.stream=stream elif self.Reader is not _A: if self.Scanner is _A:self.Scanner=ruamel.yaml.scanner.Scanner self.Parser=ruamel.yaml.parser.Parser;self.reader.stream=stream elif self.Scanner is not _A: if self.Reader is _A:self.Reader=ruamel.yaml.reader.Reader self.Parser=ruamel.yaml.parser.Parser;self.reader.stream=stream else: rslvr=self.Resolver class XLoader(self.Parser,self.Constructor,rslvr): def __init__(selfx,stream,version=self.version,preserve_quotes=_A):CParser.__init__(selfx,stream);selfx._parser=selfx._composer=selfx;self.Constructor.__init__(selfx,loader=selfx);selfx.allow_duplicate_keys=self.allow_duplicate_keys;rslvr.__init__(selfx,version=version,loadumper=selfx) self._stream=stream;loader=XLoader(stream);return loader,loader return self.constructor,self.parser def dump(self,data,stream=_A,_kw=enforce,transform=_A): if self._context_manager: if not self._output:raise TypeError('Missing output stream while dumping from context manager') if _kw is not enforce:raise TypeError('{}.dump() takes one positional argument but at least two were given ({!r})'.format(self.__class__.__name__,_kw)) if transform is not _A:raise TypeError('{}.dump() in the context manager cannot have transform keyword '.format(self.__class__.__name__)) self._context_manager.dump(data) else: if stream is _A:raise TypeError('Need a stream argument when not dumping from context manager') 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(_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,'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(_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: fstream=stream if self.encoding is _A:stream=StringIO() else:stream=BytesIO() serializer,representer,emitter=self.get_serializer_representer_emitter(stream,tlca) try: self.serializer.open() for data in documents: try:self.representer.represent(data) except AttributeError:raise self.serializer.close() finally: try:self.emitter.dispose() except AttributeError:raise delattr(self,_K);delattr(self,_L) if transform: val=stream.getvalue() if self.encoding:val=val.decode(self.encoding) if fstream is _A:transform(val) else:fstream.write(transform(val)) def get_serializer_representer_emitter(self,stream,tlca): if self.Emitter is not CEmitter: if self.Serializer is _A:self.Serializer=ruamel.yaml.serializer.Serializer 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 if self.Serializer is not _A: 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'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 def map(self,**kw): 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) 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__) try:self.representer.add_representer(cls,cls.to_yaml) except AttributeError: def t_y(representer,data):return representer.represent_yaml_object(tag,data,cls,flow_style=representer.default_flow_style) self.representer.add_representer(cls,t_y) try:self.constructor.add_constructor(tag,cls.from_yaml) except AttributeError: def f_y(constructor,node):return constructor.construct_yaml_object(node,cls) self.constructor.add_constructor(tag,f_y) return cls def parse(self,stream): _,parser=self.get_constructor_parser(stream) try: while parser.check_event():yield parser.get_event() finally: parser.dispose() try:self._reader.reset_reader() except AttributeError:pass try:self._scanner.reset_scanner() except AttributeError:pass def __enter__(self):self._context_manager=YAMLContextManager(self);return self def __exit__(self,typ,value,traceback): 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 if sequence is not _A:self.sequence_indent=sequence if offset is not _A:self.sequence_dash_offset=offset @property def indent(self):return self._indent @indent.setter def indent(self,val):self.old_indent=val @property def block_seq_indent(self):return self.sequence_dash_offset @block_seq_indent.setter def block_seq_indent(self,val):self.sequence_dash_offset=val def compact(self,seq_seq=_A,seq_map=_A):self.compact_seq_seq=seq_seq;self.compact_seq_map=seq_map 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,'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() else:self._output=BytesIO() def teardown_output(self): if self._output_inited:self._yaml.serializer.close() else:return try:self._yaml.emitter.dispose() except AttributeError:raise try:delattr(self._yaml,_K);delattr(self._yaml,_L) except AttributeError:raise if self._transform: val=self._output.getvalue() if self._yaml.encoding:val=val.decode(self._yaml.encoding) if self._fstream is _A:self._transform(val) else:self._fstream.write(self._transform(val));self._fstream.flush();self._output=self._fstream if self._output_path is not _A:self._output.close() def init_output(self,first_data): if self._yaml.top_level_colon_align is _C:tlca=max([len(str(x))for x in first_data]) else:tlca=self._yaml.top_level_colon_align self._yaml.get_serializer_representer_emitter(self._output,tlca);self._yaml.serializer.open();self._output_inited=_C def dump(self,data): if not self._output_inited:self.init_output(data) try:self._yaml.representer.represent(data) except AttributeError:raise def yaml_object(yml): def yo_deco(cls): tag=getattr(cls,_G,'!'+cls.__name__) try:yml.representer.add_representer(cls,cls.to_yaml) except AttributeError: def t_y(representer,data):return representer.represent_yaml_object(tag,data,cls,flow_style=representer.default_flow_style) yml.representer.add_representer(cls,t_y) try:yml.constructor.add_constructor(tag,cls.from_yaml) except AttributeError: def f_y(constructor,node):return constructor.construct_yaml_object(node,cls) yml.constructor.add_constructor(tag,f_y) return cls return yo_deco def scan(stream,Loader=Loader): loader=Loader(stream) try: while loader.scanner.check_token():yield loader.scanner.get_token() finally:loader._parser.dispose() def parse(stream,Loader=Loader): loader=Loader(stream) try: while loader._parser.check_event():yield loader._parser.get_event() finally:loader._parser.dispose() def compose(stream,Loader=Loader): loader=Loader(stream) try:return loader.get_single_node() finally:loader.dispose() def compose_all(stream,Loader=Loader): loader=Loader(stream) try: while loader.check_node():yield loader._composer.get_node() finally:loader._parser.dispose() def load(stream,Loader=_A,version=_A,preserve_quotes=_A): if Loader is _A:warnings.warn(UnsafeLoaderWarning.text,UnsafeLoaderWarning,stacklevel=2);Loader=UnsafeLoader loader=Loader(stream,version,preserve_quotes=preserve_quotes) try:return loader._constructor.get_single_data() finally: loader._parser.dispose() try:loader._reader.reset_reader() except AttributeError:pass try:loader._scanner.reset_scanner() except AttributeError:pass def load_all(stream,Loader=_A,version=_A,preserve_quotes=_A): if Loader is _A:warnings.warn(UnsafeLoaderWarning.text,UnsafeLoaderWarning,stacklevel=2);Loader=UnsafeLoader loader=Loader(stream,version,preserve_quotes=preserve_quotes) try: while loader._constructor.check_data():yield loader._constructor.get_data() finally: loader._parser.dispose() try:loader._reader.reset_reader() except AttributeError:pass try:loader._scanner.reset_scanner() except AttributeError:pass def safe_load(stream,version=_A):return load(stream,SafeLoader,version) def safe_load_all(stream,version=_A):return load_all(stream,SafeLoader,version) def round_trip_load(stream,version=_A,preserve_quotes=_A):return load(stream,RoundTripLoader,version,preserve_quotes=preserve_quotes) def round_trip_load_all(stream,version=_A,preserve_quotes=_A):return load_all(stream,RoundTripLoader,version,preserve_quotes=preserve_quotes) def emit(events,stream=_A,Dumper=Dumper,canonical=_A,indent=_A,width=_A,allow_unicode=_A,line_break=_A): getvalue=_A if stream is _A:stream=StringIO();getvalue=stream.getvalue dumper=Dumper(stream,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break) try: for event in events:dumper.emit(event) finally: try:dumper._emitter.dispose() except AttributeError:raise;dumper.dispose() if getvalue is not _A:return getvalue() 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: if encoding is _A:stream=StringIO() else:stream=BytesIO() getvalue=stream.getvalue dumper=Dumper(stream,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break,encoding=encoding,version=version,tags=tags,explicit_start=explicit_start,explicit_end=explicit_end) try: dumper._serializer.open() for node in nodes:dumper.serialize(node) dumper._serializer.close() finally: try:dumper._emitter.dispose() except AttributeError:raise;dumper.dispose() if getvalue is not _A:return getvalue() def serialize(node,stream=_A,Dumper=Dumper,**kwds):return serialize_all([node],stream,Dumper=Dumper,**kwds) def dump_all(documents,stream=_A,Dumper=Dumper,default_style=_A,default_flow_style=_A,canonical=_A,indent=_A,width=_A,allow_unicode=_A,line_break=_A,encoding=enc,explicit_start=_A,explicit_end=_A,version=_A,tags=_A,block_seq_indent=_A,top_level_colon_align=_A,prefix_colon=_A): getvalue=_A if top_level_colon_align is _C:top_level_colon_align=max([len(str(x))for x in documents[0]]) if stream is _A: if encoding is _A:stream=StringIO() else:stream=BytesIO() getvalue=stream.getvalue dumper=Dumper(stream,default_style=default_style,default_flow_style=default_flow_style,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break,encoding=encoding,explicit_start=explicit_start,explicit_end=explicit_end,version=version,tags=tags,block_seq_indent=block_seq_indent,top_level_colon_align=top_level_colon_align,prefix_colon=prefix_colon) try: dumper._serializer.open() for data in documents: try:dumper._representer.represent(data) except AttributeError:raise dumper._serializer.close() finally: try:dumper._emitter.dispose() except AttributeError:raise;dumper.dispose() if getvalue is not _A:return getvalue() def dump(data,stream=_A,Dumper=Dumper,default_style=_A,default_flow_style=_A,canonical=_A,indent=_A,width=_A,allow_unicode=_A,line_break=_A,encoding=enc,explicit_start=_A,explicit_end=_A,version=_A,tags=_A,block_seq_indent=_A):return dump_all([data],stream,Dumper=Dumper,default_style=default_style,default_flow_style=default_flow_style,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break,encoding=encoding,explicit_start=explicit_start,explicit_end=explicit_end,version=version,tags=tags,block_seq_indent=block_seq_indent) def safe_dump_all(documents,stream=_A,**kwds):return dump_all(documents,stream,Dumper=SafeDumper,**kwds) def safe_dump(data,stream=_A,**kwds):return dump_all([data],stream,Dumper=SafeDumper,**kwds) def round_trip_dump(data,stream=_A,Dumper=RoundTripDumper,default_style=_A,default_flow_style=_A,canonical=_A,indent=_A,width=_A,allow_unicode=_A,line_break=_A,encoding=enc,explicit_start=_A,explicit_end=_A,version=_A,tags=_A,block_seq_indent=_A,top_level_colon_align=_A,prefix_colon=_A):allow_unicode=_C if allow_unicode is _A else allow_unicode;return dump_all([data],stream,Dumper=Dumper,default_style=default_style,default_flow_style=default_flow_style,canonical=canonical,indent=indent,width=width,allow_unicode=allow_unicode,line_break=line_break,encoding=encoding,explicit_start=explicit_start,explicit_end=explicit_end,version=version,tags=tags,block_seq_indent=block_seq_indent,top_level_colon_align=top_level_colon_align,prefix_colon=prefix_colon) def add_implicit_resolver(tag,regexp,first=_A,Loader=_A,Dumper=_A,resolver=Resolver): A='add_implicit_resolver' if Loader is _A and Dumper is _A:resolver.add_implicit_resolver(tag,regexp,first);return if Loader: if hasattr(Loader,A):Loader.add_implicit_resolver(tag,regexp,first) elif issubclass(Loader,(BaseLoader,SafeLoader,ruamel.yaml.loader.Loader,RoundTripLoader)):Resolver.add_implicit_resolver(tag,regexp,first) else:raise NotImplementedError if Dumper: if hasattr(Dumper,A):Dumper.add_implicit_resolver(tag,regexp,first) elif issubclass(Dumper,(BaseDumper,SafeDumper,ruamel.yaml.dumper.Dumper,RoundTripDumper)):Resolver.add_implicit_resolver(tag,regexp,first) else:raise NotImplementedError def add_path_resolver(tag,path,kind=_A,Loader=_A,Dumper=_A,resolver=Resolver): A='add_path_resolver' if Loader is _A and Dumper is _A:resolver.add_path_resolver(tag,path,kind);return if Loader: if hasattr(Loader,A):Loader.add_path_resolver(tag,path,kind) elif issubclass(Loader,(BaseLoader,SafeLoader,ruamel.yaml.loader.Loader,RoundTripLoader)):Resolver.add_path_resolver(tag,path,kind) else:raise NotImplementedError if Dumper: if hasattr(Dumper,A):Dumper.add_path_resolver(tag,path,kind) elif issubclass(Dumper,(BaseDumper,SafeDumper,ruamel.yaml.dumper.Dumper,RoundTripDumper)):Resolver.add_path_resolver(tag,path,kind) else:raise NotImplementedError def add_constructor(tag,object_constructor,Loader=_A,constructor=Constructor): if Loader is _A:constructor.add_constructor(tag,object_constructor) else: if hasattr(Loader,'add_constructor'):Loader.add_constructor(tag,object_constructor);return if issubclass(Loader,BaseLoader):BaseConstructor.add_constructor(tag,object_constructor) elif issubclass(Loader,SafeLoader):SafeConstructor.add_constructor(tag,object_constructor) elif issubclass(Loader,Loader):Constructor.add_constructor(tag,object_constructor) elif issubclass(Loader,RoundTripLoader):RoundTripConstructor.add_constructor(tag,object_constructor) else:raise NotImplementedError def add_multi_constructor(tag_prefix,multi_constructor,Loader=_A,constructor=Constructor): if Loader is _A:constructor.add_multi_constructor(tag_prefix,multi_constructor) else: if _B and hasattr(Loader,'add_multi_constructor'):Loader.add_multi_constructor(tag_prefix,constructor);return if issubclass(Loader,BaseLoader):BaseConstructor.add_multi_constructor(tag_prefix,multi_constructor) elif issubclass(Loader,SafeLoader):SafeConstructor.add_multi_constructor(tag_prefix,multi_constructor) elif issubclass(Loader,ruamel.yaml.loader.Loader):Constructor.add_multi_constructor(tag_prefix,multi_constructor) elif issubclass(Loader,RoundTripLoader):RoundTripConstructor.add_multi_constructor(tag_prefix,multi_constructor) else:raise NotImplementedError def add_representer(data_type,object_representer,Dumper=_A,representer=Representer): if Dumper is _A:representer.add_representer(data_type,object_representer) else: if hasattr(Dumper,'add_representer'):Dumper.add_representer(data_type,object_representer);return if issubclass(Dumper,BaseDumper):BaseRepresenter.add_representer(data_type,object_representer) elif issubclass(Dumper,SafeDumper):SafeRepresenter.add_representer(data_type,object_representer) elif issubclass(Dumper,Dumper):Representer.add_representer(data_type,object_representer) elif issubclass(Dumper,RoundTripDumper):RoundTripRepresenter.add_representer(data_type,object_representer) else:raise NotImplementedError def add_multi_representer(data_type,multi_representer,Dumper=_A,representer=Representer): if Dumper is _A:representer.add_multi_representer(data_type,multi_representer) else: if hasattr(Dumper,'add_multi_representer'):Dumper.add_multi_representer(data_type,multi_representer);return if issubclass(Dumper,BaseDumper):BaseRepresenter.add_multi_representer(data_type,multi_representer) elif issubclass(Dumper,SafeDumper):SafeRepresenter.add_multi_representer(data_type,multi_representer) elif issubclass(Dumper,Dumper):Representer.add_multi_representer(data_type,multi_representer) elif issubclass(Dumper,RoundTripDumper):RoundTripRepresenter.add_multi_representer(data_type,multi_representer) else:raise NotImplementedError class YAMLObjectMetaclass(type): def __init__(cls,name,bases,kwds): super(YAMLObjectMetaclass,cls).__init__(name,bases,kwds) if _G in kwds and kwds[_G]is not _A:cls.yaml_constructor.add_constructor(cls.yaml_tag,cls.from_yaml);cls.yaml_representer.add_representer(cls,cls.to_yaml) class YAMLObject(with_metaclass(YAMLObjectMetaclass)): __slots__=();yaml_constructor=Constructor;yaml_representer=Representer;yaml_tag=_A;yaml_flow_style=_A @classmethod def from_yaml(cls,constructor,node):return constructor.construct_yaml_object(node,cls) @classmethod def to_yaml(cls,representer,data):return representer.represent_yaml_object(cls.yaml_tag,data,cls,flow_style=cls.yaml_flow_style)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/nodes.py0000644000000000000000000000347115156326620022557 0ustar00pbrochadpbrochadfrom __future__ import print_function _A=None import sys from.compat import string_types if False:from typing import Dict,Any,Text class Node: __slots__='tag','value','start_mark','end_mark','comment','anchor' 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): 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(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(E.format(D*B,A.comment)) for C in A.value: if isinstance(C,tuple): 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): __slots__='style',;id='scalar' def __init__(A,tag,value,start_mark=_A,end_mark=_A,style=_A,comment=_A,anchor=_A):Node.__init__(A,tag,value,start_mark,end_mark,comment=comment,anchor=anchor);A.style=style class CollectionNode(Node): __slots__='flow_style', def __init__(A,tag,value,start_mark=_A,end_mark=_A,flow_style=_A,comment=_A,anchor=_A):Node.__init__(A,tag,value,start_mark,end_mark,comment=comment);A.flow_style=flow_style;A.anchor=anchor class SequenceNode(CollectionNode):__slots__=();id='sequence' class MappingNode(CollectionNode): __slots__='merge',;id='mapping' def __init__(A,tag,value,start_mark=_A,end_mark=_A,flow_style=_A,comment=_A,anchor=_A):CollectionNode.__init__(A,tag,value,start_mark,end_mark,flow_style,comment,anchor);A.merge=_A././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/parser.py0000644000000000000000000004110615156326620022740 0ustar00pbrochadpbrochadfrom __future__ import absolute_import _D='expected , but found %r' _C=True _B=False _A=None from.error import MarkedYAMLError from.tokens import* from.events import* from.scanner import Scanner,RoundTripScanner,ScannerError from.compat import utf8,nprint,nprintf if _B:from typing import Any,Dict,Optional,List __all__=['Parser','RoundTripParser','ParserError'] class ParserError(MarkedYAMLError):0 class Parser: 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 self.reset_parser() def reset_parser(self):self.current_event=_A;self.tag_handles={};self.states=[];self.marks=[];self.state=self.parse_stream_start def dispose(self):self.reset_parser() @property def scanner(self): if hasattr(self.loader,'typ'):return self.loader.scanner return self.loader._scanner @property def resolver(self): if hasattr(self.loader,'typ'):return self.loader.resolver return self.loader._resolver def check_event(self,*choices): if self.current_event is _A: if self.state:self.current_event=self.state() if self.current_event is not _A: if not choices:return _C for choice in choices: if isinstance(self.current_event,choice):return _C return _B def peek_event(self): if self.current_event is _A: if self.state:self.current_event=self.state() return self.current_event def get_event(self): if self.current_event is _A: if self.state:self.current_event=self.state() value=self.current_event;self.current_event=_A;return value def parse_stream_start(self):token=self.scanner.get_token();token.move_comment(self.scanner.peek_token());event=StreamStartEvent(token.start_mark,token.end_mark,encoding=token.encoding);self.state=self.parse_implicit_document_start;return event def parse_implicit_document_start(self): if not self.scanner.check_token(DirectiveToken,DocumentStartToken,StreamEndToken):self.tag_handles=self.DEFAULT_TAGS;token=self.scanner.peek_token();start_mark=end_mark=token.start_mark;event=DocumentStartEvent(start_mark,end_mark,explicit=_B);self.states.append(self.parse_document_end);self.state=self.parse_block_node;return event else:return self.parse_document_start() def parse_document_start(self): while self.scanner.check_token(DocumentEndToken):self.scanner.get_token() if not self.scanner.check_token(StreamEndToken): token=self.scanner.peek_token();start_mark=token.start_mark;version,tags=self.process_directives() if not self.scanner.check_token(DocumentStartToken):raise ParserError(_A,_A,"expected '', but found %r"%self.scanner.peek_token().id,self.scanner.peek_token().start_mark) token=self.scanner.get_token();end_mark=token.end_mark;event=DocumentStartEvent(start_mark,end_mark,explicit=_C,version=version,tags=tags);self.states.append(self.parse_document_end);self.state=self.parse_document_content else:token=self.scanner.get_token();event=StreamEndEvent(token.start_mark,token.end_mark,comment=token.comment);assert not self.states;assert not self.marks;self.state=_A return event def parse_document_end(self): token=self.scanner.peek_token();start_mark=end_mark=token.start_mark;explicit=_B if self.scanner.check_token(DocumentEndToken):token=self.scanner.get_token();end_mark=token.end_mark;explicit=_C event=DocumentEndEvent(start_mark,end_mark,explicit=explicit) if self.resolver.processing_version==(1,1):self.state=self.parse_document_start else:self.state=self.parse_implicit_document_start return event def parse_document_content(self): if self.scanner.check_token(DirectiveToken,DocumentStartToken,DocumentEndToken,StreamEndToken):event=self.process_empty_scalar(self.scanner.peek_token().start_mark);self.state=self.states.pop();return event else:return self.parse_block_node() def process_directives(self): yaml_version=_A;self.tag_handles={} while self.scanner.check_token(DirectiveToken): token=self.scanner.get_token() if token.name=='YAML': if yaml_version is not _A:raise ParserError(_A,_A,'found duplicate YAML directive',token.start_mark) major,minor=token.value if major!=1:raise ParserError(_A,_A,'found incompatible YAML document (version 1.* is required)',token.start_mark) yaml_version=token.value elif token.name=='TAG': handle,prefix=token.value if handle in self.tag_handles:raise ParserError(_A,_A,'duplicate tag handle %r'%utf8(handle),token.start_mark) self.tag_handles[handle]=prefix if bool(self.tag_handles):value=yaml_version,self.tag_handles.copy() else:value=yaml_version,_A if self.loader is not _A and hasattr(self.loader,'tags'): self.loader.version=yaml_version if self.loader.tags is _A:self.loader.tags={} for k in self.tag_handles:self.loader.tags[k]=self.tag_handles[k] for key in self.DEFAULT_TAGS: if key not in self.tag_handles:self.tag_handles[key]=self.DEFAULT_TAGS[key] return value def parse_block_node(self):return self.parse_node(block=_C) def parse_flow_node(self):return self.parse_node() def parse_block_node_or_indentless_sequence(self):return self.parse_node(block=_C,indentless_sequence=_C) def transform_tag(self,handle,suffix):return self.tag_handles[handle]+suffix def parse_node(self,block=_B,indentless_sequence=_B): if self.scanner.check_token(AliasToken):token=self.scanner.get_token();event=AliasEvent(token.value,token.start_mark,token.end_mark);self.state=self.states.pop();return event anchor=_A;tag=_A;start_mark=end_mark=tag_mark=_A if self.scanner.check_token(AnchorToken): token=self.scanner.get_token();start_mark=token.start_mark;end_mark=token.end_mark;anchor=token.value if self.scanner.check_token(TagToken):token=self.scanner.get_token();tag_mark=token.start_mark;end_mark=token.end_mark;tag=token.value elif self.scanner.check_token(TagToken): token=self.scanner.get_token();start_mark=tag_mark=token.start_mark;end_mark=token.end_mark;tag=token.value if self.scanner.check_token(AnchorToken):token=self.scanner.get_token();start_mark=tag_mark=token.start_mark;end_mark=token.end_mark;anchor=token.value if tag is not _A: handle,suffix=tag if handle is not _A: if handle not in self.tag_handles:raise ParserError('while parsing a node',start_mark,'found undefined tag handle %r'%utf8(handle),tag_mark) 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=='!' 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=='!':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() elif self.scanner.check_token(FlowSequenceStartToken):pt=self.scanner.peek_token();end_mark=pt.end_mark;event=SequenceStartEvent(anchor,tag,implicit,start_mark,end_mark,flow_style=_C,comment=pt.comment);self.state=self.parse_flow_sequence_first_entry elif self.scanner.check_token(FlowMappingStartToken):pt=self.scanner.peek_token();end_mark=pt.end_mark;event=MappingStartEvent(anchor,tag,implicit,start_mark,end_mark,flow_style=_C,comment=pt.comment);self.state=self.parse_flow_mapping_first_key elif block and self.scanner.check_token(BlockSequenceStartToken): end_mark=self.scanner.peek_token().start_mark;pt=self.scanner.peek_token();comment=pt.comment if comment is _A or comment[1]is _A:comment=pt.split_comment() event=SequenceStartEvent(anchor,tag,implicit,start_mark,end_mark,flow_style=_B,comment=comment);self.state=self.parse_block_sequence_first_entry elif block and self.scanner.check_token(BlockMappingStartToken):end_mark=self.scanner.peek_token().start_mark;comment=self.scanner.peek_token().comment;event=MappingStartEvent(anchor,tag,implicit,start_mark,end_mark,flow_style=_B,comment=comment);self.state=self.parse_block_mapping_first_key elif anchor is not _A or tag is not _A:event=ScalarEvent(anchor,tag,(implicit,_B),'',start_mark,end_mark);self.state=self.states.pop() else: if block:node='block' else:node='flow' token=self.scanner.peek_token();raise ParserError('while parsing a %s node'%node,start_mark,'expected the node content, but found %r'%token.id,token.start_mark) return event def parse_block_sequence_first_entry(self):token=self.scanner.get_token();self.marks.append(token.start_mark);return self.parse_block_sequence_entry() def parse_block_sequence_entry(self): if self.scanner.check_token(BlockEntryToken): 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],_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): token=self.scanner.get_token();token.move_comment(self.scanner.peek_token()) if not self.scanner.check_token(BlockEntryToken,KeyToken,ValueToken,BlockEndToken):self.states.append(self.parse_indentless_sequence_entry);return self.parse_block_node() else:self.state=self.parse_indentless_sequence_entry;return self.process_empty_scalar(token.end_mark) token=self.scanner.peek_token();event=SequenceEndEvent(token.start_mark,token.start_mark,comment=token.comment);self.state=self.states.pop();return event def parse_block_mapping_first_key(self):token=self.scanner.get_token();self.marks.append(token.start_mark);return self.parse_block_mapping_key() def parse_block_mapping_key(self): if self.scanner.check_token(KeyToken): token=self.scanner.get_token();token.move_comment(self.scanner.peek_token()) 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],_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): token=self.scanner.get_token() if self.scanner.check_token(ValueToken):token.move_comment(self.scanner.peek_token()) elif not self.scanner.check_token(KeyToken):token.move_comment(self.scanner.peek_token(),empty=_C) if not self.scanner.check_token(KeyToken,ValueToken,BlockEndToken):self.states.append(self.parse_block_mapping_key);return self.parse_block_node_or_indentless_sequence() else: self.state=self.parse_block_mapping_key;comment=token.comment if comment is _A: token=self.scanner.peek_token();comment=token.comment if comment:token._comment=[_A,comment[1]];comment=[comment[0],_A] return self.process_empty_scalar(token.end_mark,comment=comment) else:self.state=self.parse_block_mapping_key;token=self.scanner.peek_token();return self.process_empty_scalar(token.start_mark) def parse_flow_sequence_first_entry(self):token=self.scanner.get_token();self.marks.append(token.start_mark);return self.parse_flow_sequence_entry(first=_C) def parse_flow_sequence_entry(self,first=_B): if not self.scanner.check_token(FlowSequenceEndToken): if not first: if self.scanner.check_token(FlowEntryToken):self.scanner.get_token() else:token=self.scanner.peek_token();raise ParserError('while parsing a flow sequence',self.marks[-1],"expected ',' or ']', but got %r"%token.id,token.start_mark) if self.scanner.check_token(KeyToken):token=self.scanner.peek_token();event=MappingStartEvent(_A,_A,_C,token.start_mark,token.end_mark,flow_style=_C);self.state=self.parse_flow_sequence_entry_mapping_key;return event elif not self.scanner.check_token(FlowSequenceEndToken):self.states.append(self.parse_flow_sequence_entry);return self.parse_flow_node() 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_flow_sequence_entry_mapping_key(self): token=self.scanner.get_token() if not self.scanner.check_token(ValueToken,FlowEntryToken,FlowSequenceEndToken):self.states.append(self.parse_flow_sequence_entry_mapping_value);return self.parse_flow_node() else:self.state=self.parse_flow_sequence_entry_mapping_value;return self.process_empty_scalar(token.end_mark) def parse_flow_sequence_entry_mapping_value(self): if self.scanner.check_token(ValueToken): token=self.scanner.get_token() if not self.scanner.check_token(FlowEntryToken,FlowSequenceEndToken):self.states.append(self.parse_flow_sequence_entry_mapping_end);return self.parse_flow_node() else:self.state=self.parse_flow_sequence_entry_mapping_end;return self.process_empty_scalar(token.end_mark) else:self.state=self.parse_flow_sequence_entry_mapping_end;token=self.scanner.peek_token();return self.process_empty_scalar(token.start_mark) def parse_flow_sequence_entry_mapping_end(self):self.state=self.parse_flow_sequence_entry;token=self.scanner.peek_token();return MappingEndEvent(token.start_mark,token.start_mark) def parse_flow_mapping_first_key(self):token=self.scanner.get_token();self.marks.append(token.start_mark);return self.parse_flow_mapping_key(first=_C) def parse_flow_mapping_key(self,first=_B): if not self.scanner.check_token(FlowMappingEndToken): if not first: if self.scanner.check_token(FlowEntryToken):self.scanner.get_token() else:token=self.scanner.peek_token();raise ParserError('while parsing a flow mapping',self.marks[-1],"expected ',' or '}', but got %r"%token.id,token.start_mark) if self.scanner.check_token(KeyToken): token=self.scanner.get_token() if not self.scanner.check_token(ValueToken,FlowEntryToken,FlowMappingEndToken):self.states.append(self.parse_flow_mapping_value);return self.parse_flow_node() else:self.state=self.parse_flow_mapping_value;return self.process_empty_scalar(token.end_mark) elif self.resolver.processing_version>(1,1)and self.scanner.check_token(ValueToken):self.state=self.parse_flow_mapping_value;return self.process_empty_scalar(self.scanner.peek_token().end_mark) elif not self.scanner.check_token(FlowMappingEndToken):self.states.append(self.parse_flow_mapping_empty_value);return self.parse_flow_node() token=self.scanner.get_token();event=MappingEndEvent(token.start_mark,token.end_mark,comment=token.comment);self.state=self.states.pop();self.marks.pop();return event def parse_flow_mapping_value(self): if self.scanner.check_token(ValueToken): token=self.scanner.get_token() if not self.scanner.check_token(FlowEntryToken,FlowMappingEndToken):self.states.append(self.parse_flow_mapping_key);return self.parse_flow_node() else:self.state=self.parse_flow_mapping_key;return self.process_empty_scalar(token.end_mark) else:self.state=self.parse_flow_mapping_key;token=self.scanner.peek_token();return self.process_empty_scalar(token.start_mark) def parse_flow_mapping_empty_value(self):self.state=self.parse_flow_mapping_key;return self.process_empty_scalar(self.scanner.peek_token().start_mark) def process_empty_scalar(self,mark,comment=_A):return ScalarEvent(_A,_A,(_C,_B),'',mark,mark,comment=comment) class RoundTripParser(Parser): def transform_tag(self,handle,suffix): if handle=='!!'and suffix in('null','bool','int','float','binary','timestamp','omap','pairs','set','str','seq','map'):return Parser.transform_tag(self,handle,suffix) return handle+suffix././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/py.typed0000644000000000000000000000000015156326621022556 0ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776272.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/reader.py0000644000000000000000000001163415156326620022711 0ustar00pbrochadpbrochadfrom __future__ import absolute_import _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 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 def __str__(A): if isinstance(A.character,binary_type):return'\'%s\' codec can\'t decode byte #x%02x: %s\n in "%s", position %d'%(A.encoding,ord(A.character),A.reason,A.name,A.position) else:return'unacceptable character #x%04x: %s\n in "%s", position %d'%(A.character,A.reason,A.name,A.position) class Reader: def __init__(A,stream,loader=_A): A.loader=loader if A.loader is not _A and getattr(A.loader,'_reader',_A)is _A:A.loader._reader=A A.reset_reader();A.stream=stream def reset_reader(A):A.name=_A;A.stream_pointer=0;A.eof=True;A.buffer='';A.pointer=0;A.raw_buffer=_A;A.raw_decode=_A;A.encoding=_A;A.index=0;A.line=0;A.column=0 @property def stream(self): try:return self._stream except AttributeError:raise YAMLStreamError('input stream needs to specified') @stream.setter def stream(self,val): B=val;A=self if B is _A:return A._stream=_A if isinstance(B,text_type):A.name='';A.check_printable(B);A.buffer=B+'\x00' elif isinstance(B,binary_type):A.name='';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','');A.eof=False;A.raw_buffer=_A;A.determine_encoding() def peek(A,index=0): B=index try:return A.buffer[A.pointer+B] except IndexError:A.update(B+1);return A.buffer[A.pointer+B] def prefix(A,length=1): B=length if A.pointer+B>=len(A.buffer):A.update(B) return A.buffer[A.pointer:A.pointer+B] def forward_1_1(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 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=='\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) else:return FileMark(A.name,A.index,A.line,A.column) def determine_encoding(A): while not A.eof and(A.raw_buffer is _A or len(A.raw_buffer)<2):A.update_raw() if isinstance(A.raw_buffer,binary_type): if A.raw_buffer.startswith(codecs.BOM_UTF16_LE):A.raw_decode=codecs.utf_16_le_decode;A.encoding='utf-16-le' elif A.raw_buffer.startswith(codecs.BOM_UTF16_BE):A.raw_decode=codecs.utf_16_be_decode;A.encoding='utf-16-be' else:A.raw_decode=codecs.utf_8_decode;A.encoding='utf-8' 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(_B) @classmethod def _get_non_printable_ascii(D,data): A=data.encode(_B);B=A.translate(_A,D._printable_ascii) if not B:return C=B[:1];return A.index(C),C.decode(_B) @classmethod def _get_non_printable_regex(B,data): A=B.NON_PRINTABLE.search(data) if not bool(A):return return A.start(),A.group() @classmethod def _get_non_printable(A,data): try:return A._get_non_printable_ascii(data) except UnicodeEncodeError:return A._get_non_printable_regex(data) def check_printable(A,data): B=A._get_non_printable(data) if B is not _A:C,D=B;E=A.index+(len(A.buffer)-A.pointer)+C;raise ReaderError(A.name,E,ord(D),'unicode','special characters are not allowed') def update(A,length): if A.raw_buffer is _A:return A.buffer=A.buffer[A.pointer:];A.pointer=0 while len(A.buffer)': comment=getattr(value,_F,_A) if comment:comment=[_A,[comment]] node=ScalarNode(tag,value,style=style,comment=comment,anchor=anchor) if self.alias_key is not _A:self.represented_objects[self.alias_key]=node return node def represent_sequence(self,tag,sequence,flow_style=_A): value=[];node=SequenceNode(tag,value,flow_style=flow_style) if self.alias_key is not _A:self.represented_objects[self.alias_key]=node best_style=_B for item in sequence: node_item=self.represent_data(item) if not(isinstance(node_item,ScalarNode)and not node_item.style):best_style=_C value.append(node_item) if flow_style is _A: if self.default_flow_style is not _A:node.flow_style=self.default_flow_style else:node.flow_style=best_style return node def represent_omap(self,tag,omap,flow_style=_A): value=[];node=SequenceNode(tag,value,flow_style=flow_style) if self.alias_key is not _A:self.represented_objects[self.alias_key]=node best_style=_B for item_key in omap:item_val=omap[item_key];node_item=self.represent_data({item_key:item_val});value.append(node_item) if flow_style is _A: if self.default_flow_style is not _A:node.flow_style=self.default_flow_style else:node.flow_style=best_style return node def represent_mapping(self,tag,mapping,flow_style=_A): value=[];node=MappingNode(tag,value,flow_style=flow_style) if self.alias_key is not _A:self.represented_objects[self.alias_key]=node best_style=_B if hasattr(mapping,'items'): mapping=list(mapping.items()) if self.sort_base_mapping_type_on_output: try:mapping=sorted(mapping) except TypeError:pass for(item_key,item_value)in mapping: node_key=self.represent_key(item_key);node_value=self.represent_data(item_value) if not(isinstance(node_key,ScalarNode)and not node_key.style):best_style=_C if not(isinstance(node_value,ScalarNode)and not node_value.style):best_style=_C value.append((node_key,node_value)) if flow_style is _A: if self.default_flow_style is not _A:node.flow_style=self.default_flow_style else:node.flow_style=best_style return node def ignore_aliases(self,data):return _C class SafeRepresenter(BaseRepresenter): def ignore_aliases(self,data): 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(_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(_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,'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): try:value=self.dumper.boolean_representation[bool(data)] except AttributeError: if data:value='true' else:value='false' return self.represent_scalar('tag:yaml.org,2002:bool',value,anchor=anchor) def represent_int(self,data):return self.represent_scalar(_G,text_type(data)) if PY2: def represent_long(self,data):return self.represent_scalar(_G,text_type(data)) inf_value=1e300 while repr(inf_value)!=repr(inf_value*inf_value):inf_value*=inf_value def represent_float(self,data): if data!=data or data==.0 and data==1.: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'.'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(_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,_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,)) SafeRepresenter.add_representer(type(_A),SafeRepresenter.represent_none) SafeRepresenter.add_representer(str,SafeRepresenter.represent_str) if PY2:SafeRepresenter.add_representer(unicode,SafeRepresenter.represent_unicode) else:SafeRepresenter.add_representer(bytes,SafeRepresenter.represent_binary) SafeRepresenter.add_representer(bool,SafeRepresenter.represent_bool) SafeRepresenter.add_representer(int,SafeRepresenter.represent_int) if PY2:SafeRepresenter.add_representer(long,SafeRepresenter.represent_long) SafeRepresenter.add_representer(float,SafeRepresenter.represent_float) SafeRepresenter.add_representer(list,SafeRepresenter.represent_list) SafeRepresenter.add_representer(tuple,SafeRepresenter.represent_list) SafeRepresenter.add_representer(dict,SafeRepresenter.represent_dict) SafeRepresenter.add_representer(set,SafeRepresenter.represent_set) SafeRepresenter.add_representer(ordereddict,SafeRepresenter.represent_ordereddict) if sys.version_info>=(2,7):import collections;SafeRepresenter.add_representer(collections.OrderedDict,SafeRepresenter.represent_ordereddict) SafeRepresenter.add_representer(datetime.date,SafeRepresenter.represent_date) SafeRepresenter.add_representer(datetime.datetime,SafeRepresenter.represent_datetime) SafeRepresenter.add_representer(_A,SafeRepresenter.represent_undefined) class Representer(SafeRepresenter): if PY2: def represent_str(self,data): tag=_A;style=_A try:data=unicode(data,_E);tag=_D except UnicodeDecodeError: 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 try:data.encode(_E);tag='tag:yaml.org,2002:python/unicode' except UnicodeEncodeError:tag=_D return self.represent_scalar(tag,data) def represent_long(self,data): tag=_G 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==.0:data='%r'%data.real elif data.real==.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=_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=_H%(cls.__module__,cls.__name__);args=_A;state=_A if hasattr(data,'__getinitargs__'):args=list(data.__getinitargs__()) if hasattr(data,_R):state=data.__getstate__() else:state=data.__dict__ 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['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) elif hasattr(data,'__reduce_ex__'):reduce=data.__reduce_ex__(2) elif hasattr(data,'__reduce__'):reduce=data.__reduce__() else:raise RepresenterError('cannot represent object: %r'%(data,)) reduce=(list(reduce)+[_A]*5)[:5];function,args,state,listitems,dictitems=reduce;args=list(args) 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=_O;newobj=_B else:tag='tag:yaml.org,2002:python/object/apply:';newobj=_C 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['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) if PY2:Representer.add_representer(str,Representer.represent_str);Representer.add_representer(unicode,Representer.represent_unicode);Representer.add_representer(long,Representer.represent_long) Representer.add_representer(complex,Representer.represent_complex) Representer.add_representer(tuple,Representer.represent_tuple) Representer.add_representer(type,Representer.represent_name) if PY2:Representer.add_representer(types.ClassType,Representer.represent_name) Representer.add_representer(types.FunctionType,Representer.represent_name) Representer.add_representer(types.BuiltinFunctionType,Representer.represent_name) Representer.add_representer(types.ModuleType,Representer.represent_module) if PY2:Representer.add_multi_representer(types.InstanceType,Representer.represent_instance) Representer.add_multi_representer(object,Representer.represent_object) 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,'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: if data.anchor is not _A and data.anchor.value is not _A:return _C 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(_I,'null') return self.represent_scalar(_I,'') def represent_literal_scalarstring(self,data): 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 def represent_folded_scalarstring(self,data): tag=_A;style='>';anchor=data.yaml_anchor(any=_B) for fold_pos in reversed(getattr(data,'fold_pos',[])): if data[fold_pos]==' 'and(fold_pos>0 and not data[fold_pos-1].isspace())and(fold_pos0:sl.insert(pos,A);pos-=underscore[0] s=''.join(sl) if underscore[1]:s=A+s if underscore[2]:s+=A return self.represent_scalar(_G,prefix+s,anchor=anchor) def represent_scalar_int(self,data): if data._width is not _A:s='{:0{}d}'.format(data,data._width) else:s=format(data,'d') anchor=data.yaml_anchor(any=_B);return self.insert_underscore('',s,data._underscore,anchor=anchor) def represent_binary_int(self,data): if data._width is not _A:s='{:0{}b}'.format(data,data._width) else:s=format(data,'b') anchor=data.yaml_anchor(any=_B);return self.insert_underscore('0b',s,data._underscore,anchor=anchor) def represent_octal_int(self,data): if data._width is not _A:s='{:0{}o}'.format(data,data._width) else:s=format(data,'o') anchor=data.yaml_anchor(any=_B);return self.insert_underscore('0o',s,data._underscore,anchor=anchor) def represent_hex_int(self,data): if data._width is not _A:s='{:0{}x}'.format(data,data._width) else:s=format(data,'x') anchor=data.yaml_anchor(any=_B);return self.insert_underscore('0x',s,data._underscore,anchor=anchor) def represent_hex_caps_int(self,data): if data._width is not _A:s='{:0{}X}'.format(data,data._width) 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): B='{:{}0{}d}';A='0';value=_A;anchor=data.yaml_anchor(any=_B) if data!=data or data==.0 and data==1.: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.','.') while len(value)0 else data._width+1 if data<0:w+=1 m=m[:w];e=int(es);m1,m2=m.split('.') while len(m1)+len(m2)=0 else 0):m2+=A 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)0:m2=A*(data._m_lead0-1)+m1+m2;m1=A;m2=m2[:-data._m_lead0];e+=data._m_lead0 while len(m1)=len(node.comment):continue nc=node.comment[idx] 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(_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=[] try:flow_style=mapping.fa.flow_style(flow_style) except AttributeError:flow_style=flow_style try:anchor=mapping.yaml_anchor() except AttributeError:anchor=_A node=MappingNode(tag,value,flow_style=flow_style,anchor=anchor) if self.alias_key is not _A:self.represented_objects[self.alias_key]=node best_style=_B try: comment=getattr(mapping,comment_attrib);node.comment=comment.comment if node.comment and node.comment[1]: for ct in node.comment[1]:ct.reset() item_comments=comment.items for v in item_comments.values(): if v and v[1]: for ct in v[1]:ct.reset() try:node.comment.append(comment.end) except AttributeError:pass except AttributeError:item_comments={} merge_list=[m[1]for m in getattr(mapping,merge_attrib,[])] try:merge_pos=getattr(mapping,merge_attrib,[[0]])[0][0] except IndexError:merge_pos=0 item_count=0 if bool(merge_list):items=mapping.non_merged_items() else:items=mapping.items() for(item_key,item_value)in items: item_count+=1;node_key=self.represent_key(item_key);node_value=self.represent_data(item_value);item_comment=item_comments.get(item_key) if item_comment: assert getattr(node_key,_F,_A)is _A;node_key.comment=item_comment[:2];nvc=getattr(node_value,_F,_A) if nvc is not _A:nvc[0]=item_comment[2];nvc[1]=item_comment[3] else:node_value.comment=item_comment[2:] if not(isinstance(node_key,ScalarNode)and not node_key.style):best_style=_C if not(isinstance(node_value,ScalarNode)and not node_value.style):best_style=_C value.append((node_key,node_value)) if flow_style is _A: if(item_count!=0 or bool(merge_list))and self.default_flow_style is not _A:node.flow_style=self.default_flow_style else:node.flow_style=best_style if bool(merge_list): if len(merge_list)==1:arg=self.represent_data(merge_list[0]) else:arg=self.represent_data(merge_list);arg.flow_style=_B value.insert(merge_pos,(ScalarNode('tag:yaml.org,2002:merge','<<'),arg)) return node def represent_omap(self,tag,omap,flow_style=_A): value=[] try:flow_style=omap.fa.flow_style(flow_style) except AttributeError:flow_style=flow_style try:anchor=omap.yaml_anchor() except AttributeError:anchor=_A node=SequenceNode(tag,value,flow_style=flow_style,anchor=anchor) if self.alias_key is not _A:self.represented_objects[self.alias_key]=node best_style=_B try: comment=getattr(omap,comment_attrib);node.comment=comment.comment if node.comment and node.comment[1]: for ct in node.comment[1]:ct.reset() item_comments=comment.items for v in item_comments.values(): if v and v[1]: for ct in v[1]:ct.reset() try:node.comment.append(comment.end) except AttributeError:pass except AttributeError:item_comments={} for item_key in omap: item_val=omap[item_key];node_item=self.represent_data({item_key:item_val});item_comment=item_comments.get(item_key) if item_comment: if item_comment[1]:node_item.comment=[_A,item_comment[1]] assert getattr(node_item.value[0][0],_F,_A)is _A;node_item.value[0][0].comment=[item_comment[0],_A];nvc=getattr(node_item.value[0][1],_F,_A) if nvc is not _A:nvc[0]=item_comment[2];nvc[1]=item_comment[3] else:node_item.value[0][1].comment=item_comment[2:] value.append(node_item) if flow_style is _A: if self.default_flow_style is not _A:node.flow_style=self.default_flow_style else:node.flow_style=best_style return node def represent_set(self,setting): 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) if self.alias_key is not _A:self.represented_objects[self.alias_key]=node best_style=_B try: comment=getattr(setting,comment_attrib);node.comment=comment.comment if node.comment and node.comment[1]: for ct in node.comment[1]:ct.reset() item_comments=comment.items for v in item_comments.values(): if v and v[1]: for ct in v[1]:ct.reset() try:node.comment.append(comment.end) except AttributeError:pass except AttributeError:item_comments={} for item_key in setting.odict: node_key=self.represent_key(item_key);node_value=self.represent_data(_A);item_comment=item_comments.get(item_key) if item_comment:assert getattr(node_key,_F,_A)is _A;node_key.comment=item_comment[:2] node_key.style=node_value.style='?' if not(isinstance(node_key,ScalarNode)and not node_key.style):best_style=_C if not(isinstance(node_value,ScalarNode)and not node_value.style):best_style=_C value.append((node_key,node_value)) best_style=best_style;return node def represent_dict(self,data): try:t=data.tag.value except AttributeError:t=_A if t: if t.startswith('!!'):tag=_T+t[2:] else:tag=t 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=_T+t[2:] else:tag=t else:tag=_L return self.represent_sequence(tag,data) def represent_datetime(self,data): 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['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 try:anchor=data.yaml_anchor() except AttributeError:anchor=_A return self.represent_scalar(tag,data.value,style=data.style,anchor=anchor) def represent_scalar_bool(self,data): try:anchor=data.yaml_anchor() except AttributeError:anchor=_A return SafeRepresenter.represent_bool(self,data,anchor=anchor) RoundTripRepresenter.add_representer(type(_A),RoundTripRepresenter.represent_none) RoundTripRepresenter.add_representer(LiteralScalarString,RoundTripRepresenter.represent_literal_scalarstring) RoundTripRepresenter.add_representer(FoldedScalarString,RoundTripRepresenter.represent_folded_scalarstring) RoundTripRepresenter.add_representer(SingleQuotedScalarString,RoundTripRepresenter.represent_single_quoted_scalarstring) RoundTripRepresenter.add_representer(DoubleQuotedScalarString,RoundTripRepresenter.represent_double_quoted_scalarstring) RoundTripRepresenter.add_representer(PlainScalarString,RoundTripRepresenter.represent_plain_scalarstring) RoundTripRepresenter.add_representer(ScalarInt,RoundTripRepresenter.represent_scalar_int) RoundTripRepresenter.add_representer(BinaryInt,RoundTripRepresenter.represent_binary_int) RoundTripRepresenter.add_representer(OctalInt,RoundTripRepresenter.represent_octal_int) RoundTripRepresenter.add_representer(HexInt,RoundTripRepresenter.represent_hex_int) RoundTripRepresenter.add_representer(HexCapsInt,RoundTripRepresenter.represent_hex_caps_int) RoundTripRepresenter.add_representer(ScalarFloat,RoundTripRepresenter.represent_scalar_float) RoundTripRepresenter.add_representer(ScalarBoolean,RoundTripRepresenter.represent_scalar_bool) RoundTripRepresenter.add_representer(CommentedSeq,RoundTripRepresenter.represent_list) RoundTripRepresenter.add_representer(CommentedMap,RoundTripRepresenter.represent_dict) RoundTripRepresenter.add_representer(CommentedOrderedMap,RoundTripRepresenter.represent_ordereddict) if sys.version_info>=(2,7):import collections;RoundTripRepresenter.add_representer(collections.OrderedDict,RoundTripRepresenter.represent_ordereddict) RoundTripRepresenter.add_representer(CommentedSet,RoundTripRepresenter.represent_set) RoundTripRepresenter.add_representer(TaggedScalar,RoundTripRepresenter.represent_tagged_scalar) RoundTripRepresenter.add_representer(TimeStamp,RoundTripRepresenter.represent_datetime)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/resolver.py0000644000000000000000000002245415156326621023313 0ustar00pbrochadpbrochadfrom __future__ import absolute_import _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 if _B:from typing import Any,Dict,List,Union,Text,Optional;from.compat import VersionType from.compat import string_types,_DEFAULT_YAML_VERSION from.error import* from.nodes import MappingNode,ScalarNode,SequenceNode from.util import RegExp __all__=['BaseResolver','Resolver','VersionedResolver'] 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={} def __init__(self,loadumper=_A): self.loadumper=loadumper if self.loadumper is not _A and getattr(self.loadumper,'_resolver',_A)is _A:self.loadumper._resolver=self.loadumper self._loader_version=_A;self.resolver_exact_paths=[];self.resolver_prefix_paths=[] @property def parser(self): if self.loadumper is not _A: if hasattr(self.loadumper,'typ'):return self.loadumper.parser return self.loadumper._parser @classmethod def add_implicit_resolver_base(cls,tag,regexp,first): 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 _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)) @classmethod def add_path_resolver(cls,tag,path,kind=_A): if'yaml_path_resolvers'not in cls.__dict__:cls.yaml_path_resolvers=cls.yaml_path_resolvers.copy() new_path=[] 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=True else:raise ResolverError('Invalid path element: %s'%(element,)) else:node_check=_A;index_check=element if node_check is str:node_check=ScalarNode elif node_check is list:node_check=SequenceNode elif node_check is dict:node_check=MappingNode elif node_check not in[ScalarNode,SequenceNode,MappingNode]and not isinstance(node_check,string_types)and node_check is not _A:raise ResolverError('Invalid node checker: %s'%(node_check,)) if not isinstance(index_check,(string_types,int))and index_check is not _A:raise ResolverError('Invalid index checker: %s'%(index_check,)) new_path.append((node_check,index_check)) if kind is str:kind=ScalarNode elif kind is list:kind=SequenceNode elif kind is dict:kind=MappingNode elif kind not in[ScalarNode,SequenceNode,MappingNode]and kind is not _A:raise ResolverError('Invalid node kind: %s'%(kind,)) cls.yaml_path_resolvers[tuple(new_path),kind]=tag def descend_resolver(self,current_node,current_index): if not self.yaml_path_resolvers:return exact_paths={};prefix_paths=[] if current_node: depth=len(self.resolver_prefix_paths) for(path,kind)in self.resolver_prefix_paths[-1]: if self.check_resolver_prefix(depth,path,kind,current_node,current_index): if len(path)>depth:prefix_paths.append((path,kind)) else:exact_paths[kind]=self.yaml_path_resolvers[path,kind] else: for(path,kind)in self.yaml_path_resolvers: if not path:exact_paths[kind]=self.yaml_path_resolvers[path,kind] else:prefix_paths.append((path,kind)) self.resolver_exact_paths.append(exact_paths);self.resolver_prefix_paths.append(prefix_paths) def ascend_resolver(self): if not self.yaml_path_resolvers:return self.resolver_exact_paths.pop();self.resolver_prefix_paths.pop() def check_resolver_prefix(self,depth,path,kind,current_node,current_index): node_check,index_check=path[depth-1] if isinstance(node_check,string_types): 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 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 True def resolve(self,kind,value,implicit): if kind is ScalarNode and implicit[0]: if value=='':resolvers=self.yaml_implicit_resolvers.get('',[]) else:resolvers=self.yaml_implicit_resolvers.get(value[0],[]) resolvers+=self.yaml_implicit_resolvers.get(_A,[]) for(tag,regexp)in resolvers: if regexp.match(value):return tag implicit=implicit[1] if bool(self.yaml_path_resolvers): exact_paths=self.resolver_exact_paths[-1] if kind in exact_paths:return exact_paths[kind] if _A in exact_paths:return exact_paths[_A] if kind is ScalarNode:return self.DEFAULT_SCALAR_TAG elif kind is SequenceNode:return self.DEFAULT_SEQUENCE_TAG elif kind is MappingNode:return self.DEFAULT_MAPPING_TAG @property def processing_version(self):0 class Resolver(BaseResolver):0 for ir in implicit_resolvers: if(1,2)in ir[0]:Resolver.add_implicit_resolver_base(*ir[1:]) class VersionedResolver(BaseResolver): def __init__(self,version=_A,loader=_A,loadumper=_A): if loader is _A and loadumper is not _A:loader=loadumper BaseResolver.__init__(self,loader);self._loader_version=self.get_loader_version(version);self._version_implicit_resolver={} def add_version_implicit_resolver(self,version,tag,regexp,first): if first is _A:first=[_A] impl_resolver=self._version_implicit_resolver.setdefault(version,{}) for ch in first:impl_resolver.setdefault(ch,[]).append((tag,regexp)) def get_loader_version(self,version): if version is _A or isinstance(version,tuple):return version if isinstance(version,list):return tuple(version) return tuple(map(int,version.split('.'))) @property def versioned_resolver(self): version=self.processing_version if version not in self._version_implicit_resolver: for x in implicit_resolvers: if version in x[0]:self.add_version_implicit_resolver(version,x[1],x[2],x[3]) return self._version_implicit_resolver[version] def resolve(self,kind,value,implicit): if kind is ScalarNode and implicit[0]: if value=='':resolvers=self.versioned_resolver.get('',[]) else:resolvers=self.versioned_resolver.get(value[0],[]) resolvers+=self.versioned_resolver.get(_A,[]) for(tag,regexp)in resolvers: if regexp.match(value):return tag implicit=implicit[1] if bool(self.yaml_path_resolvers): exact_paths=self.resolver_exact_paths[-1] if kind in exact_paths:return exact_paths[kind] if _A in exact_paths:return exact_paths[_A] if kind is ScalarNode:return self.DEFAULT_SCALAR_TAG elif kind is SequenceNode:return self.DEFAULT_SEQUENCE_TAG elif kind is MappingNode:return self.DEFAULT_MAPPING_TAG @property def processing_version(self): try:version=self.loadumper._scanner.yaml_version except AttributeError: try: if hasattr(self.loadumper,'typ'):version=self.loadumper.version else:version=self.loadumper._serializer.use_version except AttributeError:version=_A if version is _A: version=self._loader_version if version is _A:version=_DEFAULT_YAML_VERSION return version././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/scalarbool.py0000644000000000000000000000130515156326621023563 0ustar00pbrochadpbrochadfrom __future__ import print_function,absolute_import,division,unicode_literals _A=False from.anchor import Anchor if _A:from typing import Text,Any,Dict,List __all__=['ScalarBoolean'] class ScalarBoolean(int): def __new__(D,*E,**A): B=A.pop('anchor',None);C=int.__new__(D,*E,**A) if B is not None:C.yaml_set_anchor(B,always_dump=True) return C @property def anchor(self): A=self if not hasattr(A,Anchor.attrib):setattr(A,Anchor.attrib,Anchor()) return getattr(A,Anchor.attrib) def yaml_anchor(A,any=_A): if not hasattr(A,Anchor.attrib):return if any or A.anchor.always_dump:return A.anchor def yaml_set_anchor(A,value,always_dump=_A):A.anchor.value=value;A.anchor.always_dump=always_dump././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/scalarfloat.py0000644000000000000000000000440515156326621023741 0ustar00pbrochadpbrochadfrom __future__ import print_function,absolute_import,division,unicode_literals _B=False _A=None import sys from.compat import no_limit_int from.anchor import Anchor 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 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 def __ifloordiv__(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 def __imul__(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;B._prec=A._prec;return B def __ipow__(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 def __isub__(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 @property def anchor(self): A=self if not hasattr(A,Anchor.attrib):setattr(A,Anchor.attrib,Anchor()) return getattr(A,Anchor.attrib) def yaml_anchor(A,any=_B): if not hasattr(A,Anchor.attrib):return if any or A.anchor.always_dump:return A.anchor def yaml_set_anchor(A,value,always_dump=_B):A.anchor.value=value;A.anchor.always_dump=always_dump def dump(A,out=sys.stdout):out.write('ScalarFloat({}| w:{}, p:{}, s:{}, lz:{}, _:{}|{}, w:{}, s:{})\n'.format(A,A._width,A._prec,A._m_sign,A._m_lead0,A._underscore,A._exp,A._e_width,A._e_sign)) class ExponentialFloat(ScalarFloat): def __new__(A,value,width=_A,underscore=_A):return ScalarFloat.__new__(A,value,width=width,underscore=underscore) class ExponentialCapsFloat(ScalarFloat): def __new__(A,value,width=_A,underscore=_A):return ScalarFloat.__new__(A,value,width=width,underscore=underscore)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/scalarint.py0000644000000000000000000000445115156326621023427 0ustar00pbrochadpbrochadfrom __future__ import print_function,absolute_import,division,unicode_literals _B=False _A=None from.compat import no_limit_int from.anchor import Anchor 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 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 def __ifloordiv__(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 def __imul__(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 def __ipow__(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 def __isub__(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 @property def anchor(self): A=self if not hasattr(A,Anchor.attrib):setattr(A,Anchor.attrib,Anchor()) return getattr(A,Anchor.attrib) def yaml_anchor(A,any=_B): if not hasattr(A,Anchor.attrib):return if any or A.anchor.always_dump:return A.anchor def yaml_set_anchor(A,value,always_dump=_B):A.anchor.value=value;A.anchor.always_dump=always_dump class BinaryInt(ScalarInt): def __new__(A,value,width=_A,underscore=_A,anchor=_A):return ScalarInt.__new__(A,value,width=width,underscore=underscore,anchor=anchor) class OctalInt(ScalarInt): def __new__(A,value,width=_A,underscore=_A,anchor=_A):return ScalarInt.__new__(A,value,width=width,underscore=underscore,anchor=anchor) class HexInt(ScalarInt): def __new__(A,value,width=_A,underscore=_A,anchor=_A):return ScalarInt.__new__(A,value,width=width,underscore=underscore,anchor=anchor) class HexCapsInt(ScalarInt): def __new__(A,value,width=_A,underscore=_A,anchor=_A):return ScalarInt.__new__(A,value,width=width,underscore=underscore,anchor=anchor) class DecimalInt(ScalarInt): def __new__(A,value,width=_A,underscore=_A,anchor=_A):return ScalarInt.__new__(A,value,width=width,underscore=underscore,anchor=anchor)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/scalarstring.py0000644000000000000000000000453215156326621024143 0ustar00pbrochadpbrochadfrom __future__ import print_function,absolute_import,division,unicode_literals _B=False _A=None from.compat import text_type from.anchor import Anchor if _B:from typing import Text,Any,Dict,List __all__=['ScalarString','LiteralScalarString','FoldedScalarString','SingleQuotedScalarString','DoubleQuotedScalarString','PlainScalarString','PreservedScalarString'] class ScalarString(text_type): __slots__=Anchor.attrib def __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)) @property def anchor(self): A=self if not hasattr(A,Anchor.attrib):setattr(A,Anchor.attrib,Anchor()) return getattr(A,Anchor.attrib) def yaml_anchor(A,any=_B): if not hasattr(A,Anchor.attrib):return if any or A.anchor.always_dump:return A.anchor def yaml_set_anchor(A,value,always_dump=_B):A.anchor.value=value;A.anchor.always_dump=always_dump class LiteralScalarString(ScalarString): __slots__='comment';style='|' def __new__(A,value,anchor=_A):return ScalarString.__new__(A,value,anchor=anchor) PreservedScalarString=LiteralScalarString class FoldedScalarString(ScalarString): __slots__='fold_pos','comment';style='>' def __new__(A,value,anchor=_A):return ScalarString.__new__(A,value,anchor=anchor) class SingleQuotedScalarString(ScalarString): __slots__=();style="'" def __new__(A,value,anchor=_A):return ScalarString.__new__(A,value,anchor=anchor) class DoubleQuotedScalarString(ScalarString): __slots__=();style='"' def __new__(A,value,anchor=_A):return ScalarString.__new__(A,value,anchor=anchor) 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','\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={'\n':preserve_literal} if isinstance(A,G): for F in A: C=A[F] if isinstance(C,E): for B in map: if B in C:A[F]=map[B](C);break else:walk_tree(C) elif isinstance(A,H): for(I,D)in enumerate(A): if isinstance(D,E): for B in map: if B in D:A[I]=map[B](D);break else:walk_tree(D)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/scanner.py0000644000000000000000000007727615156326621023117 0ustar00pbrochadpbrochadfrom __future__ import print_function,absolute_import,division,unicode_literals _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' _F='#' _E='\n' _D=' ' _C=None _B=False _A=True from.error import MarkedYAMLError from.tokens import* from.compat import utf8,unichr,PY3,check_anchorname_char,nprint if _B:from typing import Any,Dict,Optional,List,Union,Text;from.compat import VersionType __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=_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 class Scanner: def __init__(self,loader=_C): self.loader=loader if self.loader is not _C and getattr(self.loader,'_scanner',_C)is _C:self.loader._scanner=self self.reset_scanner();self.first_time=_B;self.yaml_version=_C @property def flow_level(self):return len(self.flow_context) def reset_scanner(self):self.done=_B;self.flow_context=[];self.tokens=[];self.fetch_stream_start();self.tokens_taken=0;self.indent=-1;self.indents=[];self.allow_simple_key=_A;self.possible_simple_keys={} @property def reader(self): try:return self._scanner_reader except AttributeError: 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,'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() if bool(self.tokens): if not choices:return _A for choice in choices: if isinstance(self.tokens[0],choice):return _A return _B def peek_token(self): while self.need_more_tokens():self.fetch_more_tokens() if bool(self.tokens):return self.tokens[0] def get_token(self): while self.need_more_tokens():self.fetch_more_tokens() if bool(self.tokens):self.tokens_taken+=1;return self.tokens.pop(0) def need_more_tokens(self): if self.done:return _B if not self.tokens:return _A self.stale_possible_simple_keys() if self.next_possible_simple_key()==self.tokens_taken:return _A return _B def fetch_comment(self,comment):raise NotImplementedError def fetch_more_tokens(self): comment=self.scan_to_next_token() if comment is not _C:return self.fetch_comment(comment) 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=='-'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=='-'and self.check_block_entry():return self.fetch_block_entry() if ch=='?'and self.check_key():return self.fetch_key() 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=='>'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): min_token_number=_C for level in self.possible_simple_keys: key=self.possible_simple_keys[level] if min_token_number is _C or key.token_number1024: 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 if self.allow_simple_key:self.remove_possible_simple_key();token_number=self.tokens_taken+len(self.tokens);key=SimpleKey(token_number,required,self.reader.index,self.reader.line,self.reader.column,self.reader.get_mark());self.possible_simple_keys[self.flow_level]=key 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(_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 while self.indent>column:mark=self.reader.get_mark();self.indent=self.indents.pop();self.tokens.append(BlockEndToken(mark,mark)) def add_indent(self,column): if self.indent', 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 _J: if ch==_H:use_handle=_A;break length+=1;ch=srp(length) handle=_H if use_handle:handle=self.scan_tag_handle(A,start_mark) else:handle=_H;self.reader.forward() suffix=self.scan_tag_uri(A,start_mark) ch=srp() 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): 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'|>'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 _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'|>'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('\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 trailing=[] if chomping in[_C,_A]:chunks.append(line_break) if chomping is _A:chunks.extend(breaks) elif chomping in[_C,_B]:trailing.extend(breaks) token=ScalarToken(''.join(chunks),_B,start_mark,end_mark,style) if block_scalar_comment is not _C:token.add_pre_comments([block_scalar_comment]) if len(trailing)>0: comment=self.scan_to_next_token() while comment:trailing.append(_D*comment[1].column+comment[0]);comment=self.scan_to_next_token() 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): 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 A: increment=int(ch) if increment==0:raise ScannerError(_O,start_mark,B,self.reader.get_mark()) self.reader.forward() elif ch in A: increment=int(ch) if increment==0:raise ScannerError(_O,start_mark,B,self.reader.get_mark()) self.reader.forward();ch=srp() if ch in'+-': if ch=='+':chomping=_A else:chomping=_B self.reader.forward() ch=srp() 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 while srp()==_D:prefix+=srp();srf() if srp()==_F: comment=prefix while srp()not in _THE_END:comment+=srp();srf() ch=srp() 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 _X: if srp()!=_D:chunks.append(self.scan_line_break());end_mark=self.reader.get_mark() else: srf() if self.reader.column>max_indent:max_indent=self.reader.column return chunks,max_indent,end_mark 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(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) if length!=0:chunks.append(self.reader.prefix(length));self.reader.forward(length);length=0 if not chunks:raise ScannerError('while parsing a %s'%(name,),start_mark,'expected URI, but found %r'%utf8(ch),self.reader.get_mark()) return''.join(chunks) def scan_uri_escapes(self,name,start_mark): A='utf-8';srp=self.reader.peek;srf=self.reader.forward;code_bytes=[];mark=self.reader.get_mark() while srp()=='%': srf() for k in range(2): 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(_Q%(name,),start_mark,str(exc),mark) return value def scan_line_break(self): ch=self.reader.peek() if ch in _a: if self.reader.prefix(2)=='\r\n':self.reader.forward(2) else:self.reader.forward() return _E elif ch in _b:self.reader.forward();return ch return'' class RoundTripScanner(Scanner): def check_token(self,*choices): while self.need_more_tokens():self.fetch_more_tokens() self._gather_comments() if bool(self.tokens): if not choices:return _A for choice in choices: if isinstance(self.tokens[0],choice):return _A return _B def peek_token(self): while self.need_more_tokens():self.fetch_more_tokens() self._gather_comments() if bool(self.tokens):return self.tokens[0] def _gather_comments(self): comments=[] if not self.tokens:return comments if isinstance(self.tokens[0],CommentToken):comment=self.tokens.pop(0);self.tokens_taken+=1;comments.append(comment) while self.need_more_tokens(): self.fetch_more_tokens() if not self.tokens:return comments if isinstance(self.tokens[0],CommentToken):self.tokens_taken+=1;comment=self.tokens.pop(0);comments.append(comment) if len(comments)>=1:self.tokens[0].add_pre_comments(comments) if not self.done and len(self.tokens)<2:self.fetch_more_tokens() def get_token(self): while self.need_more_tokens():self.fetch_more_tokens() self._gather_comments() if bool(self.tokens): if len(self.tokens)>1 and isinstance(self.tokens[0],(ScalarToken,ValueToken,FlowSequenceEndToken,FlowMappingEndToken))and isinstance(self.tokens[1],CommentToken)and self.tokens[0].end_mark.line==self.tokens[1].start_mark.line: self.tokens_taken+=1;c=self.tokens.pop(1);self.fetch_more_tokens() while len(self.tokens)>1 and isinstance(self.tokens[1],CommentToken):self.tokens_taken+=1;c1=self.tokens.pop(1);c.value=c.value+_D*c1.start_mark.column+c1.value;self.fetch_more_tokens() self.tokens[0].add_post_comment(c) elif len(self.tokens)>1 and isinstance(self.tokens[0],ScalarToken)and isinstance(self.tokens[1],CommentToken)and self.tokens[0].end_mark.line!=self.tokens[1].start_mark.line: self.tokens_taken+=1;c=self.tokens.pop(1);c.value=_E*(c.start_mark.line-self.tokens[0].end_mark.line)+_D*c.start_mark.column+c.value;self.tokens[0].add_post_comment(c);self.fetch_more_tokens() while len(self.tokens)>1 and isinstance(self.tokens[1],CommentToken):self.tokens_taken+=1;c1=self.tokens.pop(1);c.value=c.value+_D*c1.start_mark.column+c1.value;self.fetch_more_tokens() self.tokens_taken+=1;return self.tokens.pop(0) def fetch_comment(self,comment): value,start_mark,end_mark=comment while value and value[-1]==_D:value=value[:-1] 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()==_U:srf() found=_B while not found: while srp()==_D:srf() ch=srp() if ch==_F: start_mark=self.reader.get_mark();comment=ch;srf() while ch not in _THE_END: ch=srp() if ch==_I:comment+=_E;break comment+=ch;srf() ch=self.scan_line_break() while len(ch)>0:comment+=ch;ch=self.scan_line_break() end_mark=self.reader.get_mark() if not self.flow_level:self.allow_simple_key=_A return comment,start_mark,end_mark if bool(self.scan_line_break()): start_mark=self.reader.get_mark() if not self.flow_level:self.allow_simple_key=_A ch=srp() if ch==_E: start_mark=self.reader.get_mark();comment='' while ch:ch=self.scan_line_break(empty_line=_A);comment+=ch if srp()==_F:comment=comment.rsplit(_E,1)[0]+_E end_mark=self.reader.get_mark();return comment,start_mark,end_mark else:found=_A def scan_line_break(self,empty_line=_B): ch=self.reader.peek() if ch in _a: if self.reader.prefix(2)=='\r\n':self.reader.forward(2) else:self.reader.forward() return _E 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)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/serializer.py0000644000000000000000000001000715156326621023612 0ustar00pbrochadpbrochadfrom __future__ import absolute_import _E='serializer is not opened' _D='serializer is closed' _C=False _B=True _A=None from.error import YAMLError from.compat import nprint,DBG_NODE,dbg,string_types,nprintf from.util import RegExp from.events import StreamStartEvent,StreamEndEvent,MappingStartEvent,MappingEndEvent,SequenceStartEvent,SequenceEndEvent,AliasEvent,ScalarEvent,DocumentStartEvent,DocumentEndEvent from.nodes import MappingNode,ScalarNode,SequenceNode if _C:from typing import Any,Dict,Union,Text,Optional;from.compat import VersionType __all__=['Serializer','SerializerError'] class SerializerError(YAMLError):0 class Serializer: ANCHOR_TEMPLATE='id%03d';ANCHOR_RE=RegExp('id(?!000$)\\d{3,}') def __init__(A,encoding=_A,explicit_start=_A,explicit_end=_A,version=_A,tags=_A,dumper=_A): B=version;A.dumper=dumper if A.dumper is not _A:A.dumper._serializer=A A.use_encoding=encoding;A.use_explicit_start=explicit_start;A.use_explicit_end=explicit_end if isinstance(B,string_types):A.use_version=tuple(map(int,B.split('.'))) else:A.use_version=B A.use_tags=tags;A.serialized_nodes={};A.anchors={};A.last_anchor_id=0;A.closed=_A;A._templated_id=_A @property def emitter(self): A=self if hasattr(A.dumper,'typ'):return A.dumper.emitter return A.dumper._emitter @property def resolver(self): A=self 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(_D) else:raise SerializerError('serializer is already opened') def close(A): 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(_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 if A in B.anchors: if B.anchors[A]is _A:B.anchors[A]=B.generate_anchor(A) else: C=_A try: if A.anchor.always_dump:C=A.anchor.value except:pass B.anchors[A]=C if isinstance(A,SequenceNode): for D in A.value:B.anchor_node(D) elif isinstance(A,MappingNode): for(E,F)in A.value:B.anchor_node(E);B.anchor_node(F) def generate_anchor(A,node): try:B=node.anchor.value except:B=_A if B is _A:A.last_anchor_id+=1;return A.ANCHOR_TEMPLATE%A.last_anchor_id return B def serialize_node(B,node,parent,index): F=index;A=node;G=B.anchors[A] if A in B.serialized_nodes:B.emitter.emit(AliasEvent(G)) else: B.serialized_nodes[A]=_B;B.resolver.descend_resolver(parent,F) if isinstance(A,ScalarNode):K=B.resolver.resolve(ScalarNode,A.value,(_B,_C));L=B.resolver.resolve(ScalarNode,A.value,(_C,_B));E=A.tag==K,A.tag==L,A.tag.startswith('tag:yaml.org,2002:');B.emitter.emit(ScalarEvent(G,A.tag,E,A.value,style=A.style,comment=A.comment)) elif isinstance(A,SequenceNode): E=A.tag==B.resolver.resolve(SequenceNode,A.value,_B);C=A.comment;D=_A;H=_A if A.flow_style is _B: if C:H=C[0] if C and len(C)>2:D=C[2] else:D=_A B.emitter.emit(SequenceStartEvent(G,A.tag,E,flow_style=A.flow_style,comment=A.comment));F=0 for M in A.value:B.serialize_node(M,A,F);F+=1 B.emitter.emit(SequenceEndEvent(comment=[H,D])) elif isinstance(A,MappingNode): E=A.tag==B.resolver.resolve(MappingNode,A.value,_B);C=A.comment;D=_A;I=_A if A.flow_style is _B: if C:I=C[0] if C and len(C)>2:D=C[2] B.emitter.emit(MappingStartEvent(G,A.tag,E,flow_style=A.flow_style,comment=A.comment,nr_items=len(A.value))) for(J,N)in A.value:B.serialize_node(J,A,_A);B.serialize_node(N,A,J) B.emitter.emit(MappingEndEvent(comment=[I,D])) B.resolver.ascend_resolver() def templated_id(s):return Serializer.ANCHOR_RE.match(s)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/setup.py0000644000000000000000000004505115156326621022610 0ustar00pbrochadpbrochadfrom __future__ import print_function,absolute_import,division,unicode_literals _T='bdist_wheel' _S='--version' _R='extra_packages' _Q='universal' _P='nested' _O='setting distdir {}/{}' _N='PYDISTBASE' _M='DVDEBUG' _L='LICENSE' _K='Jython' _J='install' _I='full_package_name' _H='__init__.py' _G='python' _F='setup.py' _E='utf-8' _D=True _C=False _B='.' _A=None import sys,os,datetime,traceback sys.path=[path for path in sys.path if path not in[os.getcwd(),'']] import platform from _ast import* from ast import parse from setuptools import setup,Extension,Distribution from setuptools.command import install_lib from setuptools.command.sdist import sdist as _sdist try:from setuptools.namespaces import Installer as NameSpaceInstaller except ImportError:msg='You should use the latest setuptools. The namespaces.py file that this setup.py uses was added in setuptools 28.7.0 (Oct 2016)';print(msg);sys.exit() if __name__!='__main__':raise NotImplementedError('should never include setup.py') full_package_name=_A if sys.version_info<(3,):string_type=basestring else:string_type=str if sys.version_info<(3,4): class Bytes:0 class NameConstant:0 if sys.version_info>=(3,8):from ast import Str,Num,Bytes,NameConstant if sys.version_info<(3,):open_kw=dict() else:open_kw=dict(encoding=_E) if sys.version_info<(2,7)or platform.python_implementation()==_K: class Set:0 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) def literal_eval(node_or_string): _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') def _convert(node): if isinstance(node,Str): if sys.version_info<(3,)and not isinstance(node.s,unicode):return node.s.decode(_E) return node.s elif isinstance(node,Bytes):return node.s elif isinstance(node,Num):return node.n elif isinstance(node,Tuple):return tuple(map(_convert,node.elts)) elif isinstance(node,List):return list(map(_convert,node.elts)) elif isinstance(node,Set):return set(map(_convert,node.elts)) elif isinstance(node,Dict):return dict((_convert(k),_convert(v))for(k,v)in zip(node.keys,node.values)) elif isinstance(node,NameConstant):return node.value elif sys.version_info<(3,4)and isinstance(node,Name): if node.id in _safe_names:return _safe_names[node.id] elif isinstance(node,UnaryOp)and isinstance(node.op,(UAdd,USub))and isinstance(node.operand,(Num,UnaryOp,BinOp)): operand=_convert(node.operand) if isinstance(node.op,UAdd):return+operand else:return-operand elif isinstance(node,BinOp)and isinstance(node.op,(Add,Sub))and isinstance(node.right,(Num,UnaryOp,BinOp))and isinstance(node.left,(Num,UnaryOp,BinOp)): left=_convert(node.left);right=_convert(node.right) if isinstance(node.op,Add):return left+right else:return left-right elif isinstance(node,Call): 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]) err=SyntaxError('malformed node or string: '+repr(node));err.filename='';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): data={} with open(fn,**open_kw)as fp: parsing=_C;lines=[] for line in fp.readlines(): if sys.version_info<(3,):line=line.decode(_E) if line.startswith('_package_data'): if'dict('in line:parsing=_G;lines.append('dict(\n') elif line.endswith('= {\n'):parsing=_G;lines.append('{\n') else:raise NotImplementedError continue if not parsing:continue if parsing==_G: if line.startswith(')')or line.startswith('}'): lines.append(line) try:data=literal_eval(''.join(lines)) except SyntaxError as e: context=2;from_line=e.lineno-(context+1);to_line=e.lineno+(context-1);w=len(str(to_line)) for(index,line)in enumerate(lines): if from_line<=index<=to_line: print('{0:{1}}: {2}'.format(index,w,line).encode(_E),end='') if index==e.lineno-1:print('{0:{1}} {2}^--- {3}'.format(' ',w,' '*e.offset,e.node)) raise break lines.append(line) else:raise NotImplementedError return data pkg_data=_package_data(__file__.replace(_F,_H)) exclude_files=[_F] def _check_convert_version(tup): ret_val=str(tup[0]);next_sep=_B;nr_digits=0;post_dev=_C for x in tup[1:]: if isinstance(x,int): nr_digits+=1 if nr_digits>2:raise ValueError('too many consecutive digits after '+ret_val) ret_val+=next_sep+str(x);next_sep=_B;continue first_letter=x[0].lower();next_sep='' if first_letter in'abcr': if post_dev:raise ValueError('release level specified after post/dev: '+x) nr_digits=0;ret_val+='rc'if first_letter=='r'else first_letter elif first_letter in'pd':nr_digits=1;post_dev=_D;ret_val+='.post'if first_letter=='p'else'.dev' else:raise ValueError('First letter of "'+x+'" not recognised') if nr_digits==1 and post_dev:ret_val+='0' return ret_val version_info=pkg_data['version_info'] version_str=_check_convert_version(version_info) class MyInstallLib(install_lib.install_lib): def install(self): fpp=pkg_data[_I].split(_B);full_exclude_files=[os.path.join(*fpp+[x])for x in exclude_files];alt_files=[];outfiles=install_lib.install_lib.install(self) for x in outfiles: for full_exclude_file in full_exclude_files: if full_exclude_file in x:os.remove(x);break else:alt_files.append(x) return alt_files class MySdist(_sdist): def initialize_options(self): _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(_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(_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)=='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(_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 self.command=x;break def pn(self,s): if sys.version_info<(3,)and isinstance(s,unicode):return s.encode(_E) return s @property def split(self): skip=[] if self._split is _A: fpn=self.full_package_name.split(_B);self._split=[] while fpn:self._split.insert(0,_B.join(fpn));fpn=fpn[:-1] for d in sorted(os.listdir(_B)): if not os.path.isdir(d)or d==self._split[0]or d[0]in'._':continue x=os.path.join(d,_H) if os.path.exists(x): pd=_package_data(x) 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 return self._split @property def namespace_packages(self):return self.split[:self.depth] def namespace_directories(self,depth=_A): res=[] for(index,d)in enumerate(self.split[:depth]): if index>0:d=os.path.join(*d.split(_B)) res.append(_B+d) return res @property def package_dir(self): d={self.full_package_name:_B} 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): directories=self.namespace_directories(self.depth) if not directories:return if not os.path.exists(directories[0]): for d in directories: os.mkdir(d) with open(os.path.join(d,_H),'w')as fp:fp.write('import pkg_resources\npkg_resources.declare_namespace(__name__)\n') def python_version(self): supported=self._pkg_data.get('supported') if supported is _A:return if len(supported)==1:minimum=supported[0] else: for x in supported: if x[0]==sys.version_info[0]:minimum=x;break else:return if sys.version_info>>>>> LICENSE file not found <<<<<\n\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) return _D def main(): A='tarfmt';dump_kw='--dump-kw' if dump_kw in sys.argv:import wheel,distutils,setuptools;print('python: ',sys.version);print('setuptools:',setuptools.__version__);print('distutils: ',distutils.__version__);print('wheel: ',wheel.__version__) 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[_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 _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: with open('README.rst')as fp:kw['long_description']=fp.read();kw['long_description_content_type']='text/x-rst' except Exception:pass if nsp.wheel(kw,setup):return for x in['-c','egg_info','--egg-base','pip-egg-info']: if x not in sys.argv:break else: for p in nsp.install_pre: import subprocess;setup_path=os.path.join(*p.split(_B)+[_F]);try_dir=os.path.dirname(sys.executable) while len(try_dir)>1: full_path_setup_py=os.path.join(try_dir,setup_path) if os.path.exists(full_path_setup_py):pip=sys.executable.replace(_G,'pip');cmd=[pip,_J,os.path.dirname(full_path_setup_py)];subprocess.check_output(cmd);break try_dir=os.path.dirname(try_dir) setup(**kw) main()././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/timestamp.py0000644000000000000000000000067015156326621023451 0ustar00pbrochadpbrochadfrom __future__ import print_function,absolute_import,division,unicode_literals import datetime,copy if False:from typing import Any,Dict,Optional,List class TimeStamp(datetime.datetime): 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././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/tokens.py0000644000000000000000000000752715156326621022761 0ustar00pbrochadpbrochadfrom __future__ import unicode_literals _D=', line: ' _C=False _B='_comment' _A=None if _C:from typing import Text,Any,Dict,Optional,List;from.error import StreamMark SHOWLINES=True class Token: __slots__='start_mark','end_mark','_comment' def __init__(A,start_mark,end_mark):A.start_mark=start_mark;A.end_mark=end_mark def __repr__(A): C=[A for A in A.__slots__ if not A.endswith('_mark')];C.sort();B=', '.join(['%s=%r'%(B,getattr(A,B))for B in C]) if SHOWLINES: try:B+=_D+str(A.start_mark.line) except:pass try:B+=', comment: '+str(A._comment) except:pass return'{}({})'.format(A.__class__.__name__,B) def add_post_comment(A,comment): if not hasattr(A,_B):A._comment=[_A,_A] A._comment[0]=comment def add_pre_comments(A,comments): if not hasattr(A,_B):A._comment=[_A,_A] assert A._comment[1]is _A;A._comment[1]=comments def get_comment(A):return getattr(A,_B,_A) @property def comment(self):return getattr(self,_B,_A) def move_comment(C,target,empty=_C): D=target;A=C.comment if A is _A:return if isinstance(D,(StreamEndToken,DocumentStartToken)):return delattr(C,_B);B=D.comment if not B: if empty:A=[A[0],A[1],_A,_A,A[0]] D._comment=A;return C if A[0]and B[0]or A[1]and B[1]:raise NotImplementedError('overlap in comment %r %r'%(A,B)) if A[0]:B[0]=A[0] if A[1]:B[1]=A[1] return C def split_comment(B): A=B.comment if A is _A or A[0]is _A:return C=[A[0],_A] if A[1]is _A:delattr(B,_B) return C class DirectiveToken(Token): __slots__='name','value';id='' def __init__(A,name,value,start_mark,end_mark):Token.__init__(A,start_mark,end_mark);A.name=name;A.value=value class DocumentStartToken(Token):__slots__=();id='' class DocumentEndToken(Token):__slots__=();id='' class StreamStartToken(Token): __slots__='encoding',;id='' def __init__(A,start_mark=_A,end_mark=_A,encoding=_A):Token.__init__(A,start_mark,end_mark);A.encoding=encoding class StreamEndToken(Token):__slots__=();id='' class BlockSequenceStartToken(Token):__slots__=();id='' class BlockMappingStartToken(Token):__slots__=();id='' class BlockEndToken(Token):__slots__=();id='' class FlowSequenceStartToken(Token):__slots__=();id='[' class FlowMappingStartToken(Token):__slots__=();id='{' class FlowSequenceEndToken(Token):__slots__=();id=']' class FlowMappingEndToken(Token):__slots__=();id='}' class KeyToken(Token):__slots__=();id='?' class ValueToken(Token):__slots__=();id=':' class BlockEntryToken(Token):__slots__=();id='-' class FlowEntryToken(Token):__slots__=();id=',' class AliasToken(Token): __slots__='value',;id='' def __init__(A,value,start_mark,end_mark):Token.__init__(A,start_mark,end_mark);A.value=value class AnchorToken(Token): __slots__='value',;id='' def __init__(A,value,start_mark,end_mark):Token.__init__(A,start_mark,end_mark);A.value=value class TagToken(Token): __slots__='value',;id='' def __init__(A,value,start_mark,end_mark):Token.__init__(A,start_mark,end_mark);A.value=value class ScalarToken(Token): __slots__='value','plain','style';id='' def __init__(A,value,plain,start_mark,end_mark,style=_A):Token.__init__(A,start_mark,end_mark);A.value=value;A.plain=plain;A.style=style class CommentToken(Token): __slots__='value','pre_done';id='' def __init__(A,value,start_mark,end_mark):Token.__init__(A,start_mark,end_mark);A.value=value def reset(A): B='pre_done' if hasattr(A,B):delattr(A,B) def __repr__(A): B='{!r}'.format(A.value) if SHOWLINES: try:B+=_D+str(A.start_mark.line);B+=', col: '+str(A.start_mark.column) except:pass return'CommentToken({})'.format(B) def __eq__(A,other): B=other if A.start_mark!=B.start_mark:return _C if A.end_mark!=B.end_mark:return _C if A.value!=B.value:return _C return True def __ne__(A,other):return not A.__eq__(other)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776273.0 dynaconf-3.2.13/dynaconf/vendor/ruamel/yaml/util.py0000644000000000000000000000401515156326621022420 0ustar00pbrochadpbrochadfrom __future__ import absolute_import,print_function _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,_A,lambda:B);return B object.__setattr__(A,_A,B) def __getattribute__(B,name): 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) 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 AE:G=A-E if J.endswith(':'): I=K(C);A=0 while C[A]==' ':A+=1 E=A;continue E=B if H is B and G is not B:H=G return O(F,**N),H,L def configobj_walker(cfg): B=cfg;from configobj import ConfigObj as D;assert isinstance(B,D) for A in B.initial_comment: if A.strip():yield A for C in _walk_section(B): if C.strip():yield C for A in B.final_comment: if A.strip():yield A def _walk_section(s,level=0): 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 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+=' '+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+=' '+B yield E for K in _walk_section(s[A],level=H+1):yield K././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9194582 dynaconf-3.2.13/dynaconf/vendor/toml/0000755000000000000000000000000015156326623017617 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/toml/__init__.py0000644000000000000000000000110715156326622021726 0ustar00pbrochadpbrochadfrom.import encoder,decoder __version__='0.10.1' _spec_='0.5.0' load=decoder.load loads=decoder.loads TomlDecoder=decoder.TomlDecoder TomlDecodeError=decoder.TomlDecodeError TomlPreserveCommentDecoder=decoder.TomlPreserveCommentDecoder dump=encoder.dump dumps=encoder.dumps TomlEncoder=encoder.TomlEncoder TomlArraySeparatorEncoder=encoder.TomlArraySeparatorEncoder TomlPreserveInlineDictEncoder=encoder.TomlPreserveInlineDictEncoder TomlNumpyEncoder=encoder.TomlNumpyEncoder TomlPreserveCommentEncoder=encoder.TomlPreserveCommentEncoder TomlPathlibEncoder=encoder.TomlPathlibEncoder././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/toml/decoder.py0000644000000000000000000004000715156326622021576 0ustar00pbrochadpbrochad_Q='Reserved escape sequence used' _P='false' _O='true' _N=',' _M=']' _L=' ' _K='{' _J='=' _I='[' _H='\\' _G='\n' _F=None _E='.' _D="'" _C='"' _B=True _A=False import datetime,io from os import linesep import re,sys from.tz import TomlTz if sys.version_info<(3,):_range=xrange else:unicode=str;_range=range;basestring=str;unichr=chr def _detect_pathlib_path(p): if(3,4)<=sys.version_info: import pathlib as A if isinstance(p,A.PurePath):return _B return _A def _ispath(p): if isinstance(p,(bytes,basestring)):return _B return _detect_pathlib_path(p) def _getpath(p): if(3,6)<=sys.version_info:import os;return os.fspath(p) if _detect_pathlib_path(p):return str(p) return p try:FNFError=FileNotFoundError except NameError:FNFError=IOError TIME_RE=re.compile('([0-9]{2}):([0-9]{2}):([0-9]{2})(\\.([0-9]{3,6}))?') class TomlDecodeError(ValueError): def __init__(A,msg,doc,pos):C=doc;B=pos;D=C.count(_G,0,B)+1;E=B-C.rfind(_G,0,B);F='{} (line {} column {} char {})'.format(msg,D,E,B);ValueError.__init__(A,F);A.msg=msg;A.doc=C;A.pos=B;A.lineno=D;A.colno=E _number_with_underscores=re.compile('([0-9])(_([0-9]))*') class CommentValue: def __init__(A,val,comment,beginline,_dict):A.val=val;B=_G if beginline else _L;A.comment=B+comment;A._dict=_dict def __getitem__(A,key):return A.val[key] def __setitem__(A,key,value):A.val[key]=value def dump(A,dump_value_func): B=dump_value_func(A.val) if isinstance(A.val,A._dict):return A.comment+_G+unicode(B) else:return unicode(B)+A.comment def _strictly_valid_num(n): n=n.strip() if not n: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[_E,'o','b','x']:return _A if n[0]=='+'or n[0]=='-': n=n[1:] if len(n)>1 and n[0]=='0'and n[1]!=_E:return _A if'__'in n:return _A return _B def load(f,_dict=dict,decoder=_F): B=_dict;A=decoder if _ispath(f): with io.open(_getpath(f),encoding='utf-8')as G:return loads(G.read(),B,A) elif isinstance(f,list): from os import path as D;from warnings import warn if not[A for A in f if D.exists(A)]:C='Load expects a list to contain filenames only.';C+=linesep;C+='The list needs to contain the path of at least one existing file.';raise FNFError(C) if A is _F:A=TomlDecoder(B) E=A.get_empty_table() for F in f: if D.exists(F):E.update(load(F,B,A)) else:warn('Non-existent filename in list with at least one valid filename') return E else: try:return loads(f.read(),B,A) 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=_F): o="Invalid group name '";K=decoder;d=[] if K is _F:K=TomlDecoder(_dict) e=K.get_empty_table();G=e if not isinstance(s,basestring):raise TypeError('Expecting something like a string') if not isinstance(s,unicode):s=s.decode('utf8') I=s;B=list(s);b=0;J=_A;Q='';D=_A;L=_A;V=_B;U=_A;W=_A;O=0;c='';j='';k=1 for(A,E)in enumerate(B): if E=='\r'and B[A+1]==_G:B[A]=_L;continue if O: c+=E if E==_G:raise TomlDecodeError('Key name found without value. Reached end of line.',I,A) if J: if E==Q: S=_A;F=1 while A>=F and B[A-F]==_H:S=not S;F+=1 if not S:O=2;J=_A;Q='' continue elif O==1: if E.isspace():O=2;continue elif E==_E:W=_B;continue elif E.isalnum()or E=='_'or E=='-':continue elif W and B[A-1]==_E and(E==_C or E==_D):J=_B;Q=E;continue elif O==2: if E.isspace(): if W: X=B[A+1] if not X.isspace()and X!=_E:O=1 continue if E==_E: W=_B;X=B[A+1] if not X.isspace()and X!=_E:O=1 continue if E==_J:O=0;j=c[:-1].rstrip();c='';W=_A else:raise TomlDecodeError("Found invalid character in key name: '"+E+"'. Try quoting the key name.",I,A) if E==_D and Q!=_C: F=1 try: while B[A-F]==_D: F+=1 if F==3:break except IndexError:pass if F==3:D=not D;J=D else:J=not J if J:Q=_D else:Q='' if E==_C and Q!=_D: S=_A;F=1;f=_A try: while B[A-F]==_C: F+=1 if F==3:f=_B;break if F==1 or F==3 and f: while B[A-F]==_H:S=not S;F+=1 except IndexError:pass if not S: if f:D=not D;J=D else:J=not J if J:Q=_C else:Q='' if E=='#'and(not J and not U and not L): R=A;l='' try: while B[R]!=_G:l+=s[R];B[R]=_L;R+=1 except IndexError:break if not b:K.preserve_comment(k,j,l,V) if E==_I and(not J and not U and not L): if V: if len(B)>A+1 and B[A+1]==_I:L=_B else:U=_B else:b+=1 if E==_M and not J: if U:U=_A elif L: if B[A-1]==_M:L=_A else:b-=1 if E==_G: if J or D: if not D:raise TomlDecodeError('Unbalanced quotes',I,A) if(B[A-1]==_D or B[A-1]==_C)and B[A-2]==B[A-1]: B[A]=B[A-1] if B[A-3]==B[A-1]:B[A-3]=_L elif b:B[A]=_L else:V=_B k+=1 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) O=1;c+=E if O:raise TomlDecodeError('Key name found without value. Reached end of file.',I,len(s)) if J:raise TomlDecodeError('Unterminated string found. Reached end of file.',I,len(s)) s=''.join(B);s=s.split(_G);T=_F;D='';P=_A;N=0 for(g,C)in enumerate(s): if g>0:N+=len(s[g-1])+1 K.embed_comments(g,G) if not D or P or _G not in D:C=C.strip() if C==''and(not T or P):continue if T: if P:D+=C else:D+=C P=_A;h=_A 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:p,r=K.load_value(D) except ValueError as Y:raise TomlDecodeError(str(Y),I,N) G[T]=p;T=_F;D='' else: F=len(D)-1 while F>-1 and D[F]==_H:P=not P;F-=1 if P:D=D[:-1] else:D+=_G continue if C[0]==_I: L=_A 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;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) if len(C)0 and(H[A][0]==_C or H[A][0]==_D): a=H[A];R=A+1 while not a[0]==a[-1]: R+=1 if R>len(H)+2:raise TomlDecodeError(o+a+"' Something "+'went wrong.',I,N) a=_E.join(H[A:R]).strip() H[A]=a[1:-1];H[A+1:R]=[] 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)): M=H[A] if M=='':raise TomlDecodeError("Can't have a keygroup with an empty name",I,N) try: G[M] if A==len(H)-1: if M in d: d.remove(M) if L:raise TomlDecodeError("An implicitly defined table can't be an array",I,N) elif L:G[M].append(K.get_empty_table()) else:raise TomlDecodeError('What? '+M+' already exists?'+str(G),I,N) except TypeError: G=G[-1] if M not in G: G[M]=K.get_empty_table() if A==len(H)-1 and L:G[M]=[K.get_empty_table()] except KeyError: if A!=len(H)-1:d.append(M) G[M]=K.get_empty_table() if A==len(H)-1 and L:G[M]=[K.get_empty_table()] G=G[M] if L: try:G=G[-1] except KeyError:pass elif C[0]==_K: 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: try:n=K.load_line(C,G,T,P) except ValueError as Y:raise TomlDecodeError(str(Y),I,N) if n is not _F:T,D,P=n return e def _load_date(val): A=val;G=0;F=_F try: if len(A)>19: if A[19]==_E: if A[-1].upper()=='Z':C=A[20:-1];D='Z' else: B=A[20:] 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=_F;C=B if D is not _F:F=TomlTz(D) G=int(int(C)*10**(6-len(C))) else:F=TomlTz(A[19:]) except ValueError:F=_F if'-'not in A[1:]:return 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) except ValueError:return return H def _load_unicode_escapes(v,hexbytes,prefix): G='Invalid escape sequence: ';E=prefix;C=_A;A=len(v)-1 while A>-1 and v[A]==_H:C=not C;A-=1 for D in hexbytes: if C: C=_A;A=len(D)-1 while A>-1 and D[A]==_H:C=not C;A-=1 v+=E;v+=D;continue B='';A=0;F=4 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','\t',_C] _escape_to_escapedchars=dict(zip(_escapes,_escapedchars)) def _unescape(v): A=0;B=_A while A0: C=B.pop(0) 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(_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: G=E.load_line(F,currentlevel,multikey,multibackslash) if G is not _F:break def _get_split_on_quotes(F,line): A=line.split(_C);D=_A;C=[] if len(A)>1 and _D in A[0]: B=A[0].split(_D);A=A[1:] while len(B)%2==0 and len(A): B[-1]+=_C+A[0];A=A[1:] if _D in B[-1]:B=B[:-1]+B[-1].split(_D) C+=B for E in A: if D:C.append(E) else:C+=E.split(_D);D=not D return C def load_line(E,line,currentlevel,multikey,multibackslash): 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('_','') 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 _F:break if TIME_RE.match(A[-1]):break 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 _E in A[0]: if _C in A[0]or _D in A[0]: M=E._get_split_on_quotes(A[0]);C=_A;B=[] for F in M: if C:B.append(F) else:B+=[A.strip()for A in F.split(_E)] C=not C else:B=A[0].split(_E) while B[-1]=='':B=B[:-1] for I in B[:-1]: if I=='':continue if I not in D:D[I]=E.get_empty_table() 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,R=E._load_line_multiline_str(A[1]) if 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: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]]=S def _load_line_multiline_str(C,p): B=0 if len(p)<3:return-1,B if p[0]==_I and(p.strip()[-1]!=_M and C._load_array_isstrarray(p)): A=p[1:].strip().split(_N) while len(A)>1 and A[-1][0]!=_C and A[-1][0]!=_D:A=A[:-2]+[A[-2]+_N+A[-1]] A=A[-1];B=len(p)-len(A);p=A if p[0]!=_C and p[0]!=_D:return-1,B if p[1]!=p[0]or p[2]!=p[0]:return-1,B 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): V='float';U='int';T='bool' if not v:raise ValueError('Empty value is invalid') 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 I=_A for J in B: if J=='': if G:H+=1 else:I=_B else: K=_A try: A=-1;N=J[A] while N==_H:K=not K;A-=1;N=J[A] except IndexError:pass if not K: if I:raise ValueError('Found tokens after a closed '+'string. Invalid TOML.') elif not G or H>1:I=_B else:H=0 if F==_C: 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(_Q) if C:C=_A 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):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 _F:return R,'date' if not strictly_valid:raise ValueError('Weirdness with leading zeroes or underscores in your number.') 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 _E in v or'x'not in v and('e'in v or'E'in v): if _E in v and v.split(_E,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=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): if len(s)==0:return _B if s[-1]!=s[0]:return _A A=-2;B=_A while len(s)+A>0: if s[A]==_H:B=not B;A-=1 else:break return not B def _load_array_isstrarray(A,a): a=a[1:-1].strip() if a!=''and(a[0]==_C or a[0]==_D):return _B return _A def load_array(H,a): I=_F;N=[];a=a.strip() if _I not in a[1:-1]or''!=a[1:-1].split(_I)[0].strip(): Q=H._load_array_isstrarray(a) if not a[1:-1].strip().startswith(_K):a=a[1:-1].split(_N) else: O=[];E=1;A=2;J=1 if a[E]==_K else 0;F=_A while A-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]!='}':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 E2 and C[0]==C[1]==C[2]and C[-2]!=C[0]and C[-3]!=C[0]: a[B]=a[B]+_N+a[B+1];C=a[B].strip() if B=(3,):unicode=str def dump(o,f,encoder=_A): if not f.write:raise TypeError('You can only dump an object to a file descriptor') A=dumps(o,encoder=encoder);f.write(A);return A def dumps(o,encoder=_A): C=encoder;A='' if C is _A:C=TomlEncoder(o.__class__) B,D=C.dump_sections(o,'');A+=B;G=[id(o)] while D: H=[id(A)for A in D] for K in G: if K in H:raise ValueError('Circular reference detected') G+=H;I=C.get_empty_table() 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+='\n' A+='['+E+']\n' if B:A+=B for J in F:I[E+'.'+J]=F[J] D=I return A def _dump_str(v): 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:] 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('\\\\',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=_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='[' 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+_C+G) C+='{ '+', '.join(D)+' }\n';return C else:return unicode(B.dump_value(A)) def dump_value(B,v): A=B.dump_funcs.get(type(v)) if A is _A and hasattr(v,'__iter__'):A=B.dump_funcs[list] 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]!='.':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=_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='\n';G+='[['+D+B+']]\n';I,J=C.dump_sections(L,D+B) if 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+'.'+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+_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=_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='[' for A in v:B.append(D.dump_value(A)) while B!=[]: E=[] for A in B: if isinstance(A,list): for F in A:E.append(F) else:C+=' '+unicode(A)+D.separator B=E C+=']';return C class TomlNumpyEncoder(TomlEncoder): 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=_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): if(3,4)<=sys.version_info: import pathlib as B if isinstance(v,B.PurePath):v=str(v) return super(TomlPathlibEncoder,A).dump_value(v)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/toml/ordered.py0000644000000000000000000000044515156326622021617 0ustar00pbrochadpbrochadfrom collections import OrderedDict from.import TomlEncoder from.import TomlDecoder class TomlOrderedDecoder(TomlDecoder): def __init__(A):super(A.__class__,A).__init__(_dict=OrderedDict) class TomlOrderedEncoder(TomlEncoder): def __init__(A):super(A.__class__,A).__init__(_dict=OrderedDict)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/toml/tz.py0000644000000000000000000000066315156326622020632 0ustar00pbrochadpbrochadfrom datetime import tzinfo,timedelta class TomlTz(tzinfo): def __init__(A,toml_offset): B=toml_offset if B=='Z':A._raw_offset='+00:00' else:A._raw_offset=B A._sign=-1 if A._raw_offset[0]=='-'else 1;A._hours=int(A._raw_offset[1:3]);A._minutes=int(A._raw_offset[4:6]) def tzname(A,dt):return'UTC'+A._raw_offset def utcoffset(A,dt):return A._sign*timedelta(hours=A._hours,minutes=A._minutes) def dst(A,dt):return timedelta(0)././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9198642 dynaconf-3.2.13/dynaconf/vendor/tomllib/0000755000000000000000000000000015156326623020306 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/tomllib/__init__.py0000644000000000000000000000025115156326622022414 0ustar00pbrochadpbrochad__all__='loads','load','TOMLDecodeError','dump','dumps' from._parser import TOMLDecodeError,load,loads from._writer import dump,dumps TOMLDecodeError.__module__=__name__././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/tomllib/_parser.py0000644000000000000000000002634615156326622022325 0ustar00pbrochadpbrochadfrom __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././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/tomllib/_re.py0000644000000000000000000000322615156326622021427 0ustar00pbrochadpbrochadfrom __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\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)././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/tomllib/_types.py0000644000000000000000000000013715156326622022163 0ustar00pbrochadpbrochadfrom typing import Any,Callable,Tuple ParseFloat=Callable[[str],Any] Key=Tuple[str,...] Pos=int././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf/vendor/tomllib/_writer.py0000644000000000000000000000677115156326622022345 0ustar00pbrochadpbrochadfrom __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(A,G)for A in A):F.extend((B,A,_C)for A 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(B)} = {format_literal(C,A)}"for(B,C)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(A,dict)for A 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././@PaxHeader0000000000000000000000000000007200000000000010214 xustar0015 uid=4214970 15 gid=4214970 28 mtime=1773776274.9222023 dynaconf-3.2.13/dynaconf.egg-info/0000755000000000000000000000000015156326623017041 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf.egg-info/PKG-INFO0000644000000000000000000002231115156326622020134 0ustar00pbrochadpbrochadMetadata-Version: 2.4 Name: dynaconf Version: 3.2.13 Summary: The dynamic configurator for your Python Project Home-page: https://github.com/dynaconf/dynaconf Author: Bruno Rocha Author-email: rochacbruno@gmail.com License: MIT Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django Classifier: Framework :: Flask Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English 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.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Utilities Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=3.8 Description-Content-Type: text/markdown 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 Provides-Extra: redis Requires-Dist: redis; extra == "redis" Provides-Extra: vault Requires-Dist: hvac; extra == "vault" Provides-Extra: yaml Requires-Dist: ruamel.yaml; extra == "yaml" Provides-Extra: toml Requires-Dist: toml; extra == "toml" Provides-Extra: ini Requires-Dist: configobj; extra == "ini" Provides-Extra: configobj Requires-Dist: configobj; extra == "configobj" Provides-Extra: all Requires-Dist: redis; extra == "all" Requires-Dist: ruamel.yaml; extra == "all" Requires-Dist: configobj; extra == "all" Requires-Dist: hvac; extra == "all" Provides-Extra: test Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-cov; extra == "test" Requires-Dist: pytest-xdist; extra == "test" Requires-Dist: pytest-mock; extra == "test" Requires-Dist: radon; extra == "test" Requires-Dist: flask>=0.12; extra == "test" Requires-Dist: django; extra == "test" Requires-Dist: python-dotenv; extra == "test" Requires-Dist: toml; extra == "test" Requires-Dist: redis; extra == "test" Requires-Dist: hvac>=1.1.0; extra == "test" Requires-Dist: configobj; extra == "test" Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: description-content-type Dynamic: home-page Dynamic: license Dynamic: license-file Dynamic: platform Dynamic: provides-extra Dynamic: requires-python Dynamic: summary

dynaconf. new logo

> **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/) ![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 - Inspired by the [12-factor application guide](https://12factor.net/config) - Settings management (default values, validation, parsing, templating) - Protection of sensitive information (passwords/tokens) - Multiple file formats `toml|yaml|json|ini|py` and also customizable loaders. - Full support for environment variables to override existing settings (dotenv support included). - Optional layered system for multi environments `[default, development, testing, production]` - Built-in support for Hashicorp Vault and Redis as settings and secrets storage. - Built-in extensions for **Django** and **Flask** web frameworks. - CLI for common operations such as `init, list, write, validate, export`. - full docs on https://dynaconf.com ### Install ```bash $ pip install dynaconf ``` #### Initialize Dynaconf on project root directory ```plain $ cd path/to/your/project/ $ dynaconf init -f toml ⚙️ Configuring your Dynaconf environment ------------------------------------------ 🐍 The file `config.py` was generated. 🎛️ settings.toml created to hold your settings. 🔑 .secrets.toml created to hold your secrets. 🙈 the .secrets.* is also included in `.gitignore` beware to not push your secrets to a public repo. 🎉 Dynaconf is configured! read more on https://dynaconf.com ``` > **TIP:** You can select `toml|yaml|json|ini|py` on `dynaconf init -f ` **toml** is the default and also the most recommended format for configuration. #### Dynaconf init creates the following files ```plain . ├── 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 settings (optional) ``` On the file `config.py` Dynaconf init generates the following boilerpate ```py from dynaconf import Dynaconf settings = Dynaconf( envvar_prefix="DYNACONF", # export envvars with `export DYNACONF_FOO=bar`. settings_files=['settings.yaml', '.secrets.yaml'], # Load files in the given order. ) ``` > **TIP:** You can create the files yourself instead of using the `init` command as shown above and you can give any name you want instead of the default `config.py` (the file must be in your importable python path) - See more options that you can pass to `Dynaconf` class initializer on https://dynaconf.com #### Using Dynaconf Put your settings on `settings.{toml|yaml|ini|json|py}` ```toml username = "admin" port = 5555 database = {name='mydb', schema='main'} ``` Put sensitive information on `.secrets.{toml|yaml|ini|json|py}` ```toml password = "secret123" ``` > **IMPORTANT:** `dynaconf init` command puts the `.secrets.*` in your `.gitignore` to avoid it to be exposed on public repos but it is your responsibility to keep it safe in your local environment, also the recommendation for production environments is to use the built-in support for Hashicorp Vault service for password and tokens. Optionally you can now use environment variables to override values per execution or per environment. ```bash # override `port` from settings.toml file and automatically casts as `int` value. export DYNACONF_PORT=9900 ``` On your code import the `settings` object ```py from path.to.project.config import settings # Reading the settings settings.username == "admin" # dot notation with multi nesting support settings.PORT == 9900 # case insensitive settings['password'] == "secret123" # dict like access settings.get("nonexisting", "default value") # Default values just like a dict settings.databases.name == "mydb" # Nested key traversing settings['databases.schema'] == "main" # Nested key traversing ``` ## More - Settings Schema Validation - Custom Settings Loaders - Vault Services - Template substitutions - etc... There is a lot more you can do, **read the docs:** http://dynaconf.com ## Contribute 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 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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf.egg-info/SOURCES.txt0000644000000000000000000001125015156326622020723 0ustar00pbrochadpbrochad3.x-release-notes.md CHANGELOG.md CONTRIBUTING.md CONTRIBUTORS.md LICENSE MANIFEST.in README.md setup.cfg setup.py dynaconf/VERSION dynaconf/__init__.py dynaconf/__main__.py dynaconf/base.py dynaconf/cli.py dynaconf/constants.py dynaconf/default_settings.py dynaconf/hooking.py dynaconf/test_settings.py dynaconf/validator.py dynaconf/validator_conditions.py dynaconf.egg-info/PKG-INFO dynaconf.egg-info/SOURCES.txt dynaconf.egg-info/dependency_links.txt dynaconf.egg-info/entry_points.txt dynaconf.egg-info/not-zip-safe dynaconf.egg-info/requires.txt dynaconf.egg-info/top_level.txt dynaconf/contrib/__init__.py dynaconf/contrib/django_dynaconf_v2.py dynaconf/contrib/flask_dynaconf.py dynaconf/loaders/__init__.py dynaconf/loaders/base.py dynaconf/loaders/env_loader.py dynaconf/loaders/ini_loader.py dynaconf/loaders/json_loader.py dynaconf/loaders/py_loader.py dynaconf/loaders/redis_loader.py dynaconf/loaders/toml_loader.py dynaconf/loaders/vault_loader.py dynaconf/loaders/yaml_loader.py dynaconf/strategies/__init__.py dynaconf/strategies/filtering.py dynaconf/utils/__init__.py dynaconf/utils/boxing.py dynaconf/utils/files.py dynaconf/utils/functional.py dynaconf/utils/inspect.py dynaconf/utils/parse_conf.py dynaconf/vendor/__init__.py dynaconf/vendor/box/__init__.py dynaconf/vendor/box/box.py dynaconf/vendor/box/box_list.py dynaconf/vendor/box/config_box.py dynaconf/vendor/box/converters.py dynaconf/vendor/box/exceptions.py dynaconf/vendor/box/from_file.py dynaconf/vendor/box/shorthand_box.py dynaconf/vendor/click/__init__.py dynaconf/vendor/click/_bashcomplete.py dynaconf/vendor/click/_compat.py dynaconf/vendor/click/_termui_impl.py dynaconf/vendor/click/_textwrap.py dynaconf/vendor/click/_unicodefun.py dynaconf/vendor/click/_winconsole.py dynaconf/vendor/click/core.py dynaconf/vendor/click/decorators.py dynaconf/vendor/click/exceptions.py dynaconf/vendor/click/formatting.py dynaconf/vendor/click/globals.py dynaconf/vendor/click/parser.py dynaconf/vendor/click/termui.py dynaconf/vendor/click/testing.py dynaconf/vendor/click/types.py dynaconf/vendor/click/utils.py dynaconf/vendor/dotenv/__init__.py dynaconf/vendor/dotenv/cli.py dynaconf/vendor/dotenv/compat.py dynaconf/vendor/dotenv/ipython.py dynaconf/vendor/dotenv/main.py dynaconf/vendor/dotenv/parser.py dynaconf/vendor/dotenv/py.typed dynaconf/vendor/dotenv/version.py dynaconf/vendor/ruamel/__init__.py dynaconf/vendor/ruamel/yaml/__init__.py dynaconf/vendor/ruamel/yaml/anchor.py dynaconf/vendor/ruamel/yaml/comments.py dynaconf/vendor/ruamel/yaml/compat.py dynaconf/vendor/ruamel/yaml/composer.py dynaconf/vendor/ruamel/yaml/configobjwalker.py dynaconf/vendor/ruamel/yaml/constructor.py dynaconf/vendor/ruamel/yaml/cyaml.py dynaconf/vendor/ruamel/yaml/dumper.py dynaconf/vendor/ruamel/yaml/emitter.py dynaconf/vendor/ruamel/yaml/error.py dynaconf/vendor/ruamel/yaml/events.py dynaconf/vendor/ruamel/yaml/loader.py dynaconf/vendor/ruamel/yaml/main.py dynaconf/vendor/ruamel/yaml/nodes.py dynaconf/vendor/ruamel/yaml/parser.py dynaconf/vendor/ruamel/yaml/py.typed dynaconf/vendor/ruamel/yaml/reader.py dynaconf/vendor/ruamel/yaml/representer.py dynaconf/vendor/ruamel/yaml/resolver.py dynaconf/vendor/ruamel/yaml/scalarbool.py dynaconf/vendor/ruamel/yaml/scalarfloat.py dynaconf/vendor/ruamel/yaml/scalarint.py dynaconf/vendor/ruamel/yaml/scalarstring.py dynaconf/vendor/ruamel/yaml/scanner.py dynaconf/vendor/ruamel/yaml/serializer.py dynaconf/vendor/ruamel/yaml/setup.py dynaconf/vendor/ruamel/yaml/timestamp.py dynaconf/vendor/ruamel/yaml/tokens.py dynaconf/vendor/ruamel/yaml/util.py 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 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_hooking.py tests/test_ini_loader.py tests/test_inspect.py tests/test_json_loader.py tests/test_nested_loading.py tests/test_py_loader.py tests/test_redis.py tests/test_settings_loader.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././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf.egg-info/dependency_links.txt0000644000000000000000000000000115156326622023106 0ustar00pbrochadpbrochad ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf.egg-info/entry_points.txt0000644000000000000000000000005715156326622022340 0ustar00pbrochadpbrochad[console_scripts] dynaconf = dynaconf.cli:main ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf.egg-info/not-zip-safe0000644000000000000000000000000115156326622021266 0ustar00pbrochadpbrochad ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf.egg-info/requires.txt0000644000000000000000000000041115156326622021434 0ustar00pbrochadpbrochad [all] redis ruamel.yaml configobj hvac [configobj] configobj [ini] configobj [redis] redis [test] pytest pytest-cov pytest-xdist pytest-mock radon flask>=0.12 django python-dotenv toml redis hvac>=1.1.0 configobj [toml] toml [vault] hvac [yaml] ruamel.yaml ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776274.0 dynaconf-3.2.13/dynaconf.egg-info/top_level.txt0000644000000000000000000000001115156326622021562 0ustar00pbrochadpbrochaddynaconf ././@PaxHeader0000000000000000000000000000007100000000000010213 xustar0015 uid=4214970 15 gid=4214970 27 mtime=1773776274.924025 dynaconf-3.2.13/setup.cfg0000644000000000000000000000052615156326623015372 0ustar00pbrochadpbrochad[bdist_wheel] universal = 1 [mypy] ignore_missing_imports = True [mypy-*.vendor.*] ignore_errors = True [mypy-tests.*] ignore_errors = True [mypy-example.*] ignore_errors = True [mypy-*.contrib.*] ignore_errors = True [mypy-*.utils.*] ignore_errors = True [mypy-*.loaders.*] ignore_errors = True [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/setup.py0000644000000000000000000000562215156324234015261 0ustar00pbrochadpbrochadfrom __future__ import annotations import os from setuptools import find_packages from setuptools import setup def read(*names, **kwargs): """Read a file.""" content = "" with open( os.path.join(os.path.dirname(__file__), *names), encoding=kwargs.get("encoding", "utf8"), ) as open_file: content = open_file.read().strip() return content test_requirements = [ "pytest", "pytest-cov", "pytest-xdist", "pytest-mock", "radon", "flask>=0.12", "django", "python-dotenv", "toml", "redis", "hvac>=1.1.0", "configobj", ] setup( name="dynaconf", version=read("dynaconf", "VERSION"), 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", long_description=read("README.md"), long_description_content_type="text/markdown", packages=find_packages( exclude=[ "tests", "tests.*", "tests_functional", "tests_functional.*", "docs", "legacy_docs", "legacy_docs.*", "docs.*", "build", "build.*", "dynaconf.vendor_src", "dynaconf/vendor_src", "dynaconf.vendor_src.*", "dynaconf/vendor_src/*", ] ), include_package_data=True, zip_safe=False, platforms="any", tests_require=test_requirements, extras_require={ "redis": ["redis"], "vault": ["hvac"], "yaml": ["ruamel.yaml"], "toml": ["toml"], "ini": ["configobj"], "configobj": ["configobj"], "all": ["redis", "ruamel.yaml", "configobj", "hvac"], "test": test_requirements, }, python_requires=">=3.8", entry_points={"console_scripts": ["dynaconf=dynaconf.cli:main"]}, setup_requires=["setuptools>=38.6.0"], classifiers=[ "Development Status :: 5 - Production/Stable", "Framework :: Django", "Framework :: Flask", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Utilities", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", ], ) ././@PaxHeader0000000000000000000000000000007100000000000010213 xustar0015 uid=4214970 15 gid=4214970 27 mtime=1773776274.921593 dynaconf-3.2.13/tests/0000755000000000000000000000000015156326623014710 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_base.py0000644000000000000000000015106015156324234017232 0ustar00pbrochadpbrochadfrom __future__ import annotations import os import pytest from dynaconf import Dynaconf from dynaconf import LazySettings from dynaconf import ValidationError from dynaconf import Validator from dynaconf.loaders import toml_loader from dynaconf.loaders import yaml_loader from dynaconf.strategies.filtering import PrefixFilter from dynaconf.utils.boxing import DynaBox 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_populate_obj_convert_to_dict(settings): class Obj: pass # first make sure regular populate brings in Box and BoxList obj = Obj() settings.populate_obj(obj) assert isinstance(obj.ADICT, DynaBox) assert isinstance(obj.ALIST, BoxList) assert isinstance(obj.ADICT.to_yaml(), str) # now make sure convert_to_dict=True brings in dict and list obj = Obj() settings.populate_obj(obj, convert_to_dict=True) assert isinstance(obj.ADICT, dict) assert isinstance(obj.ALIST, list) with pytest.raises(AttributeError): assert isinstance(obj.ADICT.to_yaml(), str) 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_allow_underline(settings): settings.setenv("COOL_env") assert settings.current_env == "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_set_merge_false(): # data list_old = [1, 2, 3] list_new = [4, 5, 6] list_expected = [4, 5, 6] dict_old = {"a": {"b": "B"}} dict_new = {"a": {"c": "C"}} dict_expected = {"a": {"c": "C"}} # environment = False settings_a = Dynaconf( environments=False, merge_enabled=False, ) settings_a.set("default", {"dicty": dict_old, "listy": list_old}) settings_a.set("default", {"dicty": dict_new, "listy": list_new}) assert settings_a.default.dicty == dict_expected assert settings_a.default.listy == list_expected # environment = True settings_b = Dynaconf( environments=True, merge_enabled=False, ) settings_b.set("test", {"dicty": dict_old, "listy": list_old}) settings_b.set("test", {"dicty": dict_new, "listy": list_new}) assert settings_b.test.dicty == dict_expected assert settings_b.test.listy == list_expected 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_local_set_merge_false_dict(): # environment = False settings_a = Dynaconf( environments=False, merge_enabled=True, ) dict_old = {"a": {"b": {"key_old": "from_old"}}} dict_new = {"a": {"b": {"key_new": "from_new", "dynaconf_merge": False}}} settings_a.set("default", {"dicty": dict_old, "listy": [1, 2, 3]}) settings_a.set("default", {"dicty": dict_new, "listy": [9999]}) assert settings_a.default.dicty.a.b == {"key_new": "from_new"} assert settings_a.default.listy == [1, 2, 3, 9999] # environment = True settings_b = Dynaconf( environments=True, merge_enabled=True, ) dict_old = {"a": {"b": {"key_old": "from_old"}}} dict_new = {"a": {"b": {"key_new": "from_new", "dynaconf_merge": False}}} settings_b.set("test", {"dicty": dict_old, "listy": [1, 2, 3]}) settings_b.set("test", {"dicty": dict_new, "listy": [9999]}) assert settings_b.test.dicty.a.b == {"key_new": "from_new"} assert settings_b.test.listy == [1, 2, 3, 9999] 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("b_list", "@merge 7, 8, 9") assert settings.B_LIST == [ 1, 2, 3, 4, 5, 6.6, False, "foo", "bar", "zaz", 7, 8, 9, ] settings.set("b_list", "@merge '10','11','12'") assert settings.B_LIST == [ 1, 2, 3, 4, 5, 6.6, False, "foo", "bar", "zaz", 7, 8, 9, "10", "11", "12", ] 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_set_insert_token(tmpdir): data = { "a_list": [1, 2], "b_list": [1], "a_dict": {"name": "Bruno", "teams": ["dev"]}, "list_of_dicts": [{"number": 1}], } 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", "teams": ["dev"]} assert settings.A_DICT.name == "Bruno" assert settings.A_DICT.teams == ["dev"] settings.set("a_list", "@insert 0 0") assert settings.A_LIST == [0, 1, 2] settings.set("a_list", "@insert 1 1.5") assert settings.A_LIST == [0, 1.5, 1, 2] settings.set("a_list", "@insert 4 4") assert settings.A_LIST == [0, 1.5, 1, 2, 4] settings.set("b_list", "@insert 0 42") assert settings.B_LIST == [42, 1] settings.set("b_list", "@insert 1 43") assert settings.B_LIST == [42, 43, 1] settings.set("a_dict.teams", "@insert 0 'admin'") assert settings.A_DICT.teams == ["admin", "dev"] settings.set("a_dict.teams", "@insert 1 'staff'") assert settings.A_DICT.teams == ["admin", "staff", "dev"] settings.set("a_dict.teams", "@insert 3 'user'") assert settings.A_DICT.teams == ["admin", "staff", "dev", "user"] settings.set("a_dict.teams", "@insert 0 'admin'") assert settings.A_DICT.teams == ["admin", "admin", "staff", "dev", "user"] settings.set("new_key", "@insert 0 'foo'") assert settings.NEW_KEY == ["foo"] settings.set("new_key", "@insert 'bar'") assert settings.NEW_KEY == ["bar", "foo"] settings.set("new_key", "@insert 42") assert settings.NEW_KEY == [42, "bar", "foo"] settings.set("new_key", "@insert -1 'last'") assert settings.NEW_KEY == [42, "bar", "last", "foo"] settings.set("list_of_dicts", '@insert 0 @json {"number": 0}') assert settings.LIST_OF_DICTS == [{"number": 0}, {"number": 1}] settings.set("list_of_dicts", "@insert 2 {number=2}") # valid toml assert settings.LIST_OF_DICTS == [ {"number": 0}, {"number": 1}, {"number": 2}, ] def test_set_with_non_str_types(): """This replicates issue #1005 in a simplified setup.""" settings = Dynaconf(merge_enabled=True) settings.set("a", {"b": {1: "foo"}}) settings.set("a", {"b": {"c": "bar"}}) assert settings["a"]["b"][1] == "foo" assert settings["a"]["b"]["c"] == "bar" def test_set_with_non_str_types_on_first_level(): """Non-str key types on first level.""" settings = Dynaconf(merge_enabled=True) settings.set(1, {"b": {1: "foo"}}) settings.set("a", {"1": {1: "foo"}}) assert settings[1]["b"][1] == "foo" assert settings[1].b[1] == "foo" assert settings.get(1).b[1] == "foo" assert settings["a"]["1"][1] == "foo" 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_envless_load_file(tmpdir): """Ensure passing settings.load_file accepts env argument. settings.load_file("filename.toml", env="development") will load the default + development env. settings.load_file("filename.toml", env=False|None) will load in envless mode. settings.load_file("filename.toml") will respect the current env settings (normal mode). """ data = { "default": {"a_default": "From default env"}, "development": {"value": "From development env"}, } toml_path = str(tmpdir.join("base_settings.toml")) toml_loader.write(toml_path, data, merge=False) data = { "development": {"another_value": "Another development env"}, } toml_path2 = str(tmpdir.join("base_settings2.toml")) toml_loader.write(toml_path2, data, merge=False) data = {"value": "Envless value"} toml_path_envless = str(tmpdir.join("base_settings_envless.toml")) toml_loader.write(toml_path_envless, data, merge=False) settings = Dynaconf( environments=["development", "other"], settings_file=toml_path ) assert settings.VALUE == "From development env" assert settings.A_DEFAULT == "From default env" # load the env file normal way (respecting current env) settings.load_file(toml_path2) assert settings.ANOTHER_VALUE == "Another development env" # load the envless file settings.load_file(toml_path_envless, env=False) assert settings.VALUE == "Envless value" 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 # #712 # introduce update(validate), set(validate) and load_file(validate) # and global VALIDATE_ON_UPDATE option # parametrize data to tests validate_on_update_data = ( pytest.param( {"value_a": "foo"}, # valid data {"value_b": "bar"}, # invalid data id="simple-value", ), pytest.param( {"value_a__nested": "foo"}, {"value_b__nested": "bar"}, id="dunder-value", ), ) @pytest.mark.parametrize("valid_data,invalid_data", validate_on_update_data) def test_update__validate_on_update_is_false(valid_data, invalid_data): """ When `Dynaconf(validate_on_update=False)` Should behave correctly (bypass, pass or raise) """ settings = Dynaconf() # validate_on_update default is false settings.validators.register(Validator("value_a", must_exist=True)) # should bypass settings.update(invalid_data) settings.update(invalid_data, validate="random") settings.update(invalid_data, validate=123) # should raise with pytest.raises(ValidationError): settings.update(invalid_data, validate=True) # should pass assert not settings.exists("value_a") settings.update(valid_data, validate=True) @pytest.mark.parametrize("valid_data,invalid_data", validate_on_update_data) def test_update__validate_on_update_is_true(valid_data, invalid_data): """ When `Dynaconf(validate_on_update=True)` Should behave correctly (bypass, pass or raise) """ settings = Dynaconf(validate_on_update=True) settings.validators.register(Validator("value_a", must_exist=True)) # should bypass settings.update(invalid_data, validate=False) settings.update(invalid_data, validate="random") settings.update(invalid_data, validate=123) # should raise with pytest.raises(ValidationError): settings.update(invalid_data) # should pass assert not settings.exists("value_a") settings.update(valid_data) def test_update__validate_on_update_is_str_all(): """ When `Dynaconf(validate_on_update="all")` Should behave correctly (bypass, pass or raise) """ settings = Dynaconf(validate_on_update="all") settings.validators.register(Validator("value_a", must_exist=True)) settings.validators.register(Validator("value_int", eq=42)) # should bypass settings.update({"value_b": "foo"}, validate=False) settings.update({"value_b": "foo"}, validate="random") settings.update({"value_b": "foo"}, validate=123) # should raise and accumulate errors with pytest.raises(ValidationError) as e: settings.update({"value_b": "not_a", "value_int": 41}) assert len(e.value.details) == 2 # should pass assert not settings.exists("value_a") settings.update({"value_a": "exists", "value_int": 42}) def test_set__validate_on_update_is_false(): """ When `Dynaconf(validate_on_update=False)` Should behave correctly (bypass, pass or raise) """ settings = Dynaconf() # validate = false settings.validators.register(Validator("value_a", must_exist=True)) # should bypass settings.set("value_b", "foo") settings.set("value_b", "foo", validate="random") settings.set("value_b", "foo", validate=123) # should raise with pytest.raises(ValidationError): settings.set("value_b", "foo", validate=True) # should pass assert not settings.exists("value_a") settings.set("value_a", "foo", validate=True) def test_set__validate_on_update_is_true(): """ When `Dynaconf(validate_on_update=True)` Should behave correctly (bypass, pass or raise) """ settings = Dynaconf(validate_on_update=True) settings.validators.register(Validator("value_a", must_exist=True)) # should bypass settings.set("value_b__nested", "foo", validate=False) settings.set("value_b__nested", "foo", validate="random") settings.set("value_b__nested", "foo", validate=123) # should raise with pytest.raises(ValidationError): settings.set("value_b__nested", "foo") # should pass assert not settings.exists("value_a") settings.set("value_a__nested", "foo") def test_set__validate_on_update_is_str_all(): """ When `Dynaconf(validate_on_update="all")` Should behave correctly (bypass, pass or raise) """ settings = Dynaconf(validate_on_update="all") settings.validators.register(Validator("value_a", must_exist=True)) # should bypass settings.set("value_b__nested", "foo", validate=False) settings.set("value_b__nested", "foo", validate="random") settings.set("value_b__nested", "foo", validate=123) # should raise. "all" doesn't make difference here with pytest.raises(ValidationError): settings.set("value_b__nested", "foo") # should pass assert not settings.exists("value_a") settings.set("value_a__nested", "foo") def mkfile(tmp_dir, filename: str, data: str): """ Test utility function to create tmp files """ file = tmp_dir.join(filename) with open(file.strpath, "w", encoding="utf-8") as f: f.write(data) return file params__load_file__data = ( pytest.param( {"fname": "valid.toml", "data": "value_a='foo'\nvalue_int=42"}, {"fname": "invalid.toml", "data": "value_b='foo'\nvalue_int=41"}, id="load-toml", ), pytest.param( {"fname": "valid.ini", "data": "value_a='foo'\nvalue_int='@int 42'"}, {"fname": "invalid.ini", "data": "value_b='foo'\nvalue_int='@int 41'"}, id="load-ini", ), pytest.param( {"fname": "valid.yaml", "data": "value_a: 'foo'\nvalue_int: 42"}, {"fname": "invalid.yaml", "data": "value_b: 'foo'\nvalue_int: 41"}, id="load-yaml", ), pytest.param( {"fname": "valid.json", "data": '{"value_a":"foo",\n"value_int":42}'}, { "fname": "invalid.json", "data": '{"value_b":"foo",\n"value_int":41}', }, id="load-json", ), pytest.param( {"fname": "valid.py", "data": 'VALUE_A="foo"\nVALUE_INT=42'}, {"fname": "invalid.py", "data": 'VALUE_B="foo"\nVALUE_INT=41'}, id="load-py", ), ) @pytest.mark.parametrize("valid,invalid", params__load_file__data) def test_load_file__validate_on_update_is_false(tmpdir, valid, invalid): """ When `Dynaconf(validate_on_update=False)` Should behave correctly (bypass, pass or raise) """ # setup files file_with_valid = mkfile(tmpdir, valid["fname"], valid["data"]) file_with_invalid = mkfile(tmpdir, invalid["fname"], invalid["data"]) # setup dyna settings = Dynaconf() # validate = false settings.validators.register(Validator("value_a", must_exist=True)) # should bypass when settings.load_file(file_with_invalid) settings.load_file(file_with_invalid, validate="random") settings.load_file(file_with_invalid, validate=123) # should raise with pytest.raises(ValidationError): settings.load_file(file_with_invalid, validate=True) # should pass assert not settings.exists("value_a") settings.load_file(file_with_valid, validate=True) @pytest.mark.parametrize("valid,invalid", params__load_file__data) def test_load_file__validate_on_update_is_true(tmpdir, valid, invalid): """ When `Dynaconf(validate_on_update=True)` Should behave correctly (bypass, pass or raise) """ # setup files file_with_valid = mkfile(tmpdir, valid["fname"], valid["data"]) file_with_invalid = mkfile(tmpdir, invalid["fname"], invalid["data"]) # setup dyna settings = Dynaconf(validate_on_update=True) settings.validators.register(Validator("value_a", must_exist=True)) # should bypass when settings.load_file(file_with_invalid, validate=False) # should raise with pytest.raises(ValidationError): settings.load_file(file_with_invalid) # should pass assert not settings.exists("value_a") settings.load_file(file_with_valid) @pytest.mark.parametrize("valid,invalid", params__load_file__data) def test_load_file__validate_on_update_is_str_all(tmpdir, valid, invalid): """ When `Dynaconf(validate_on_update="all")` Should behave correctly (bypass, pass or raise accumulating errors) """ # setup files file_with_valid = mkfile(tmpdir, valid["fname"], valid["data"]) file_with_invalid = mkfile(tmpdir, invalid["fname"], invalid["data"]) # setup dyna settings = Dynaconf(validate_on_update="all") settings.validators.register(Validator("value_a", must_exist=True)) settings.validators.register(Validator("value_int", eq=42)) # should bypass settings.load_file(file_with_invalid, validate=False) settings.load_file(file_with_invalid, validate="random") settings.load_file(file_with_invalid, validate=123) # should raise and accumulate errors with pytest.raises(ValidationError) as e: settings.load_file(file_with_invalid) assert len(e.value.details) == 2 # should pass assert not settings.exists("value_a") settings.load_file(file_with_valid) def test_get_with_sysenv_fallback_global_as_false(): """ When global sysenv_fallback is False Should not fallback to sys envvars (default) """ settings = Dynaconf(sysenv_fallback=False) settings.environ["TEST_KEY"] = "TEST_VALUE" assert settings.sysenv_fallback_for_dynaconf is False assert not settings.get("test_key") def test_get_with_sysenv_fallback_global_as_true(): """ When sysenv_fallback is True Should fallback to sys envvars for uppercase envvar-names only """ settings = Dynaconf(sysenv_fallback=True) settings.environ["TEST_KEY"] = "TEST_VALUE" assert settings.sysenv_fallback_for_dynaconf is True assert settings.get("test_key") == "TEST_VALUE" def test_get_with_sysenv_fallback_global_as_list(): """ When sysenv_fallback is list Should fallback to sys envvars only for listed names """ settings = Dynaconf(sysenv_fallback=["FOO_KEY"]) settings.environ["TEST_KEY"] = "TEST_VALUE" settings.environ["FOO_KEY"] = "FOO_VALUE" assert isinstance(settings.sysenv_fallback_for_dynaconf, list) assert not settings.get("test_key") assert settings.get("foo_key") == "FOO_VALUE" def test_get_with_sysenv_fallback_local_overrides(): """ When there are local overrides Should behave according to the overrides """ # global is False settings = Dynaconf(sysenv_fallback=False) settings.environ["TEST_KEY"] = "TEST_VALUE" assert settings.sysenv_fallback_for_dynaconf is False assert not settings.get("test_key") assert not settings.get("test_key", sysenv_fallback=["foo_key"]) assert settings.get("test_key", sysenv_fallback=True) == "TEST_VALUE" assert ( settings.get("test_key", sysenv_fallback=["test_key"]) == "TEST_VALUE" ) # global is True settings = Dynaconf(sysenv_fallback=True) settings.environ["ANOTHER_TEST"] = "ANOTHER_VALUE" assert settings.sysenv_fallback_for_dynaconf is True assert settings.get("another_test") == "ANOTHER_VALUE" assert not settings.get("another_test", sysenv_fallback=False) # issue #965 def test_no_extra_values_in_nested_structure(): settings = Dynaconf() settings.set("key", [{"d": "v"}]) assert settings.key == [{"d": "v"}] ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_basic.py0000644000000000000000000000020315156311032017361 0ustar00pbrochadpbrochadfrom __future__ import annotations from dynaconf import settings def test_has_wrapped(): assert settings.configured is True ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_cli.py0000644000000000000000000005550415156311032017065 0ustar00pbrochadpbrochadfrom __future__ import annotations import json import os from pathlib import Path from textwrap import dedent from unittest import mock 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 settings = LazySettings(OPTION_FOR_TESTS=True, environments=True) def run(cmd, env=None, attr="output"): with mock.patch.dict(os.environ, {}): runner = CliRunner() 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 'test_value'" in result @pytest.mark.parametrize("cmd", ["list", "inspect", "get"]) def test_not_found_key_exit_error(cmd, testdir): """Test error code when key not found""" if cmd == "get": command = [cmd, "DONTEXIST"] else: command = [cmd, "-k", "DONTEXIST"] result = run( command, env={ "ROOT_PATH_FOR_DYNACONF": testdir, "INSTANCE_FOR_DYNACONF": "tests.config.settings", }, attr="exit_code", ) assert result == 1 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_negative_get(testdir): """Tests get command erroring when key does not exist""" cmd = ["get", "DONTEXIST"] env = { "ROOT_PATH_FOR_DYNACONF": testdir, "INSTANCE_FOR_DYNACONF": "tests.config.settings", } assert run(cmd, env=env, attr="stdout") == "Key not found" assert run(cmd, env=env, attr="exit_code") == 1 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 'test_value'" in result assert json.loads(read_file("sets.json"))["TEST_KEY"] == "test_value" def test_list_prints_json(testdir): """Ensure output of list --json is JSON compatible""" result = run( ["-i", "tests.config.settings", "list", "--json"], env={"ROOT_PATH_FOR_DYNACONF": testdir}, ) data = json.loads(result) assert data["TEST_KEY"] == "test_value" assert data["A_PATH"] == "foo" 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 '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 True" in result def test_list_with_instance_from_env(): result = run( ["list"], {"INSTANCE_FOR_DYNACONF": "tests.test_cli.settings"} ) assert "OPTION_FOR_TESTS 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 '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 '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 def create_file(filename: str | Path, data: str): """Utility to write data to filename.""" encoding = str(default_settings.ENCODING_FOR_DYNACONF) with open(filename, "w", encoding=encoding) as f: f.write(dedent(f"{data}")) return filename def get_current_test_name(): """ Utility to get the current test name. Instance names should be unique for proper test isolation. """ name = os.environ["PYTEST_CURRENT_TEST"] # clean name to avoid issues with file creation: # "this/test.py::"{testname}" (call)" start = name.find("::") + 2 end = name.find(" (call") name = name[start:end] return name def test_inspect_no_args(tmp_path): """Inspect command with no arguments""" instance_name = get_current_test_name() setting_file = tmp_path / "a.toml" environ = {"DYNACONF_FOO": "from_environ_no_args"} cmd = ["-i", f"{instance_name}.settings", "inspect"] create_file( tmp_path / f"{instance_name}.py", f"""\ settings = __import__('dynaconf').Dynaconf( settings_file="{setting_file.as_posix()}" ) """, ) create_file(setting_file, "foo='from_file_no_args'") result = run(cmd, env=environ) expected_header = """\ { "header": { "env_filter": "None", "key_filter": "None", "new_first": "True", "history_limit": "None", "include_internal": "False" }, "current": { "FOO": "from_environ_no_args" }, "history": [ { "loader": "env_global", "identifier": "DYNACONF", "env": "global", "merged": false, "value": { "FOO": "from_environ_no_args" } }, """ assert result assert result.startswith(dedent(expected_header)) def test_inspect_yaml_format(tmp_path): """Inspect command with format argument""" instance_name = get_current_test_name() setting_file = tmp_path / "a.toml" environ = { "DYNACONF_FOO": "from_environ_yaml_format", } cmd = ["-i", f"{instance_name}.settings", "inspect", "-f", "yaml"] create_file( tmp_path / f"{instance_name}.py", f"""\ settings = __import__('dynaconf').Dynaconf( settings_file="{setting_file.as_posix()}" ) """, ) create_file(setting_file, "bar='from_file_yaml_format'") result = run(cmd, env=environ) expected_header = """\ header: env_filter: None key_filter: None new_first: 'True' history_limit: None include_internal: 'False' current: BAR: from_file_yaml_format FOO: from_environ_yaml_format history: - loader: env_global identifier: DYNACONF env: global merged: false value: FOO: from_environ_yaml_format """ assert result assert result.startswith(dedent(expected_header)) def test_inspect_key_filter(tmp_path): """Inspect command with key filter argument""" instance_name = get_current_test_name() setting_file = tmp_path / "a.toml" environ = {"DYNACONF_FOO": "from_environ_key_filter"} cmd = ["-i", f"{instance_name}.settings", "inspect", "-k", "bar"] create_file( tmp_path / f"{instance_name}.py", f"""\ settings = __import__('dynaconf').Dynaconf( settings_file="{setting_file.as_posix()}" ) """, ) create_file( setting_file, """\ foo='from_file_key_filter' bar='file_only' """, ) result = run(cmd, env=environ) expected_header = """\ { "header": { "env_filter": "None", "key_filter": "bar", "new_first": "True", "history_limit": "None", "include_internal": "False" }, "current": "file_only", """ assert result assert result.startswith(dedent(expected_header)) def test_inspect_env_filter(tmp_path): """Inspect command with env filter argument""" instance_name = get_current_test_name() setting_file = tmp_path / "a.toml" environ = {} cmd = ["-i", f"{instance_name}.settings", "inspect", "-e", "prod"] create_file( tmp_path / f"{instance_name}.py", f"""\ from dynaconf import Dynaconf settings = Dynaconf( settings_file="{setting_file.as_posix()}", environments=True ) """, ) create_file( setting_file, """\ default.foo='from_env_default' development.foo='from_env_development' prod.bar='prod_only_and_foo_default' """, ) result = run(cmd, env=environ) expected_header = """\ { "header": { "env_filter": "prod", "key_filter": "None", "new_first": "True", "history_limit": "None", "include_internal": "False" }, "current": { "FOO": "from_env_default", "BAR": "prod_only_and_foo_default" }, """ assert result assert result.startswith(dedent(expected_header)) def test_inspect_limit(tmp_path): """ Inspect command with --limit. Should include only the last history entry. """ instance_name = get_current_test_name() setting_file = tmp_path / "a.toml" environ = {"DYNACONF_FOO": "from_environ"} cmd = [ "-i", f"{instance_name}.settings", "inspect", "--limit", "1", ] create_file( tmp_path / f"{instance_name}.py", f"""\ from dynaconf import Dynaconf settings = Dynaconf( settings_file="{setting_file.as_posix()}", ) """, ) create_file( setting_file, "foo='from_file'", ) result = run(cmd, env=environ) expected_result = """\ { "header": { "env_filter": "None", "key_filter": "None", "new_first": "True", "history_limit": "1", "include_internal": "False" }, "current": { "FOO": "from_environ" }, "history": [ { "loader": "env_global", "identifier": "DYNACONF", "env": "global", "merged": false, "value": { "FOO": "from_environ" } } ] } """ assert result assert result == dedent(expected_result) def test_inspect_all_args(tmp_path): """Inspect command with all arguments""" instance_name = get_current_test_name() # should be unique for isolation setting_file = tmp_path / "a.toml" environ = {"DYNACONF_BAR": "actual value but not in history"} cmd = [ "-i", f"{instance_name}.settings", "inspect", "--key", "bar", "--env", "prod", "--format", "yaml", ] create_file( tmp_path / f"{instance_name}.py", f"""\ from dynaconf import Dynaconf settings = Dynaconf( settings_file="{setting_file.as_posix()}", environments=True ) """, ) create_file( setting_file, """\ default.foo='from_env_default' development.foo='from_env_development' prod.bar='prod_only' prod.spam='should_appear' """, ) result = run(cmd, env=environ) expected_result = f"""\ header: env_filter: prod key_filter: bar new_first: 'True' history_limit: None include_internal: 'False' current: actual value but not in history history: - loader: toml identifier: {setting_file.as_posix()} env: prod merged: false value: prod_only\n """ assert result assert result == dedent(expected_result) def test_inspect_invalid_key(tmp_path): instance_name = get_current_test_name() environ = {} cmd = [ "-i", f"{instance_name}.settings", "inspect", "--key", "dont_exist", ] create_file( tmp_path / f"{instance_name}.py", """\ from dynaconf import Dynaconf settings = Dynaconf(environments=True) """, ) result = run(cmd, env=environ) assert result == "The requested key was not found: 'dont_exist'\n" def test_inspect_invalid_env(tmp_path): instance_name = get_current_test_name() environ = {} cmd = [ "-i", f"{instance_name}.settings", "inspect", "--env", "dont_exist", ] create_file( tmp_path / f"{instance_name}.py", """\ from dynaconf import Dynaconf settings = Dynaconf(environments=True) """, ) result = run(cmd, env=environ) assert result == "The requested env is not valid: 'dont_exist'\n" def test_inspect_invalid_format(tmp_path): instance_name = get_current_test_name() environ = {} cmd = [ "-i", f"{instance_name}.settings", "inspect", "--format", "dont_exist", ] create_file( tmp_path / f"{instance_name}.py", """\ from dynaconf import Dynaconf settings = Dynaconf() """, ) result = run(cmd, env=environ) expected = ( "Error: Invalid value for '--format' / '-f': " "invalid choice: dont_exist." ) assert expected in result ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_compat.py0000644000000000000000000000366615156311032017603 0ustar00pbrochadpbrochadfrom __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 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_django.py0000644000000000000000000000270215156311032017550 0ustar00pbrochadpbrochadfrom __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" ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_dynabox.py0000644000000000000000000000640615156324234017767 0ustar00pbrochadpbrochadfrom __future__ import annotations from collections import namedtuple from dynaconf.utils.boxing import DynaBox 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" ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_endtoend.py0000644000000000000000000000677315156324234020132 0ustar00pbrochadpbrochadfrom __future__ import annotations import os from contextlib import AbstractContextManager as ContextManager from contextlib import nullcontext import pytest from dynaconf.utils.parse_conf import DynaconfFormatError from dynaconf.utils.parse_conf import DynaconfParseError 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 @pytest.mark.parametrize( "template_string, expectation, warn", [ pytest.param( "@jinja {{ cycler.__init__.__globals__.os.popen('id').read() }}", nullcontext(""), pytest.warns(UserWarning, match="Unsafe"), id="jinja-returns-empty-string", ), pytest.param( "@format {this.__class__.__init__.__globals__[os].environ}", pytest.raises(DynaconfFormatError), nullcontext("no warning"), id="format-raises", ), pytest.param( "@get __class__.__init__.__globals__[os].environ", pytest.raises(DynaconfParseError), nullcontext("no warning"), id="get-raises", ), ], ) def test_templating_safety( template_string: str, expectation: ContextManager, warn: ContextManager ): from dynaconf import Dynaconf settings = Dynaconf(malicious=template_string) with expectation as result: with warn: assert settings.malicious == result ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_env_loader.py0000644000000000000000000003523115156311032020427 0ustar00pbrochadpbrochadfrom __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" def test_boolean_fix(): environ["BOOLFIX_CAPITALTRUE"] = "True" environ["BOOLFIX_CAPITALFALSE"] = "False" settings.IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF = False load_from_env( obj=settings, prefix="BOOLFIX", key=None, silent=True, identifier="env_global", env=False, ) assert settings.CAPITALTRUE is True assert settings.CAPITALFALSE is False ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_envvar_prefix.py0000644000000000000000000000225215156311032021164 0ustar00pbrochadpbrochadfrom __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"] ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_feature_flag.py0000644000000000000000000000135215156311032020732 0ustar00pbrochadpbrochadfrom __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 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_flask.py0000644000000000000000000001231315156324234017415 0ustar00pbrochadpbrochadfrom __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" def test_setting_instance_options_works_case_insensitive(): """ assert that dynaconf options (that are modified by FlaskDynaconf) can be set by the user in a case insensitive manner. see #848 """ app = Flask(__name__) FlaskDynaconf( app, envVar_prefix="MYPREFIX", eNv_swItcHer="MY_ENV_SWITCHER", enViroNments=False, lOaD_dOtenv=False, ) assert app.config.envvar_prefix_for_dynaconf == "MYPREFIX" assert app.config.env_switcher_for_dynaconf == "MY_ENV_SWITCHER" assert app.config.environments_for_dynaconf is False # oddly, using '_for_dynaconf' won't work, although # the option functionality seems to work as expected assert app.config.load_dotenv is False ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_hooking.py0000644000000000000000000001677615156324234017774 0ustar00pbrochadpbrochadfrom __future__ import annotations import pytest from dynaconf import Dynaconf from dynaconf.hooking import Action from dynaconf.hooking import EagerValue from dynaconf.hooking import get_hooks from dynaconf.hooking import Hook from dynaconf.hooking import hookable from dynaconf.hooking import HookableSettings from dynaconf.hooking import HookValue from dynaconf.utils.boxing import DynaBox class BaseHookedSettings: def __init__(self, **kwargs): self._store = DynaBox(kwargs) self._loaded_by_loaders = {} @property def __dict__(self): return self._store @hookable def get(self, key): return self._store.get(key) def test_hook_dynaconf_class_get_templated(): settings = Dynaconf( INTERNAL_VALUE=42, TEMPLATED="@format {this[INTERNAL_VALUE]}abc", TEMPLATED1="@int @format {this[INTERNAL_VALUE]}", TEMPLATED2="@jinja {{this.INTERNAL_VALUE}}abcd", TEMPLATED3="@int @jinja {{this.INTERNAL_VALUE}}", PERSON={"NAME": "Joe"}, _wrapper_class=HookableSettings, ) def just_assert_values_after_get(s, v, key, *_, **__): if key == "INTERNAL_VALUE": assert v == 99 elif key == "PERSON": assert s["PERSON"] == {"NAME": "Joe", "city": "Valhala", "AGE": 18} else: assert s["PERSON"] == {"NAME": "Joe"} assert s["INTERNAL_VALUE"] == 42 return v def set_internal_value_to_99_before_get(s, v, key, *_, **__): if key == "INTERNAL_VALUE": return EagerValue(99) return v def contribute_to_person_after_get(s, v, key, *_, **__): if key == "PERSON": data = {"PERSON__AGE": 18, "PERSON": "@merge city=Valhala"} s.update(data) return s.get(key) return v def assert_unchanged_person_value_before_get(s, v, key, *_, **__): assert s["PERSON"] == {"NAME": "Joe"} return v settings["_registered_hooks"] = { Action.BEFORE_GET: [ Hook(set_internal_value_to_99_before_get), Hook(assert_unchanged_person_value_before_get), ], Action.AFTER_GET: [ Hook(contribute_to_person_after_get), Hook(just_assert_values_after_get), ], } assert settings.TEMPLATED == "99abc" settings.set("FOOVALUE", 100) assert settings.FOOVALUE == 100 assert settings["FOOVALUE"] == 100 assert settings.TEMPLATED1 == 99 assert settings.TEMPLATED2 == "99abcd" assert settings.TEMPLATED3 == 99 assert settings.INTERNAL_VALUE == 99 assert settings.get("INTERNAL_VALUE") == 99 assert settings.PERSON.NAME == "Joe" assert settings.PERSON.AGE == 18 assert settings.PERSON.CITY == "Valhala" def test_hook_dynaconf_class_after_get(): settings = Dynaconf(INTERNAL_VALUE=42, _wrapper_class=HookableSettings) assert settings.internal_value == 42 def adds_one(s, v, *_, **__): return v + 1 settings["_registered_hooks"] = { Action.AFTER_GET: [Hook(adds_one)], } hooks = get_hooks(settings) assert len(hooks) == 1 assert settings.INTERNAL_VALUE == 43 assert settings.get("INTERNAL_VALUE") == 43 def test_hooked_dict(): class HookedDict(BaseHookedSettings, dict): _store = {} @hookable def get(self, key, default=None): return "to" d = HookedDict() d["_registered_hooks"] = { Action.AFTER_GET: [ Hook(lambda s, v, *_, **__: f"{v}fu"), ], } assert d.get("key") == "tofu" def test_hooked_dict_store(): class HookedDict(BaseHookedSettings, dict): ... d = HookedDict( key="to", _registered_hooks={ Action.AFTER_GET: [ Hook(lambda s, v, *_, **__: f"{v}fu"), ], }, ) assert d.get("key") == "tofu" def test_hook_before_and_after_bypass_method(): """Method is never executed, before and after hooks are called""" class HookedSettings(BaseHookedSettings): _store = {} _registered_hooks = { # Accumulate all values Action.BEFORE_GET: [ Hook(lambda s, v, *_, **__: "ba"), Hook(lambda s, v, *_, **__: EagerValue(f"{v.value}na")), # EagerValue is a special value that bypasses the method # and goes to the after hooks ], # After hooks makes the final value Action.AFTER_GET: [ Hook(lambda s, v, *_, **__: f"{v.value}na"), ], } @hookable(name="get") def get(self, key): # 1st before hook will make value to be "ba" # 2nd before hook will make value to be "bana" # 1st after hook will make value to be "banana" return "value" # this will never be executed settings = HookedSettings() assert settings.get("key") == "banana" def test_hook_runs_after_method(): """After method the after hooks transforms value.""" DATABASE = { "feature_enabled": True, } def try_to_get_from_database(d, value, key, *_, **__): assert d.get("feature_enabled") is False return DATABASE.get(key, value.value) class HookedSettings(BaseHookedSettings): ... settings = HookedSettings( feature_enabled=False, something_not_in_database="default value", _registered_hooks={ Action.AFTER_GET: [ Hook(try_to_get_from_database), ], }, ) # On the object feature is disabled # but on the database it is enabled assert settings.get("feature_enabled") is True # This key is not in the database, so returns regular value assert settings.get("something_not_in_database") == "default value" def test_hook_fail_with_wrong_parameters(): """Hookable decorator fails when called with wrong parameters.""" with pytest.raises(TypeError): @hookable("not a function") def foo(): pass def test_hook_values(): value = HookValue(1) assert value == 1 assert value != 2 assert value == HookValue(1) assert value != HookValue(2) assert bool(value) is True assert str(value) == "1" assert repr(value) == repr(value.value) assert value + 1 == 2 assert value - 1 == 0 assert value * 2 == 2 assert value / 2 == 0.5 assert value // 2 == 0 assert value % 2 == 1 assert value**2 == 1 assert divmod(value, 2) == (0, 1) value = HookValue([1, 2, 3]) assert value == [1, 2, 3] assert value != [1, 2, 4] assert value == HookValue([1, 2, 3]) assert value != HookValue([1, 2, 4]) assert bool(value) is True assert str(value) == "[1, 2, 3]" assert repr(value) == repr(value.value) assert value[0] == 1 assert value[1] == 2 assert value[2] == 3 assert len(value) == 3 assert value[0:2] == [1, 2] assert 2 in value assert [x for x in value] == [1, 2, 3] class Dummy: pass _value = Dummy() value = HookValue(_value) assert value == value.value assert value != object() assert value == HookValue(_value) assert value != HookValue(object()) assert bool(value) is True value.name = "dummy value" assert value.name == "dummy value" delattr(value, "name") assert not hasattr(value, "name") value = HookValue({}) assert value == {} assert value != {"a": 1} assert value == HookValue({}) value["a"] = 1 assert value == {"a": 1} assert value["a"] == 1 del value["a"] assert value == {} ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_ini_loader.py0000644000000000000000000001332215156311032020413 0ustar00pbrochadpbrochadfrom __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 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_inspect.py0000644000000000000000000007220715156324234017772 0ustar00pbrochadpbrochad""" Test dynaconf.utils.inspect:inspect """ from __future__ import annotations import copy import os from textwrap import dedent import pytest from dynaconf import Dynaconf from dynaconf.utils.inspect import _ensure_serializable from dynaconf.utils.inspect import _get_data_by_key from dynaconf.utils.inspect import EnvNotFoundError from dynaconf.utils.inspect import get_debug_info from dynaconf.utils.inspect import get_history from dynaconf.utils.inspect import inspect_settings from dynaconf.utils.inspect import KeyNotFoundError from dynaconf.utils.inspect import OutputFormatError from dynaconf.validator import Validator def create_file(filename: str, data: str) -> str: with open(filename, "w") as f: f.write(dedent(data)) return filename def is_dict_subset(original: dict, partial: dict) -> bool: """Check if partial dict is subset of original dict.""" return {**original, **partial} == original @pytest.fixture(autouse=True, scope="function") def teardown(): backup = copy.deepcopy(os.environ) for key in os.environ.keys(): if key.startswith(("DYNACONF_", "FLASK_", "DJANGO_")): del os.environ[key] yield os.environ.update(backup) @pytest.fixture(autouse=True, scope="module") def module_teardown(): yield for key in os.environ.keys(): if key.startswith(("DYNACONF_", "FLASK_", "DJANGO_")): del os.environ[key] def test_ensure_serializable(): settings = Dynaconf() settings.update( { "listy": [1, 2, 3, {"a": "A", "b": [4, 5, 6]}], "dicty": {"a": "A", "b": [1, 2, 3, {"a": "A", "b": "B"}]}, } ) normal_list = _ensure_serializable(settings.listy) normal_dict = _ensure_serializable(settings.dicty) assert normal_list.__class__ == list assert normal_list[3].__class__ == dict assert normal_list[3]["b"].__class__ == list assert normal_dict.__class__ == dict assert normal_dict["b"].__class__ == list # type: ignore assert normal_dict["b"][3].__class__ == dict # type: ignore def test_get_data_by_key(): data = {"a": "A", "b": [1, 2, 3, {"a": "A", "b": "B"}], "c": {"d": "D"}} assert _get_data_by_key(data, "a") == "A" # 2 cases below are not supported dynaconf idiom yet # assert _get_data_by_key(data, "b._1_") == 2 # assert _get_data_by_key(data, "b._3_.b") == "B" assert _get_data_by_key(data, "c.d") == "D" assert _get_data_by_key(data, "this.doesnt.exist", default="foo") == "foo" def test_get_history_general(tmp_path): """ Given two yaml files Should return - list of length 2 - per-file metadata containing: loader, identifier, env and value. - correct types: dicts and lists, not DynaBox and BoxList """ file_a = tmp_path / "a.yml" file_b = tmp_path / "b.yml" create_file( file_a, """\ dicty: a: A b: - 1 - c: C d: D """, ) create_file( file_b, """\ listy: - 1 - a: A b: B c: - 1 - 2 """, ) settings = Dynaconf(settings_file=[file_a, file_b]) history = get_history(settings) # metadata assert len(history) == 4 assert is_dict_subset( history[0], { "loader": "set_method", "identifier": "init_kwargs", "env": "global", "merged": False, }, ) assert is_dict_subset( history[1], { "loader": "set_method", "identifier": "settings_module_method", "env": "global", "merged": False, }, ) assert is_dict_subset( history[2], { "loader": "yaml", "identifier": str(file_a), "env": "default", "merged": False, }, ) assert is_dict_subset( history[3], { "loader": "yaml", "identifier": str(file_b), "env": "default", "merged": False, }, ) # data integrity assert "SETTINGS_FILE_FOR_DYNACONF" in history[0]["value"] assert "SETTINGS_MODULE" in history[1]["value"] assert history[2]["value"]["DICTY"] == { "a": "A", "b": [1, {"c": "C", "d": "D"}], } assert history[3]["value"]["LISTY"] == [ 1, {"a": "A", "b": "B", "c": [1, 2]}, ] # types has been normalized assert history[2]["value"]["DICTY"].__class__ == dict assert history[3]["value"]["LISTY"].__class__ == list def test_get_history_env_false__file_plus_envvar(tmp_path): """ Given environments=False and sources=file+envvar Should return correct metadata history """ os.environ["DYNACONF_FOO"] = "from_environ" file_a = tmp_path / "a.yml" create_file(file_a, "foo: from_file") settings = Dynaconf(settings_file=file_a) history = get_history(settings) # metadata assert len(history) == 4 assert is_dict_subset( history[2], { "loader": "yaml", "identifier": str(file_a), "env": "default", "merged": False, }, ) assert is_dict_subset( history[3], { "loader": "env_global", "identifier": "DYNACONF", "env": "global", "merged": False, }, ) def test_get_history_env_false__val_default_plus_envvar(): """ Given environments=False and sources=validation-default+envvar Should return correct metadata history """ os.environ["DYNACONF_FOO"] = "from_environ" settings = Dynaconf( validators=[Validator("bar", default="from_val_default")] ) history = get_history(settings) # metadata (validation_default runs after envvar loading) assert len(history) == 3 assert is_dict_subset( history[0], { "loader": "env_global", "identifier": "DYNACONF", "env": "global", "merged": False, }, ) assert is_dict_subset( history[1], { "loader": "setdefault", "identifier": "unique", "env": "main", "merged": False, }, ) def test_get_history_env_false__merge_marks(tmp_path): """ Given environments=False and sources=envvar+file Should return correct metadata history """ os.environ["DYNACONF_LISTY"] = "@merge [10,11,12]" file_a = tmp_path / "file_a.toml" file_b = tmp_path / "file_b.toml" file_c = tmp_path / "file_c.toml" create_file(file_a, "listy=[1,2,3]") create_file(file_b, "listy=[4,5,6, 'dynaconf_merge']") create_file(file_c, "dynaconf_merge=true\nlisty=[7,8,9]") settings = Dynaconf(settings_file=[file_a, file_b, file_c]) history = get_history(settings) # metadata assert len(history) == 6 assert history[2] == { "loader": "toml", "identifier": str(file_a), "env": "default", "merged": False, "value": {"LISTY": [1, 2, 3]}, } assert history[3] == { "loader": "toml", "identifier": str(file_b), "env": "default", "merged": True, "value": {"LISTY": [4, 5, 6, "dynaconf_merge"]}, } assert history[4] == { "loader": "toml", "identifier": str(file_c), "env": "default", "merged": True, "value": {"LISTY": [7, 8, 9]}, } assert is_dict_subset( history[5], { "loader": "env_global", "identifier": "DYNACONF", "env": "global", "merged": True, }, ) # when using @merge the value outputs as `Merge(....)` assert "Merge" in history[5]["value"]["LISTY"] def test_get_history_env_true__file_plus_envvar(tmp_path): """ Given environments=True and sources=file+envvar Should return correct metadata history """ os.environ["DYNACONF_FOO"] = "from_environ" file_a = tmp_path / "a.toml" create_file( file_a, """\n default.foo="from_default_env" development.foo="from_development_env" """, ) settings = Dynaconf(settings_file=file_a, environments=True) history = get_history(settings) assert len(history) == 5 assert history[2] == { "loader": "toml", "identifier": str(file_a), "env": "default", "merged": False, "value": {"FOO": "from_default_env"}, } assert history[3] == { "loader": "toml", "identifier": str(file_a), "env": "development", "merged": False, "value": {"FOO": "from_development_env"}, } assert history[4] == { "loader": "env_global", "identifier": "DYNACONF", "env": "global", "merged": False, "value": {"FOO": "from_environ"}, } def test_get_history_env_true__val_default_plus_file(tmp_path): """ Given environments=True and sources=validation-default+file Should return correct metadata history """ file_a = tmp_path / "a.toml" create_file( file_a, """\ default.foo="from_default_file" development.foo="from_development_file" """, ) settings = Dynaconf( validators=[ Validator("bar", default="from_val_default_current_env"), Validator( "baz", env="production", default="from_val_default_prod" ), ], settings_file=file_a, environments=True, ) history = get_history(settings) assert len(history) == 8 assert history[2] == { "loader": "toml", "identifier": str(file_a), "env": "default", "merged": False, "value": {"FOO": "from_default_file"}, } assert history[3] == { "loader": "toml", "identifier": str(file_a), "env": "development", "merged": False, "value": {"FOO": "from_development_file"}, } assert history[4] == { "loader": "setdefault", "identifier": "unique", "env": "development", "merged": False, "value": {"BAR": "from_val_default_current_env"}, } # REVIEW: history[5] is not correct, validation default on other env should # not have side effect on current object but only on its copy/clone # history[6] == switching to production env, so it sets ENV_FOR_DYNACONF assert history[7] == { "loader": "setdefault", "identifier": "unique", "env": "production", "merged": False, "value": {"BAZ": "from_val_default_prod"}, } def test_get_history_env_true__merge_marks(tmp_path): """ Given environments=True and sources=envvar+file Should return correct metadata history """ os.environ["DYNACONF_LISTY"] = "@merge [10,11,12]" file_a = tmp_path / "file_a.toml" file_b = tmp_path / "file_b.toml" file_c = tmp_path / "file_c.toml" create_file(file_a, "listy=[1,2,3]") create_file(file_b, "listy=[4,5,6, 'dynaconf_merge']") create_file(file_c, "dynaconf_merge=true\nlisty=[7,8,9]") settings = Dynaconf(settings_file=[file_a, file_b, file_c]) history = get_history(settings) # metadata assert len(history) == 6 assert history[2] == { "loader": "toml", "identifier": str(file_a), "env": "default", "merged": False, "value": {"LISTY": [1, 2, 3]}, } assert history[3] == { "loader": "toml", "identifier": str(file_b), "env": "default", "merged": True, "value": {"LISTY": [4, 5, 6, "dynaconf_merge"]}, } assert history[4] == { "loader": "toml", "identifier": str(file_c), "env": "default", "merged": True, "value": {"LISTY": [7, 8, 9]}, } assert is_dict_subset( history[5], { "loader": "env_global", "identifier": "DYNACONF", "env": "global", "merged": True, }, ) # when using @merge the value outputs as `Merge(....)` assert "Merge" in history[5]["value"]["LISTY"] def test_get_history_key_filter(tmp_path): """Asserts key filtering through get_history param works""" file_a = tmp_path / "file_a.toml" file_b = tmp_path / "file_b.toml" file_c = tmp_path / "file_c.toml" create_file(file_a, "a='aA'\nb='aB'\nc='aC'") create_file(file_b, "a='bA'\nb='bB'\nc='bC'") create_file(file_c, "a='cA'\nb='cB'\nc='cC'") settings = Dynaconf(settings_file=[file_a, file_b, file_c]) history = get_history(settings, key="a") assert history[0]["value"] == "aA" assert history[1]["value"] == "bA" assert history[2]["value"] == "cA" def test_get_history_key_filter_nested(tmp_path): """Asserts key filtering through get_history param works""" file_a = tmp_path / "file_a.toml" file_b = tmp_path / "file_b.toml" file_c = tmp_path / "file_c.toml" create_file(file_a, "a.b='aB'\na.c='aC'") create_file(file_b, "a.b='bB'\na.c='bC'") create_file(file_c, "a.b='cB'\na.c='cC'") settings = Dynaconf(settings_file=[file_a, file_b, file_c]) history = get_history(settings, key="a.c") assert len(history) == 3 assert history[0]["value"] == "aC" assert history[1]["value"] == "bC" assert history[2]["value"] == "cC" def test_get_history_env_filter(tmp_path): """Asserts env filtering through env_filter function works""" file_a = tmp_path / "file_a.toml" file_b = tmp_path / "file_b.toml" create_file( file_a, """\ [default] foo="from_default_a" [development] foo="from_development_a" [prod] foo="from_prod_a" """, ) create_file( file_b, """\ [default] foo="from_default_b" [development] foo="from_development_b" [prod] foo="from_prod_b" """, ) settings = Dynaconf(settings_file=[file_a, file_b], environments=True) settings.from_env("prod") # CAVEAT: activate loading of prod history = get_history( settings, filter_callable=lambda x: x.env.lower() == "prod" ) assert len(history) == 2 assert history[0]["value"] == {"FOO": "from_prod_a"} assert history[1]["value"] == {"FOO": "from_prod_b"} def test_get_history_env_and_key_filter(tmp_path): """Asserts combined use of filters works""" file_a = tmp_path / "file_a.toml" file_b = tmp_path / "file_b.toml" create_file( file_a, """\ [default] foo="from_default_a" bar="from_default_a" [development] foo="from_development_a" bar="from_development_a" [prod] foo="from_prod_a" bar="from_prod_a" """, ) create_file( file_b, """\ [default] foo="from_default_b" bar="from_default_b" [development] foo="from_development_b" bar="from_development_b" [prod] foo="from_prod_b" bar="from_prod_b" """, ) settings = Dynaconf(settings_file=[file_a, file_b], environments=True) settings.from_env("prod") # CAVEAT: activate loading of prod history = get_history( settings, key="bar", filter_callable=lambda x: x.env.lower() == "prod", ) assert len(history) == 2 assert history[0]["value"] == "from_prod_a" assert history[1]["value"] == "from_prod_b" def tests_get_history_with_variable_interpolation(): """Variable interpolation is not evaluated. https://github.com/dynaconf/dynaconf/issues/1180 The original issue was about an exception being raised when there was variable interpolation involved. But in the end, the get_history shouldnt evaluate the interpolations: - History should accurately inform the order and content of what the user loaded. - For a key with a interpolation value, the accurate representation is not the evaluated value, but the original template string. - The evaluated depends on other keys, so for history inspecting it is more reliable to show what variables were used so the user can verify if everything happened as expected. """ data = { "a": { "b": "foo", "c": "bar", "d": "@format {this.a.b} {this.a.c}", } } settings = Dynaconf(**data) assert settings.a.d == "foo bar" history = get_history(settings, "a.d") # shows the not-evaluated (raw) value assert history[0]["value"] == "@format {this.a.b} {this.a.c}" def test_caveat__get_history_env_true(tmp_path): """ Given environments=True and sources=file Should return correct metadata history Caveat: An environment that is not a builtin env name, like (default, global, development) and that is not the 'current_env' will not be loaded and, thus, will not be shown in history. Possible workaround: a) leave for the user to trigger this env loaded. >>> settings.from_env("production") b) add a global config for loading all envs, even if they are not in the builtin env names. """ file_a = tmp_path / "a.toml" create_file( file_a, """\n default.foo="from_default_env" development.foo="from_development_env" production.foo="from_production_env" """, ) settings = Dynaconf(settings_file=file_a, environments=True) history = get_history(settings) assert len(history) == 4 assert history[2] == { "loader": "toml", "identifier": str(file_a), "env": "default", "merged": False, "value": {"FOO": "from_default_env"}, } assert history[3] == { "loader": "toml", "identifier": str(file_a), "env": "development", "merged": False, "value": {"FOO": "from_development_env"}, } with pytest.raises(IndexError): assert history[4] == { "loader": "toml", "identifier": str(file_a), "env": "production", "merged": False, "value": {"FOO": "from_production_env"}, } def test_caveat__get_history_env_true_workaround(tmp_path): """ Given environments=True and sources=file Should return correct metadata history Caveat: see original test Workaround: force loading with desired 'current_env' """ file_a = tmp_path / "a.toml" create_file( file_a, """\n default.foo="from_default_env" development.foo="from_development_env" production.foo="from_production_env" """, ) settings = Dynaconf(settings_file=file_a, environments=True) settings.from_env("production") history = get_history(settings) assert len(history) == 6 assert history[2] == { "loader": "toml", "identifier": str(file_a), "env": "default", "merged": False, "value": {"FOO": "from_default_env"}, } assert history[3] == { "loader": "toml", "identifier": str(file_a), "env": "development", "merged": False, "value": {"FOO": "from_development_env"}, } assert history[5] == { "loader": "toml", "identifier": str(file_a), "env": "production", "merged": False, "value": {"FOO": "from_production_env"}, } def test_inspect_key_filter(tmp_path): os.environ["DYNACONF_FOO"] = "from_environ" os.environ["DYNACONF_BAR"] = "environ_only" filename = create_file(tmp_path / "a.yaml", "foo: from_yaml") settings = Dynaconf(settings_file=filename) result = inspect_settings(settings, key="foo", dumper="yaml") assert result["header"]["key_filter"] == "foo" assert result["current"] == "from_environ" assert result["history"] == [ { "loader": "env_global", "identifier": "DYNACONF", "env": "global", "merged": False, "value": "from_environ", }, { "loader": "yaml", "identifier": str(filename), "env": "default", "merged": False, "value": "from_yaml", }, ] def test_inspect_no_filter(tmp_path): os.environ["DYNACONF_FOO"] = "from_environ" os.environ["DYNACONF_BAR"] = "environ_only" filename = create_file(tmp_path / "a.yaml", "foo: from_yaml") settings = Dynaconf(settings_file=filename) result = inspect_settings(settings, dumper="yaml") assert result["header"]["key_filter"] == "None" assert result["header"]["env_filter"] == "None" assert result["current"] == {"FOO": "from_environ", "BAR": "environ_only"} # first two entries assert result["history"][:2] == [ { "env": "global", "identifier": "DYNACONF", "loader": "env_global", "merged": False, "value": {"BAR": "environ_only", "FOO": "from_environ"}, }, { "env": "default", "identifier": str(filename), "loader": "yaml", "merged": False, "value": {"FOO": "from_yaml"}, }, ] def test_inspect_env_filter(tmp_path): """ Caveat: env filter will show current default env values too. History will be filtered properly. """ filename = create_file( tmp_path / "a.toml", """\ default.foo="from_env_default" development.foo="from_env_dev" prod.bar="prod_only" """, ) settings = Dynaconf(settings_file=filename, environments=True) result = inspect_settings(settings, dumper="yaml", env="prod") assert result["header"]["env_filter"] == "prod" assert result["header"]["key_filter"] == "None" assert result["current"] == {"FOO": "from_env_default", "BAR": "prod_only"} assert result["history"] == [ { "loader": "toml", "identifier": str(filename), "env": "prod", "merged": False, "value": {"BAR": "prod_only"}, } ] def test_inspect_to_file(tmp_path): """Assert file is written correctly with 'to_file' option""" os.environ["DYNACONF_FOO"] = "from_environ" os.environ["DYNACONF_BAR"] = "environ_only" filename = create_file(tmp_path / "a.yaml", "foo: from_yaml") settings = Dynaconf(settings_file=filename) file_out = tmp_path / "output.yml" inspect_settings(settings, dumper="yaml", to_file=file_out) # open created file assert file_out.read_text() == dedent( f"""\ header: env_filter: None key_filter: None new_first: 'True' history_limit: None include_internal: 'False' current: FOO: from_environ BAR: environ_only history: - loader: env_global identifier: DYNACONF env: global merged: false value: FOO: from_environ BAR: environ_only - loader: yaml identifier: {filename} env: default merged: false value: FOO: from_yaml - loader: set_method identifier: settings_module_method env: global merged: false value: SETTINGS_MODULE: {filename} - loader: set_method identifier: init_kwargs env: global merged: false value: SETTINGS_FILE_FOR_DYNACONF: {filename} """ ) def test_inspect_exception_key_not_found(): settings = Dynaconf() with pytest.raises(KeyNotFoundError): inspect_settings(settings, key="non_existent") def test_inspect_empty_settings(): settings = Dynaconf() result = inspect_settings(settings) assert result["history"] == [] def test_inspect_exception_env_not_found(): settings = Dynaconf(environments=True) with pytest.raises(EnvNotFoundError): inspect_settings(settings, env="non_existent") def test_inspect_exception_invalid_format(): settings = Dynaconf() with pytest.raises(OutputFormatError): inspect_settings(settings, dumper="invalid_format") def test_get_debug_info(tmp_path): file_a = tmp_path / "a.yml" file_b = tmp_path / "b.yml" file_c = tmp_path / "c.py" create_file( file_a, """\ dicty: a: A b: - 1 - c: C d: D """, ) create_file( file_b, """\ listy: - 1 - a: A b: B c: - 1 - 2 """, ) create_file( file_c, """\ from dynaconf import post_hook FRUIT = "tomato" @post_hook def set_name_to_foo2(settings): return {"name": "foo2"} """, ) def set_name_to_foo(settings): return {"name": "foo"} settings = Dynaconf( settings_file=[file_a, file_b], post_hooks=[ set_name_to_foo, lambda settings: {"bar": "baz"}, ], hello="world", validators=[Validator("name", required=True)], ) settings.load_file(file_c) debug_info = get_debug_info(settings) assert "dynaconf" in debug_info["versions"] assert debug_info["root_path"] == str(tmp_path) assert len(debug_info["validators"]) == 1 assert len(debug_info["post_hooks"]) == 3 assert len(debug_info["loaded_hooks"]) == 3 assert len(debug_info["loaded_files"]) == 3 assert len(debug_info["history"]) == 11 # Now including keys debug_info = get_debug_info(settings, verbosity=1) assert debug_info["history"][0]["data"] == [ "POST_HOOKS", "HELLO", "SETTINGS_FILE_FOR_DYNACONF", ] assert debug_info["history"][0]["identifier"] == "init_kwargs" assert len(debug_info["history"][1]["data"]) > 1 assert debug_info["history"][1]["identifier"] == "default_settings" assert debug_info["history"][2]["data"] == ["SETTINGS_MODULE"] assert debug_info["history"][2]["identifier"] == "settings_module_method" assert debug_info["history"][3]["data"] == ["DICTY"] assert debug_info["history"][3]["identifier"] == str(file_a) assert debug_info["history"][4]["data"] == ["LISTY"] assert debug_info["history"][4]["identifier"] == str(file_b) assert debug_info["history"][5]["data"] == ["NAME"] assert debug_info["history"][5]["identifier"] == "set_name_to_foo@instance" assert debug_info["history"][6]["data"] == ["BAR"] assert "lambda_" in debug_info["history"][6]["identifier"] assert debug_info["history"][9]["data"] == ["FRUIT"] c_identifier = str(file_c).rstrip(".py") assert debug_info["history"][9]["identifier"] == c_identifier assert debug_info["history"][10]["data"] == ["NAME"] assert ( debug_info["history"][10]["identifier"] == f"set_name_to_foo2@{c_identifier}" ) assert debug_info["loaded_files"] == [ str(file_a), str(file_b), str(file_c), ] assert debug_info["loaded_hooks"][0]["data"] == ["name"] assert debug_info["loaded_hooks"][0]["hook"] == "set_name_to_foo@instance" assert debug_info["loaded_hooks"][1]["data"] == ["bar"] assert "lambda_" in debug_info["loaded_hooks"][1]["hook"] assert debug_info["loaded_hooks"][2]["data"] == ["name"] assert ( debug_info["loaded_hooks"][2]["hook"] == f"set_name_to_foo2@{c_identifier}" ) assert "set_name_to_foo" in debug_info["post_hooks"][0] assert "lambda" in debug_info["post_hooks"][1] assert "set_name_to_foo2" in debug_info["post_hooks"][2] # Now including keys and values debug_info = get_debug_info(settings, verbosity=2) assert debug_info["history"][0]["data"]["HELLO"] == "world" assert debug_info["history"][3]["data"]["DICTY"] == { "a": "A", "b": [1, {"c": "C", "d": "D"}], } assert debug_info["history"][4]["data"]["LISTY"] == [ 1, {"a": "A", "b": "B", "c": [1, 2]}, ] assert debug_info["history"][5]["data"]["NAME"] == "foo" assert debug_info["history"][6]["data"]["BAR"] == "baz" assert debug_info["history"][9]["data"]["FRUIT"] == "tomato" assert debug_info["history"][10]["data"]["NAME"] == "foo2" assert debug_info["loaded_hooks"][0]["data"]["name"] == "foo" assert debug_info["loaded_hooks"][1]["data"]["bar"] == "baz" assert debug_info["loaded_hooks"][2]["data"]["name"] == "foo2" # Now passing a specific key debug_info = get_debug_info(settings, key="DICTY") # assert the passed key is present on its own entry # assert other keys are not present assert debug_info["history"][0]["data"] == {} assert debug_info["history"][1]["data"] == {} assert debug_info["history"][2]["data"] == {} assert debug_info["history"][3]["data"]["DICTY"] == { "a": "A", "b": [1, {"c": "C", "d": "D"}], } assert debug_info["history"][4]["data"] == {} assert debug_info["history"][5]["data"] == {} assert debug_info["history"][6]["data"] == {} assert debug_info["history"][7]["data"] == {} assert debug_info["history"][8]["data"] == {} assert debug_info["history"][9]["data"] == {} assert debug_info["history"][10]["data"] == {} assert debug_info["loaded_hooks"][0]["data"] == {} assert debug_info["loaded_hooks"][1]["data"] == {} ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_json_loader.py0000644000000000000000000001424715156311032020614 0ustar00pbrochadpbrochadfrom __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 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_nested_loading.py0000644000000000000000000002157015156324234021301 0ustar00pbrochadpbrochadfrom __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 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_py_loader.py0000644000000000000000000002253115156324234020276 0ustar00pbrochadpbrochadfrom __future__ import annotations 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_decorated_hooks(clean_env, tmpdir): # Arrange settings_path = tmpdir.join("settings.py") settings_extra = tmpdir.join("extra.py") to_write = { str(settings_path): [ "INSTALLED_APPS = ['admin']", "COLORS = ['red', 'green']", "DATABASES = {'default': {'NAME': 'db'}}", "BANDS = ['Rush', 'Yes']", "from dynaconf import post_hook", "@post_hook", "def post_hook(settings):", " return {", " 'INSTALLED_APPS': ['admin', 'plugin'],", " 'COLORS': '@merge blue',", " 'DATABASES__default': '@merge PORT=5151',", " 'DATABASES__default__VERSION': 42,", " 'DATABASES__default__FORCED_INT': '@int 12',", " 'BANDS': ['Anathema', 'dynaconf_merge'],", " }", ], str(settings_extra): [ "NAME = 'Bruce'", "BASE_PATH = '/etc/frutaria'", "from dynaconf import post_hook", "@post_hook", "def post_hook(settings):", " return {", " 'INSTALLED_APPS': ['admin', 'extra'],", " 'COLORS': '@merge yellow',", " 'DATABASES__default': '@merge PORT=5152',", " 'DATABASES__default__VERSION': 43,", " 'DATABASES__default__FORCED_INT': '@int 13',", " 'BANDS': ['Opeth', 'dynaconf_merge'],", # Not sure @format should be supported on hooks, as settings is available on scope. # maybe useful to set as a lazy value to be resolved after all hooks are executed. " 'FULL_NAME': '@format {this.NAME} Rock',", " 'FULL_PATH': f'{settings.BASE_PATH}/batata/',", " }", ], } 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(settings_file="settings.py") # Assert # settings has _post_hooks attribute with one hook registered assert settings._post_hooks assert len(settings._post_hooks) == 1 # assert values set by the first hook only assert settings.INSTALLED_APPS == ["admin", "plugin"] 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"] # Call load_file to load the second file settings.load_file(str(settings_extra)) # assert _post_hooks attribute has two hooks registered assert len(settings._post_hooks) == 2 # assert values set by the second hook only assert settings.INSTALLED_APPS == ["admin", "extra"] assert settings.COLORS == ["red", "green", "blue", "yellow"] assert settings.DATABASES.default.NAME == "db" assert settings.DATABASES.default.PORT == 5152 assert settings.DATABASES.default.VERSION == 43 assert settings.DATABASES.default.FORCED_INT == 13 assert settings.BANDS == ["Rush", "Yes", "Anathema", "Opeth"] assert settings.FULL_NAME == "Bruce Rock" assert settings.FULL_PATH == "/etc/frutaria/batata/" 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"] plugin_hook, settings_hook = list(settings._loaded_hooks.keys()) assert settings._loaded_hooks[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[settings_hook] == { "post": { "INSTALLED_APPS": ["dummyplugin"], } } ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_redis.py0000644000000000000000000001027415156324234017427 0ustar00pbrochadpbrochadfrom __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 from dynaconf.utils.inspect import get_history 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 @pytest.mark.integration def test_redis_has_proper_source_metadata(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) history = get_history( settings, filter_callable=lambda s: s.loader == "redis" ) assert history[0]["env"] == "development" # default when environments=True assert history[0]["value"]["SECRET"] == "redis_works_perfectly" ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_settings_loader.py0000644000000000000000000001311515156311032021474 0ustar00pbrochadpbrochadfrom __future__ import annotations from dynaconf import Dynaconf from dynaconf.loaders import settings_loader def test_load_using_settings_loader(tmpdir): settings = Dynaconf(envvar_prefix=None) dummy_folder = tmpdir.mkdir("dummy1") dummy_folder.join("dummy_module.py").write('FOOB = "bar"') dummy_folder.join("__init__.py").write('print("initing dummy...")') settings_loader(settings, "dummy1.dummy_module") assert settings.exists("FOOB") assert settings.FOOB == "bar" def test_load_using_settings_loader_with_environments(tmpdir): settings = Dynaconf(environments=True) dummy_folder = tmpdir.mkdir("dummy2") dummy_folder.join("dummy_module.py").write('FOOB = "bar"') dummy_folder.join("__init__.py").write('print("initing dummy...")') settings_loader(settings, "dummy2.dummy_module") assert settings.exists("FOOB") assert settings.FOOB == "bar" assert settings.current_env == "DEVELOPMENT" def test_load_using_settings_loader_with_temporary_env(tmpdir): settings = Dynaconf(environments=True) dummya_folder = tmpdir.mkdir("dummya") dummya_folder.join("dummya_module.py").write('FOOB = "bar"') dummya_folder.join("__init__.py").write('print("initing dummya...")') # change the env on the fly only for this load settings_loader(settings, "dummya.dummya_module", env="PRODUCTION") assert settings.exists("FOOB") assert settings.FOOB == "bar" assert ( settings.current_env == "DEVELOPMENT" ) # does not affect the current env def test_load_using_settings_loader_with_multi_temporary_env(tmpdir): settings = Dynaconf(environments=True) dummyab_folder = tmpdir.mkdir("dummyab") dummyab_folder.join("dummyab_module.py").write('FOOB = "bar"') dummyab_folder.join("__init__.py").write('print("initing dummyab...")') dummyab_folder.join("quiet_dummyab_module.py").write("DEBUG = False") dummyab_folder.join("other_dummyab_module.py").write('FOOB = "zaz"') # change the env on the fly only for this load settings_loader( settings, "dummyab.dummyab_module", env="PRODUCTION,quiet,OTHER" ) assert settings.exists("FOOB") assert settings.FOOB == "zaz" # comes from other_dummyab_module.py assert ( settings.current_env == "DEVELOPMENT" ) # does not affect the current env assert settings.DEBUG is False def test_load_using_settings_loader_with_set_env(tmpdir): settings = Dynaconf(environments=True) settings.setenv("PRODUCTION") dummyb_folder = tmpdir.mkdir("dummyb") dummyb_folder.join("dummyb_module.py").write('FOOB = "bar"') dummyb_folder.join("__init__.py").write('print("initing dummyb...")') settings_loader(settings, "dummyb.dummyb_module") assert settings.exists("FOOB") assert settings.FOOB == "bar" assert settings.current_env == "PRODUCTION" def test_load_using_settings_loader_with_one_env_named_file_module_path( tmpdir, ): settings = Dynaconf(environments=True) settings.setenv("PRODUCTION") dummyc_folder = tmpdir.mkdir("dummyc") dummyc_folder.join("dummyc_module.py").write('FOOB = "bar"') dummyc_folder.join("__init__.py").write('print("initing dummyc...")') dummyc_folder.join("production_dummyc_module.py").write('BAZ = "zaz"') settings_loader(settings, "dummyc.dummyc_module") assert settings.exists("FOOB") assert settings.FOOB == "bar" assert settings.current_env == "PRODUCTION" assert settings.BAZ == "zaz" def test_load_using_settings_loader_with_one_env_named_file_file_path(tmpdir): settings = Dynaconf(environments=True) settings.setenv("PRODUCTION") dummyd_folder = tmpdir.mkdir("dummyd") dummyd_folder.join("dummyd_module.py").write('FOOB = "bar"') dummyd_folder.join("__init__.py").write('print("initing dummyd...")') dummyd_folder.join("production_dummyd_module.py").write('BAZ = "zaz"') settings_loader(settings, "dummyd/dummyd_module.py") assert settings.exists("FOOB") assert settings.FOOB == "bar" assert settings.current_env == "PRODUCTION" assert settings.BAZ == "zaz" def test_load_using_settings_loader_with_one_env_named_file_module_path_multi_env( tmpdir, ): settings = Dynaconf(environments=True) settings.setenv("PRODUCTION,special") dummye_folder = tmpdir.mkdir("dummye") dummye_folder.join("dummye_module.py").write('FOOB = "bar"') dummye_folder.join("__init__.py").write('print("initing dummye...")') dummye_folder.join("production_dummye_module.py").write('BAZ = "zaz"') dummye_folder.join("special_dummye_module.py").write("DEBUG = False") settings_loader(settings, "dummye.dummye_module") assert settings.exists("FOOB") assert settings.FOOB == "bar" assert "PRODUCTION" in settings.current_env assert settings.BAZ == "zaz" assert settings.DEBUG is False # comes from special_ def test_load_using_settings_loader_with_one_env_named_file_file_path_multi_env( tmpdir, ): settings = Dynaconf(environments=True) settings.setenv("PRODUCTION,special") dummyf_folder = tmpdir.mkdir("dummyf") dummyf_folder.join("dummyf_module.py").write('FOOB = "bar"') dummyf_folder.join("__init__.py").write('print("initing dummyf...")') dummyf_folder.join("production_dummyf_module.py").write('BAZ = "zaz"') dummyf_folder.join("special_dummyf_module.py").write("DEBUG = True") settings_loader(settings, "dummyf/dummyf_module.py") assert settings.exists("FOOB") assert settings.FOOB == "bar" assert "PRODUCTION" in settings.current_env assert settings.BAZ == "zaz" assert settings.DEBUG is True # comes from special_ ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_toml_loader.py0000644000000000000000000001737415156311032020622 0ustar00pbrochadpbrochadfrom __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" [extra] password = "@int 77777" [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_multiple_envs(tmpdir): """Asserts loader can read from TOML when multiple comma separated envs are provided""" _settings = LazySettings(environments=True) load(_settings, filename=TOML, env="DEVELOPMENT,EXTRA") assert _settings.HOST == "devserver.com" # comes from [development] assert _settings.PASSWORD == 77777 # comes from [extra] assert _settings.GLOBAL_VALUE == "global" # comes from [global] 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 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_utils.py0000644000000000000000000004513015156324234017460 0ustar00pbrochadpbrochadfrom __future__ import annotations import json import os import sys from collections import namedtuple from pathlib import Path from textwrap import dedent import pytest from dynaconf import add_converter from dynaconf import default_settings from dynaconf import Dynaconf from dynaconf import DynaconfFormatError from dynaconf import DynaconfParseError 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 prepare_json 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 boolean_fix 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_unparsable(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) == "" 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_split 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) def __contains__(self, key): value = getattr(self, key, missing) if value is missing: return False return True 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", ] def create_file(filename: str, data: str) -> str: with open(filename, "w", encoding="utf-8") as f: f.write(dedent(data)) return filename @pytest.mark.skipif( sys.platform.startswith("win"), reason="Doesn't work on windows due to backslash decoding errors", ) def test_add_converter_path_example(tmp_path): """Assert add_converter Path example works""" add_converter("path", Path) fn = create_file( tmp_path / "settings.yaml", f"""\ my_path: "@path {Path.home()}" """, ) settings = Dynaconf(settings_file=fn) assert isinstance(settings.my_path, Path) def test_boolean_fix(): """Assert boolean fix works""" assert boolean_fix("True") == "true" assert boolean_fix("False") == "false" assert boolean_fix("NotOnlyTrue") == "NotOnlyTrue" assert boolean_fix("TrueNotOnly") == "TrueNotOnly" assert boolean_fix("FalseNotOnly") == "FalseNotOnly" assert boolean_fix("NotOnlyFalse") == "NotOnlyFalse" def test_get_converter(settings): """Ensure the work of @get converter""" settings.set("FOO", 12) settings.set("BAR", "@get FOO") assert settings.BAR == settings.FOO == 12 settings.set("ZAZ", "@get RAZ @float 42") assert settings.ZAZ == 42.0 settings.set("ZAZ", "@get RAZ 34 @float") assert settings.ZAZ == 34.0 settings.set("STRINGFLOAT", "23.2") settings.set("ZAZ", "@get STRINGFLOAT @float") assert settings.ZAZ == 23.2 def test_get_converter_with_1_levels(settings): """Ensure the work of @get converter on nested data""" settings.set("NAME", "Wilsom") settings.set("OTHERNAME", "@get name") assert settings.othername == "Wilsom" def test_get_converter_with_2_levels(): settings = Dynaconf() """Ensure the work of @get converter on nested data""" settings.set("RESTFRAMEWORK", {"AUTHENTICATION_CLASSES": ["A", "B"]}) assert settings.RESTFRAMEWORK # pulp ansible settings.set("MYCLASSES", "@get RESTFRAMEWORK.AUTHENTICATION_CLASSES") assert settings.MYCLASSES[0] == "A" def test_get_converter_with_3_levels(settings): """Ensure the work of @get converter on nested data""" settings.set("FOO__BAR__ZAZ", 12) settings.set("BAR", "@get FOO.BAR.ZAZ") assert settings.FOO.BAR.ZAZ == 12 assert settings.BAR == 12 def test_get_converter_error_when_converting(settings): """Malformed declaration errors""" settings.set("BLA", "@get") with pytest.raises(DynaconfFormatError): settings.BLA def test_get_converter_error_when_not_found(settings): """Malformed declaration errors""" settings.set("BLA", "@get BATATA123") with pytest.raises(DynaconfParseError): settings.BLA @pytest.mark.parametrize( "input_expected", [ ({"path": Path("foo")}, {"path": "foo"}), ({1: Path("foo")}, {"1": "foo"}), ({True: Path("foo")}, {"True": "foo"}), ([Path("one"), Path("two"), 1, True], ["one", "two", 1, True]), (1, 1), (True, True), ("test", "test"), ], ) def test_prepare_json(input_expected): data, expected = input_expected assert prepare_json(data) == expected ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_validators.py0000644000000000000000000006212415156311032020462 0ustar00pbrochadpbrochadfrom __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 (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 that 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) def test_is_type_of__raises__with_type_error(): """ When invalid type is given to is_type_of, should raise ValidationError """ settings = Dynaconf( validators=[Validator("ENV_FOR_DYNACONF", is_type_of="invalid")] ) with pytest.raises(ValidationError): settings.validators.validate() ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_validators_conditions.py0000644000000000000000000000331715156311032022712 0ustar00pbrochadpbrochadfrom __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]) ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773775004.0 dynaconf-3.2.13/tests/test_vault.py0000644000000000000000000001114415156324234017451 0ustar00pbrochadpbrochadfrom __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 from dynaconf.utils.inspect import get_history 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" @pytest.mark.integration def test_vault_has_proper_source_metadata(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) history = get_history( settings, filter_callable=lambda s: s.loader == "vault" ) assert history[0]["env"] == "default" assert history[0]["value"]["SECRET"] == "vault_works_in_default" assert history[1]["env"] == "dev" assert history[1]["value"]["SECRET"] == "vault_works_in_dev" assert history[2]["env"] == "prod" assert history[2]["value"]["SECRET"] == "vault_works_in_prod" ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/tests/test_yaml_loader.py0000644000000000000000000003762615156311032020613 0ustar00pbrochadpbrochadfrom __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__)), ) @pytest.fixture(scope="module") def multidoc_settings(): return LazySettings( environments=True, ENV_FOR_DYNACONF="PRODUCTION", YAML_LOADER_FOR_DYNACONF="safe_load_all", ) 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] MULTIDOC = "\n---\n".join(YAMLS) 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_load_from_multidoc_yaml(multidoc_settings): """Assert loads from YAML string""" settings = multidoc_settings load(settings, filename=MULTIDOC) 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 ././@PaxHeader0000000000000000000000000000007100000000000010213 xustar0015 uid=4214970 15 gid=4214970 27 mtime=1773776274.922122 dynaconf-3.2.13/vendor_licenses/0000755000000000000000000000000015156326623016730 5ustar00pbrochadpbrochad././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/vendor_licenses/box-LICENSE.txt0000644000000000000000000000206415156311032021327 0ustar00pbrochadpbrochadMIT 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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/vendor_licenses/click-LICENSE.rst0000644000000000000000000000270315156311032021615 0ustar00pbrochadpbrochadCopyright 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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/vendor_licenses/licenses.sh0000755000000000000000000000140115156311032021054 0ustar00pbrochadpbrochad#!/usr/bin/bash # SPDX-FileCopyrightText: 2022 Maxwell G # 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 ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/vendor_licenses/python-dotenv-LICENSE.txt0000644000000000000000000001077015156311032023360 0ustar00pbrochadpbrochadpython-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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/vendor_licenses/ruamel.yaml-LICENSE.txt0000644000000000000000000000214115156311032022761 0ustar00pbrochadpbrochad 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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/vendor_licenses/toml-LICENSE.txt0000644000000000000000000000234515156311032021514 0ustar00pbrochadpbrochadThe 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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773769242.0 dynaconf-3.2.13/vendor_licenses/tomli-LICENSE.txt0000644000000000000000000000206015156311032021657 0ustar00pbrochadpbrochadMIT 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. ././@PaxHeader0000000000000000000000000000006400000000000010215 xustar0015 uid=4214970 15 gid=4214970 22 mtime=1773776269.0 dynaconf-3.2.13/vendor_licenses/vendor_versions.txt0000644000000000000000000000014415156326615022716 0ustar00pbrochadpbrochadpython-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