Skip to content

Commit

Permalink
Merge branch 'master' into request-id
Browse files Browse the repository at this point in the history
  • Loading branch information
ahopkins authored Jan 18, 2021
2 parents 9041994 + 0c252e7 commit d820e80
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 264 deletions.
54 changes: 30 additions & 24 deletions sanic/config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
from inspect import isclass
from os import environ
from pathlib import Path
from typing import Any, Union

# NOTE(tomaszdrozdz): remove in version: 21.3
# We replace from_envvar(), from_object(), from_pyfile() config object methods
# with one simpler update_config() method.
# We also replace "loading module from file code" in from_pyfile()
# in a favour of load_module_from_file_location().
# Please see pull request: 1903
# and issue: 1895
from .deprecated import from_envvar, from_object, from_pyfile # noqa
from .utils import load_module_from_file_location, str_to_bool


Expand Down Expand Up @@ -69,17 +63,6 @@ def __getattr__(self, attr):
def __setattr__(self, attr, value):
self[attr] = value

# NOTE(tomaszdrozdz): remove in version: 21.3
# We replace from_envvar(), from_object(), from_pyfile() config object
# methods with one simpler update_config() method.
# We also replace "loading module from file code" in from_pyfile()
# in a favour of load_module_from_file_location().
# Please see pull request: 1903
# and issue: 1895
from_envvar = from_envvar
from_pyfile = from_pyfile
from_object = from_object

def load_environment_vars(self, prefix=SANIC_PREFIX):
"""
Looks for prefixed environment variables and applies
Expand All @@ -100,42 +83,65 @@ def load_environment_vars(self, prefix=SANIC_PREFIX):
self[config_key] = v

def update_config(self, config: Union[bytes, str, dict, Any]):
"""Update app.config.
"""
Update app.config.
Note:: only upper case settings are considered.
You can upload app config by providing path to py file
holding settings.
.. code-block:: python
# /some/py/file
A = 1
B = 2
config.update_config("${some}/py/file")
.. code-block:: python
config.update_config("${some}/py/file")
Yes you can put environment variable here, but they must be provided
in format: ${some_env_var}, and mark that $some_env_var is treated
as plain string.
You can upload app config by providing dict holding settings.
.. code-block:: python
d = {"A": 1, "B": 2}
config.update_config(d)
You can upload app config by providing any object holding settings,
but in such case config.__dict__ will be used as dict holding settings.
.. code-block:: python
class C:
A = 1
B = 2
config.update_config(C)"""
if isinstance(config, (bytes, str)):
config.update_config(C)
"""

if isinstance(config, (bytes, str, Path)):
config = load_module_from_file_location(location=config)

if not isinstance(config, dict):
config = config.__dict__
cfg = {}
if not isclass(config):
cfg.update(
{
key: getattr(config, key)
for key in config.__class__.__dict__.keys()
}
)

config = dict(config.__dict__)
config.update(cfg)

config = dict(filter(lambda i: i[0].isupper(), config.items()))

self.update(config)

load = update_config
2 changes: 1 addition & 1 deletion sanic/cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def __setitem__(self, key, value):
if value is not False:
if key.lower() == "max-age":
if not str(value).isdigit():
value = DEFAULT_MAX_AGE
raise ValueError("Cookie max-age must be an integer")
elif key.lower() == "expires":
if not isinstance(value, datetime):
raise TypeError(
Expand Down
106 changes: 0 additions & 106 deletions sanic/deprecated.py

This file was deleted.

90 changes: 61 additions & 29 deletions sanic/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import types

from importlib.util import module_from_spec, spec_from_file_location
from os import environ as os_environ
from pathlib import Path
from re import findall as re_findall
from typing import Union

from .exceptions import LoadFileException
from sanic.exceptions import LoadFileException, PyFileError
from sanic.helpers import import_string


def str_to_bool(val: str) -> bool:
Expand Down Expand Up @@ -39,7 +43,7 @@ def str_to_bool(val: str) -> bool:


def load_module_from_file_location(
location: Union[bytes, str], encoding: str = "utf8", *args, **kwargs
location: Union[bytes, str, Path], encoding: str = "utf8", *args, **kwargs
):
"""Returns loaded module provided as a file path.
Expand Down Expand Up @@ -67,33 +71,61 @@ def load_module_from_file_location(
"/some/path/${some_env_var}"
)
"""

# 1) Parse location.
if isinstance(location, bytes):
location = location.decode(encoding)

# A) Check if location contains any environment variables
# in format ${some_env_var}.
env_vars_in_location = set(re_findall(r"\${(.+?)}", location))

# B) Check these variables exists in environment.
not_defined_env_vars = env_vars_in_location.difference(os_environ.keys())
if not_defined_env_vars:
raise LoadFileException(
"The following environment variables are not set: "
f"{', '.join(not_defined_env_vars)}"
)

# C) Substitute them in location.
for env_var in env_vars_in_location:
location = location.replace("${" + env_var + "}", os_environ[env_var])

# 2) Load and return module.
name = location.split("/")[-1].split(".")[
0
] # get just the file name without path and .py extension
_mod_spec = spec_from_file_location(name, location, *args, **kwargs)
module = module_from_spec(_mod_spec)
_mod_spec.loader.exec_module(module) # type: ignore

return module
if isinstance(location, Path) or "/" in location or "$" in location:

if not isinstance(location, Path):
# A) Check if location contains any environment variables
# in format ${some_env_var}.
env_vars_in_location = set(re_findall(r"\${(.+?)}", location))

# B) Check these variables exists in environment.
not_defined_env_vars = env_vars_in_location.difference(
os_environ.keys()
)
if not_defined_env_vars:
raise LoadFileException(
"The following environment variables are not set: "
f"{', '.join(not_defined_env_vars)}"
)

# C) Substitute them in location.
for env_var in env_vars_in_location:
location = location.replace(
"${" + env_var + "}", os_environ[env_var]
)

location = str(location)
if ".py" in location:
name = location.split("/")[-1].split(".")[
0
] # get just the file name without path and .py extension
_mod_spec = spec_from_file_location(
name, location, *args, **kwargs
)
module = module_from_spec(_mod_spec)
_mod_spec.loader.exec_module(module) # type: ignore

else:
module = types.ModuleType("config")
module.__file__ = str(location)
try:
with open(location) as config_file:
exec( # nosec
compile(config_file.read(), location, "exec"),
module.__dict__,
)
except IOError as e:
e.strerror = "Unable to load configuration file (e.strerror)"
raise
except Exception as e:
raise PyFileError(location) from e

return module
else:
try:
return import_string(location)
except ValueError:
raise IOError("Unable to load configuration %s" % str(location))
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ def open_local(paths, mode="r", encoding="utf8"):
"pytest-sanic",
"pytest-sugar",
"pytest-benchmark",
"pytest-dependency",
]

docs_require = [
Expand Down
Loading

0 comments on commit d820e80

Please sign in to comment.