Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
rochacbruno committed Jul 25, 2021
2 parents fb9ab11 + db2983a commit d8785d3
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 8 deletions.
31 changes: 31 additions & 0 deletions .github/dependabot.yml
@@ -0,0 +1,31 @@
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
time: "08:00"
open-pull-requests-limit: 10
ignore:
- dependency-name: mkdocs-material
versions:
- 6.2.6
- 6.2.7
- 6.2.8
- 7.0.0
- 7.0.1
- 7.0.2
- 7.0.3
- 7.0.7
- 7.1.0
- 7.1.1
- 7.1.2
- dependency-name: pymdown-extensions
versions:
- 8.1.1
- dependency-name: mkdocs-git-revision-date-localized-plugin
versions:
- "0.8"
- dependency-name: mkdocs-versioning
versions:
- 0.4.0
20 changes: 14 additions & 6 deletions azure-pipelines.yml
Expand Up @@ -14,7 +14,7 @@ jobs:

- job: 'Adequacy'
pool:
vmImage: 'Ubuntu-16.04'
vmImage: 'Ubuntu-18.04'
strategy:
matrix:
Python37:
Expand All @@ -33,7 +33,7 @@ jobs:
- job: 'LinuxInstall'
dependsOn: Adequacy
pool:
vmImage: 'Ubuntu-16.04'
vmImage: 'Ubuntu-18.04'
strategy:
matrix:
Python36:
Expand All @@ -42,6 +42,8 @@ jobs:
python.version: '3.7'
Python38:
python.version: '3.8'
Python39:
python.version: '3.9'
maxParallel: 4

steps:
Expand All @@ -64,7 +66,7 @@ jobs:
variables:
- group: codecov
pool:
vmImage: 'Ubuntu-16.04'
vmImage: 'Ubuntu-18.04'
strategy:
matrix:
Python36:
Expand All @@ -73,6 +75,8 @@ jobs:
python.version: '3.7'
Python38:
python.version: '3.8'
Python39:
python.version: '3.9'
maxParallel: 4

steps:
Expand Down Expand Up @@ -100,7 +104,7 @@ jobs:
- job: 'LinuxFunctional'
dependsOn: Adequacy
pool:
vmImage: 'Ubuntu-16.04'
vmImage: 'Ubuntu-18.04'
strategy:
matrix:
Python36:
Expand Down Expand Up @@ -133,6 +137,8 @@ jobs:
python.version: '3.7'
Python38:
python.version: '3.8'
Python39:
python.version: '3.9'
maxParallel: 4

steps:
Expand Down Expand Up @@ -164,6 +170,8 @@ jobs:
python.version: '3.7'
Python38:
python.version: '3.8'
Python39:
python.version: '3.9'
maxParallel: 4

steps:
Expand Down Expand Up @@ -216,7 +224,7 @@ jobs:
variables:
REDIS_ENABLED_FOR_DYNACONF: true
pool:
vmImage: 'Ubuntu-16.04'
vmImage: 'Ubuntu-18.04'
strategy:
matrix:
Python37:
Expand Down Expand Up @@ -248,7 +256,7 @@ jobs:
VAULT_ENABLED_FOR_DYNACONF: true
VAULT_TOKEN_FOR_DYNACONF: myroot
pool:
vmImage: 'Ubuntu-16.04'
vmImage: 'Ubuntu-18.04'
strategy:
matrix:
Python37:
Expand Down
16 changes: 16 additions & 0 deletions dynaconf/contrib/flask_dynaconf.py
@@ -1,4 +1,5 @@
import warnings
from collections import ChainMap
from contextlib import suppress

