Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config.update support for setters #2354

Merged
merged 4 commits into from Jan 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/codeql-analysis.yml
Expand Up @@ -2,9 +2,13 @@ name: "CodeQL"

on:
push:
branches: [ main ]
branches:
- main
- "*LTS"
pull_request:
branches: [ main ]
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
schedule:
- cron: '25 16 * * 0'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Expand Up @@ -3,6 +3,7 @@ on:
push:
branches:
- main
- "*LTS"
tags:
- "!*" # Do not execute on tags
pull_request:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-bandit.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-docs.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-linter.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-python310.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-python37.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-python38.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-python39.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-type-check.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-windows.yml
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]

jobs:
Expand Down
25 changes: 15 additions & 10 deletions sanic/config.py
Expand Up @@ -124,22 +124,27 @@ def __getattr__(self, attr):
raise AttributeError(f"Config has no '{ke.args[0]}'")

def __setattr__(self, attr, value) -> None:
if attr in self.__class__.__setters__:
try:
super().__setattr__(attr, value)
except AttributeError:
...
else:
return None
self.update({attr: value})

def __setitem__(self, attr, value) -> None:
self.update({attr: value})

def update(self, *other, **kwargs) -> None:
other_mapping = {k: v for item in other for k, v in dict(item).items()}
super().update(*other, **kwargs)
for attr, value in {**other_mapping, **kwargs}.items():
kwargs.update({k: v for item in other for k, v in dict(item).items()})
setters: Dict[str, Any] = {
k: kwargs.pop(k)
for k in {**kwargs}.keys()
prryplatypus marked this conversation as resolved.
Show resolved Hide resolved
if k in self.__class__.__setters__
}

for key, value in setters.items():
try:
super().__setattr__(key, value)
except AttributeError:
...

super().update(**kwargs)
for attr, value in {**setters, **kwargs}.items():
self._post_set(attr, value)

def _post_set(self, attr, value) -> None:
Expand Down
25 changes: 22 additions & 3 deletions tests/test_config.py
Expand Up @@ -5,7 +5,7 @@
from pathlib import Path
from tempfile import TemporaryDirectory
from textwrap import dedent
from unittest.mock import Mock
from unittest.mock import Mock, call

import pytest

Expand Down Expand Up @@ -385,5 +385,24 @@ def test_config_set_methods(app, monkeypatch):
post_set.assert_called_once_with("FOO", 5)
post_set.reset_mock()

app.config.update_config({"FOO": 6})
post_set.assert_called_once_with("FOO", 6)
app.config.update({"FOO": 6}, {"BAR": 7})
post_set.assert_has_calls(
calls=[
call("FOO", 6),
call("BAR", 7),
]
)
post_set.reset_mock()

app.config.update({"FOO": 8}, BAR=9)
post_set.assert_has_calls(
calls=[
call("FOO", 8),
call("BAR", 9),
],
any_order=True,
)
post_set.reset_mock()

app.config.update_config({"FOO": 10})
post_set.assert_called_once_with("FOO", 10)
16 changes: 16 additions & 0 deletions tests/test_errorpages.py
Expand Up @@ -334,6 +334,22 @@ async def start(app, _):
assert response.content_type == "text/plain; charset=utf-8"


def test_config_fallback_using_update_dict(app):
app.config.update({"FALLBACK_ERROR_FORMAT": "text"})

_, response = app.test_client.get("/error")
assert response.status == 500
assert response.content_type == "text/plain; charset=utf-8"


def test_config_fallback_using_update_kwarg(app):
app.config.update(FALLBACK_ERROR_FORMAT="text")

_, response = app.test_client.get("/error")
assert response.status == 500
assert response.content_type == "text/plain; charset=utf-8"


def test_config_fallback_bad_value(app):
message = "Unknown format: fake"
with pytest.raises(SanicException, match=message):
Expand Down
16 changes: 6 additions & 10 deletions tests/test_pipelining.py
Expand Up @@ -62,19 +62,15 @@ async def handler(request):

data = ["hello", "world"]

class Data(AsyncByteStream):
def __init__(self, data):
self.data = data

async def __aiter__(self):
for value in self.data:
yield value.encode("utf-8")

client = ReusableClient(app, port=1234)

async def stream(data):
for value in data:
yield value.encode("utf-8")

with client:
_, response1 = client.post("/", data=Data(data))
_, response2 = client.post("/", data=Data(data))
_, response1 = client.post("/", data=stream(data))
_, response2 = client.post("/", data=stream(data))

assert response1.status == response2.status == 200
assert response1.json["data"] == response2.json["data"] == data
Expand Down
2 changes: 1 addition & 1 deletion tests/test_server_loop.py
Expand Up @@ -4,8 +4,8 @@

import pytest

from sanic.server import loop
from sanic.compat import OS_IS_WINDOWS, UVLOOP_INSTALLED
from sanic.server import loop


@pytest.mark.skipif(
Expand Down