Add config.update support for setters (#2354)

This commit is contained in:
Adam Hopkins 2022-01-06 09:55:03 +02:00 committed by GitHub
parent dc3ccba527
commit 34d1dee407
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 76 additions and 26 deletions

View File

@ -2,9 +2,13 @@ name: "CodeQL"
on: on:
push: push:
branches: [ main ] branches:
- main
- "*LTS"
pull_request: pull_request:
branches: [ main ] branches:
- main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
schedule: schedule:
- cron: '25 16 * * 0' - cron: '25 16 * * 0'

View File

@ -3,6 +3,7 @@ on:
push: push:
branches: branches:
- main - main
- "*LTS"
tags: tags:
- "!*" # Do not execute on tags - "!*" # Do not execute on tags
pull_request: pull_request:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:

View File

@ -124,22 +124,27 @@ class Config(dict, metaclass=DescriptorMeta):
raise AttributeError(f"Config has no '{ke.args[0]}'") raise AttributeError(f"Config has no '{ke.args[0]}'")
def __setattr__(self, attr, value) -> None: 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}) self.update({attr: value})
def __setitem__(self, attr, value) -> None: def __setitem__(self, attr, value) -> None:
self.update({attr: value}) self.update({attr: value})
def update(self, *other, **kwargs) -> None: def update(self, *other, **kwargs) -> None:
other_mapping = {k: v for item in other for k, v in dict(item).items()} kwargs.update({k: v for item in other for k, v in dict(item).items()})
super().update(*other, **kwargs) setters: Dict[str, Any] = {
for attr, value in {**other_mapping, **kwargs}.items(): k: kwargs.pop(k)
for k in {**kwargs}.keys()
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) self._post_set(attr, value)
def _post_set(self, attr, value) -> None: def _post_set(self, attr, value) -> None:

View File

@ -5,7 +5,7 @@ from os import environ
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from textwrap import dedent from textwrap import dedent
from unittest.mock import Mock from unittest.mock import Mock, call
import pytest import pytest
@ -385,5 +385,24 @@ def test_config_set_methods(app, monkeypatch):
post_set.assert_called_once_with("FOO", 5) post_set.assert_called_once_with("FOO", 5)
post_set.reset_mock() post_set.reset_mock()
app.config.update_config({"FOO": 6}) app.config.update({"FOO": 6}, {"BAR": 7})
post_set.assert_called_once_with("FOO", 6) 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)

View File

@ -334,6 +334,22 @@ def test_config_fallback_before_and_after_startup(app):
assert response.content_type == "text/plain; charset=utf-8" 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): def test_config_fallback_bad_value(app):
message = "Unknown format: fake" message = "Unknown format: fake"
with pytest.raises(SanicException, match=message): with pytest.raises(SanicException, match=message):

View File

@ -62,19 +62,15 @@ def test_streaming_body_requests(app):
data = ["hello", "world"] 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) client = ReusableClient(app, port=1234)
async def stream(data):
for value in data:
yield value.encode("utf-8")
with client: with client:
_, response1 = client.post("/", data=Data(data)) _, response1 = client.post("/", data=stream(data))
_, response2 = client.post("/", data=Data(data)) _, response2 = client.post("/", data=stream(data))
assert response1.status == response2.status == 200 assert response1.status == response2.status == 200
assert response1.json["data"] == response2.json["data"] == data assert response1.json["data"] == response2.json["data"] == data

View File

@ -4,8 +4,8 @@ from unittest.mock import Mock, patch
import pytest import pytest
from sanic.server import loop
from sanic.compat import OS_IS_WINDOWS, UVLOOP_INSTALLED from sanic.compat import OS_IS_WINDOWS, UVLOOP_INSTALLED
from sanic.server import loop
@pytest.mark.skipif( @pytest.mark.skipif(