try:
Expand Down Expand Up @@ -165,6 +166,21 @@ def __setitem__(self, key, value):
"""
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 __iter__(self):
return self._chain_map().__iter__()

def __getattr__(self, name):
"""
First try to get value from dynaconf then from Flask Config
Expand Down
17 changes: 17 additions & 0 deletions dynaconf/utils/__init__.py
Expand Up @@ -366,3 +366,20 @@ def recursively_evaluate_lazy_format(value, settings):
)

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(type(n) == str for n in f)
6 changes: 6 additions & 0 deletions dynaconf/utils/parse_conf.py
Expand Up @@ -5,6 +5,7 @@
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
Expand Down Expand Up @@ -288,10 +289,15 @@ def _parse_conf_data(data, tomlfy=False, box_settings=None):

def parse_conf_data(data, tomlfy=False, box_settings=None):

# fix for https://github.com/rochacbruno/dynaconf/issues/595
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)
Expand Down
36 changes: 36 additions & 0 deletions example/fastapi_with_ini/app.py
@@ -0,0 +1,36 @@
from dynaconf import Dynaconf
from typing import Optional
from fastapi import FastAPI

settings = Dynaconf(
settings_file="config.toml", # location of config file
environments=["ansible", "puppet"], # available modes/environments
envvar_prefix="TEFLO", # prefix for exporting env vars
env_switcher="TEFLO_MODE", # Variable that controls mode switch
env="ANSIBLE", # Initial env/mode
)

app = FastAPI()

@app.get("/settings")
def read_root():
return settings

@app.get("/settings/{setting_name}")
def read_item(setting_name: str):
return {setting_name: settings.setting_name}

if settings.current_env == "ANSIBLE": # this is the initial default
assert settings.data_folder == "ansible/.teflo"

if settings.current_env == "PUPPET": # only when TEFLO_MODE=puppet
assert settings.data_folder == "puppet/.teflo"

assert settings.log_level == "info" # might be exported TEFLO_DEBUG_MODE

assert settings.workspace == "."

print(settings.data_folder)
print(settings["log_remove"])
print(settings.get("verbosity"))
print(settings.LOG_LEVEL)
14 changes: 14 additions & 0 deletions example/fastapi_with_ini/config.toml
@@ -0,0 +1,14 @@
[default]
log_level = "info"
workspace= "."
data_folder= ".teflo"

[ansible]
data_folder = "ansible/.teflo"
log_remove = true
verbosity = "v"

[puppet]
data_folder = "puppet/.teflo"
log_remove = false
verbosity = "vvv"
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -93,6 +93,7 @@ def read(*names, **kwargs):
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Topic :: Utilities",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
Expand Down
14 changes: 13 additions & 1 deletion tests/test_dynabox.py
@@ -1,3 +1,5 @@
from collections import namedtuple

import pytest

from dynaconf.utils.boxing import DynaBox
Expand All @@ -6,6 +8,9 @@
from dynaconf.vendor.box import BoxList


DBDATA = namedtuple("DbData", ["server", "port"])


box = DynaBox(
{
"server": {
Expand All @@ -16,11 +21,18 @@
"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/rochacbruno/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)
Expand Down
31 changes: 31 additions & 0 deletions tests/test_flask.py
@@ -1,10 +1,33 @@
from collections import namedtuple

import pytest
from flask import Flask

from dynaconf.contrib import FlaskDynaconf
from example.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__)
Expand Down Expand Up @@ -78,6 +101,14 @@ def test_flask_dynaconf(settings):

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)

with pytest.raises(KeyError):
app.config["NONEXISTENETVAR"]
Expand Down
16 changes: 16 additions & 0 deletions tests/test_utils.py
@@ -1,13 +1,15 @@
import io
import json
import os
from collections import namedtuple

import pytest

from dynaconf import default_settings
from dynaconf.loaders.json_loader import DynaconfEncoder
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
Expand All @@ -23,6 +25,20 @@
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"
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
@@ -1,5 +1,5 @@
[tox]
envlist = py36,py37,py38
envlist = py36,py37,py38,py39
whitelist_externals=make

[testenv]
Expand Down

0 comments on commit d8785d3

Please sign in to comment.