diff --git a/sanic_ext/bootstrap.py b/sanic_ext/bootstrap.py index c3e4a514..6e20d16f 100644 --- a/sanic_ext/bootstrap.py +++ b/sanic_ext/bootstrap.py @@ -1,5 +1,7 @@ from __future__ import annotations +import os + from types import SimpleNamespace from typing import Any, Callable, Dict, List, Mapping, Optional, Type, Union from warnings import warn @@ -114,6 +116,8 @@ def __init__( started.add(ext) def _display(self): + if "SANIC_WORKER_IDENTIFIER" in os.environ: + return init_logs = ["Sanic Extensions:"] for extension in self.extensions: label = extension.render_label() diff --git a/sanic_ext/config.py b/sanic_ext/config.py index f410cb5e..ba4a5cc1 100644 --- a/sanic_ext/config.py +++ b/sanic_ext/config.py @@ -44,7 +44,13 @@ def __init__( injection_load_custom_constants: bool = False, logging: bool = False, logging_queue_max_size: int = 4096, - loggers: List[str] = ["sanic.access", "sanic.error", "sanic.root"], + loggers: List[str] = [ + "sanic.access", + "sanic.error", + "sanic.root", + "sanic.server", + "sanic.websockets", + ], oas: bool = True, oas_autodoc: bool = True, oas_custom_file: Optional[os.PathLike] = None, diff --git a/sanic_ext/exceptions.py b/sanic_ext/exceptions.py index f88b61c1..c04230cc 100644 --- a/sanic_ext/exceptions.py +++ b/sanic_ext/exceptions.py @@ -5,9 +5,7 @@ class ValidationError(SanicException): status_code = 400 -class InitError(SanicException): - ... +class InitError(SanicException): ... -class ExtensionNotFound(SanicException): - ... +class ExtensionNotFound(SanicException): ... diff --git a/sanic_ext/extensions/base.py b/sanic_ext/extensions/base.py index ad5fa67a..e5bf922a 100644 --- a/sanic_ext/extensions/base.py +++ b/sanic_ext/extensions/base.py @@ -46,8 +46,7 @@ def _startup(self, bootstrap): self._started = True @abstractmethod - def startup(self, bootstrap) -> None: - ... + def startup(self, bootstrap) -> None: ... def label(self): return "" diff --git a/sanic_ext/extensions/health/monitor.py b/sanic_ext/extensions/health/monitor.py index d43d8d73..ba486694 100644 --- a/sanic_ext/extensions/health/monitor.py +++ b/sanic_ext/extensions/health/monitor.py @@ -17,8 +17,7 @@ from sanic import Sanic -class Stale(ValueError): - ... +class Stale(ValueError): ... @dataclass diff --git a/sanic_ext/extensions/logging/extractor.py b/sanic_ext/extensions/logging/extractor.py new file mode 100644 index 00000000..9de958a9 --- /dev/null +++ b/sanic_ext/extensions/logging/extractor.py @@ -0,0 +1,116 @@ +import logging + +from typing import Any, Dict, Optional, TypedDict + + +class LoggerConfig(TypedDict): + level: str + propagate: bool + handlers: list[str] + + +class HandlerConfig(TypedDict): + class_: str + level: str + stream: Optional[str] + formatter: Optional[str] + + +class FormatterConfig(TypedDict): + class_: str + format: Optional[str] + datefmt: Optional[str] + + +class LoggingConfig(TypedDict): + version: int + disable_existing_loggers: bool + formatters: Dict[str, FormatterConfig] + handlers: Dict[str, HandlerConfig] + loggers: Dict[str, LoggerConfig] + + +class LoggingConfigExtractor: + def __init__(self): + self.version = 1 + self.disable_existing_loggers = False + self.formatters: Dict[str, FormatterConfig] = {} + self.handlers: Dict[str, HandlerConfig] = {} + self.loggers: Dict[str, LoggerConfig] = {} + + def add_logger(self, logger: logging.Logger): + self._extract_logger_config(logger) + self._extract_handlers(logger) + + def compile(self) -> LoggingConfig: + output = { + "version": self.version, + "disable_existing_loggers": self.disable_existing_loggers, + "formatters": self.formatters, + "handlers": self.handlers, + "loggers": self.loggers, + } + return self._clean(output) + + def _extract_logger_config(self, logger: logging.Logger): + config: LoggerConfig = { + "level": logging.getLevelName(logger.level), + "propagate": logger.propagate, + "handlers": [handler.get_name() for handler in logger.handlers], + } + self.loggers[logger.name] = config + + def _extract_handlers(self, logger: logging.Logger): + for handler in logger.handlers: + self._extract_handler_config(handler) + + def _extract_handler_config(self, handler: logging.Handler): + handler_name = handler.get_name() + if handler_name in self.handlers: + return + config: HandlerConfig = { + "class_": self._full_name(handler), + "level": logging.getLevelName(handler.level), + "formatter": ( + self._formatter_name(handler.formatter) + if handler.formatter + else None + ), + "stream": None, + } + # if (stream := getattr(handler, "stream", None)) and ( + # stream_name := getattr(stream, "name", None) + # ): + # config["stream"] = stream_name + self.handlers[handler_name] = config + if handler.formatter: + self._extract_formatter_config(handler.formatter) + + def _extract_formatter_config(self, formatter: logging.Formatter): + formatter_name = self._formatter_name(formatter) + if formatter_name in self.formatters: + return + config: FormatterConfig = { + "class_": self._full_name(formatter), + "format": formatter._fmt, + "datefmt": formatter.datefmt, + } + self.formatters[formatter_name] = config + + def _clean(self, d: Dict[str, Any]) -> Dict[str, Any]: + return { + k.replace("class_", "class"): self._clean(v) + if isinstance(v, dict) + else v + for k, v in d.items() + } + + @staticmethod + def _formatter_name( + formatter: logging.Formatter, prefix: str = "formatter" + ): + return f"{prefix}_{formatter.__class__.__name__}".lower() + + @staticmethod + def _full_name(obj): + return f"{obj.__module__}.{obj.__class__.__name__}" diff --git a/sanic_ext/extensions/logging/logger.py b/sanic_ext/extensions/logging/logger.py index 393baa9d..6cc1b0b9 100644 --- a/sanic_ext/extensions/logging/logger.py +++ b/sanic_ext/extensions/logging/logger.py @@ -10,7 +10,11 @@ from typing import List from sanic import Sanic -from sanic.log import server_logger +from sanic.log import logger as root_logger +from sanic.log import logger as server_logger +from sanic.logging.setup import setup_logging + +from sanic_ext.extensions.logging.extractor import LoggingConfigExtractor async def prepare_logger(app: Sanic, *_): @@ -19,12 +23,18 @@ async def prepare_logger(app: Sanic, *_): async def setup_logger(app: Sanic, *_): logger = Logger() + extractor = LoggingConfigExtractor() + for logger_name in app.config.LOGGERS: + l = logging.getLogger(logger_name) + extractor.add_logger(l) app.manager.manage( "Logger", logger, { "queue": app.shared_ctx.logger_queue, + "config": extractor.compile(), }, + transient=True, ) @@ -59,7 +69,7 @@ async def remove_server_logging(app: Sanic): class Logger: - LOGGERS = [] + LOGGERS: List[str] = [] def __init__(self): self.run = True @@ -67,10 +77,24 @@ def __init__(self): logger: logging.getLogger(logger) for logger in self.LOGGERS } - def __call__(self, queue) -> None: + def __call__(self, queue, config) -> None: signal_func(SIGINT, self.stop) signal_func(SIGTERM, self.stop) + logging.config.dictConfig(config) + + setup_loggers = set(config["loggers"].keys()) + enabled_loggers = set(self.loggers.keys()) + missing = enabled_loggers - setup_loggers + root_logger.info( + f"Setup background logging for: {', '.join(setup_loggers)}" + ) + if missing: + root_logger.warning( + f"Logger config not found for: {', '.join(missing)}" + ) + setup_logging(True, no_color=False, log_extra=True) + while self.run: try: record: LogRecord = queue.get(timeout=0.05) diff --git a/sanic_ext/extensions/openapi/blueprint.py b/sanic_ext/extensions/openapi/blueprint.py index c7cdbc6c..89f86093 100644 --- a/sanic_ext/extensions/openapi/blueprint.py +++ b/sanic_ext/extensions/openapi/blueprint.py @@ -173,9 +173,9 @@ def build_spec(app, loop): ): operation.autodoc(docstring) - operation._default[ - "operationId" - ] = f"{method.lower()}~{route_name}" + operation._default["operationId"] = ( + f"{method.lower()}~{route_name}" + ) operation._default["summary"] = clean_route_name(route_name) if host: diff --git a/sanic_ext/extensions/openapi/builders.py b/sanic_ext/extensions/openapi/builders.py index a9d4bea1..dae7d029 100644 --- a/sanic_ext/extensions/openapi/builders.py +++ b/sanic_ext/extensions/openapi/builders.py @@ -438,8 +438,10 @@ def _build_paths(self, app: Sanic) -> Dict: def _build_security(self): return [ - {sec.fields["name"]: sec.fields["value"]} - if sec.fields["name"] is not None - else {} + ( + {sec.fields["name"]: sec.fields["value"]} + if sec.fields["name"] is not None + else {} + ) for sec in self.security ] diff --git a/sanic_ext/extensions/openapi/definitions.py b/sanic_ext/extensions/openapi/definitions.py index e29c66ff..0b93fa47 100644 --- a/sanic_ext/extensions/openapi/definitions.py +++ b/sanic_ext/extensions/openapi/definitions.py @@ -4,6 +4,7 @@ I.e., the objects described https://swagger.io/docs/specification """ + from __future__ import annotations from inspect import isclass diff --git a/sanic_ext/extensions/openapi/openapi.py b/sanic_ext/extensions/openapi/openapi.py index 6f0330bb..8d26cec6 100644 --- a/sanic_ext/extensions/openapi/openapi.py +++ b/sanic_ext/extensions/openapi/openapi.py @@ -3,6 +3,7 @@ documentation to OperationStore() and components created in the blueprints. """ + from functools import wraps from inspect import isawaitable, isclass from typing import ( @@ -94,13 +95,11 @@ def _content_or_component(content): @overload -def exclude(flag: bool = True, *, bp: Blueprint) -> None: - ... +def exclude(flag: bool = True, *, bp: Blueprint) -> None: ... @overload -def exclude(flag: bool = True) -> Callable: - ... +def exclude(flag: bool = True) -> Callable: ... def exclude(flag: bool = True, *, bp: Optional[Blueprint] = None): @@ -247,8 +246,7 @@ def parameter( *, parameter: definitions.Parameter, **kwargs, -) -> Callable[[T], T]: - ... +) -> Callable[[T], T]: ... @overload @@ -258,8 +256,7 @@ def parameter( location: None, parameter: definitions.Parameter, **kwargs, -) -> Callable[[T], T]: - ... +) -> Callable[[T], T]: ... @overload @@ -269,8 +266,7 @@ def parameter( location: Optional[str] = None, parameter: None = None, **kwargs, -) -> Callable[[T], T]: - ... +) -> Callable[[T], T]: ... def parameter( diff --git a/sanic_ext/extensions/openapi/types.py b/sanic_ext/extensions/openapi/types.py index 8e7950f4..008d57df 100644 --- a/sanic_ext/extensions/openapi/types.py +++ b/sanic_ext/extensions/openapi/types.py @@ -344,9 +344,11 @@ def make(cls, value: Any, **kwargs): fields = [ MsgspecAdapter( name=f.name, - default=MISSING - if f.default in (UNSET, NODEFAULT) - else f.default, + default=( + MISSING + if f.default in (UNSET, NODEFAULT) + else f.default + ), metadata=getattr(f.type, "extra", {}), ) for f in msgspec_type_info(value).fields diff --git a/sanic_ext/extensions/templating/render.py b/sanic_ext/extensions/templating/render.py index 0650f9dc..ce67066a 100644 --- a/sanic_ext/extensions/templating/render.py +++ b/sanic_ext/extensions/templating/render.py @@ -16,8 +16,7 @@ from jinja2 import Environment -class TemplateResponse(HTTPResponse): - ... +class TemplateResponse(HTTPResponse): ... class LazyResponse(TemplateResponse): diff --git a/sanic_ext/extras/validation/validators.py b/sanic_ext/extras/validation/validators.py index c3b2276d..d04b4494 100644 --- a/sanic_ext/extras/validation/validators.py +++ b/sanic_ext/extras/validation/validators.py @@ -27,8 +27,8 @@ def validate_body( except VALIDATION_ERROR as e: raise ValidationError( f"Invalid request body: {model.__name__}. Error: {e}", - extra={"exception": e}, - ) + extra={"exception": str(e)}, + ) from None def _msgspec_validate_instance(model, body, allow_coerce): diff --git a/setup.py b/setup.py index 0724189a..2c7978e7 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,7 @@ """ Sanic """ + from setuptools import setup + setup() diff --git a/tests/extensions/http/test_methods.py b/tests/extensions/http/test_methods.py index 7f46f540..abe918ee 100644 --- a/tests/extensions/http/test_methods.py +++ b/tests/extensions/http/test_methods.py @@ -57,9 +57,7 @@ def test_auto_trace(bare_app: Sanic): async def foo_handler(_): return text("...") - request, response = bare_app.test_client.request( - "/foo", http_method="trace" - ) + request, response = bare_app.test_client.request("/foo", http_method="trace") assert response.status == 200 assert response.body.startswith(request.head) diff --git a/tests/extensions/injection/test_add_dependency.py b/tests/extensions/injection/test_add_dependency.py index 15071665..aaa6e11f 100644 --- a/tests/extensions/injection/test_add_dependency.py +++ b/tests/extensions/injection/test_add_dependency.py @@ -99,8 +99,7 @@ def create(cls, request: Request, c: C, d: D): return cls(c, d) -class Alpha: - ... +class Alpha: ... class Beta: @@ -136,9 +135,7 @@ def make_gamma_without_request(beta: Beta): def test_injection_not_allowed_when_ext_disabled(bare_app): ext = Extend(bare_app, built_in_extensions=False) - with pytest.raises( - SanicException, match="Injection extension not enabled" - ): + with pytest.raises(SanicException, match="Injection extension not enabled"): ext.add_dependency(1, 2) @@ -197,9 +194,7 @@ def test_injection_of_object_with_constructor(app): async def person_details(request, person_id: PersonID, person: Person): request.ctx.person_id = person_id request.ctx.person = person - return text( - f"{person.person_id.person_id}\n{person.name}\n{person.age}" - ) + return text(f"{person.person_id.person_id}\n{person.name}\n{person.age}") app.ext.add_dependency(Person, Person.create) app.ext.add_dependency(PersonID) diff --git a/tests/extensions/injection/test_constants.py b/tests/extensions/injection/test_constants.py index 19c61269..5d7904e8 100644 --- a/tests/extensions/injection/test_constants.py +++ b/tests/extensions/injection/test_constants.py @@ -61,8 +61,6 @@ def test_load_custom(app: Sanic): def test_load_overwrite(app: Sanic): app.ext.load_constants({"foo": "bar"}) - with pytest.raises( - ValueError, match="A value for FOO has already been assigned" - ): + with pytest.raises(ValueError, match="A value for FOO has already been assigned"): app.ext.load_constants({"foo": "bar"}) app.ext.load_constants({"foo": "bar"}, True) diff --git a/tests/extensions/injection/test_injection_registry.py b/tests/extensions/injection/test_injection_registry.py index d2484216..1ae13738 100644 --- a/tests/extensions/injection/test_injection_registry.py +++ b/tests/extensions/injection/test_injection_registry.py @@ -47,8 +47,7 @@ def create(cls, chicken: Chicken): return cls() -class InheritedRequest(Request): - ... +class InheritedRequest(Request): ... class Baz: diff --git a/tests/extensions/logging/test_custom_background_logger.py b/tests/extensions/logging/test_custom_background_logger.py index ea8afc96..c50e3446 100644 --- a/tests/extensions/logging/test_custom_background_logger.py +++ b/tests/extensions/logging/test_custom_background_logger.py @@ -17,6 +17,4 @@ def test_custom_background_logger(app: Sanic): assert "sanic.root" in Logger.LOGGERS logger = Logger() - assert ( - len(logger.loggers) == len(Logger.LOGGERS) == len(app.config.LOGGERS) - ) + assert len(logger.loggers) == len(Logger.LOGGERS) == len(app.config.LOGGERS) diff --git a/tests/extensions/openapi/test_decorators.py b/tests/extensions/openapi/test_decorators.py index b5c690f2..1b56b55b 100644 --- a/tests/extensions/openapi/test_decorators.py +++ b/tests/extensions/openapi/test_decorators.py @@ -48,11 +48,7 @@ class BigFoo: (({"application/json": LittleFoo},), "application/json"), ((openapi.definitions.RequestBody(LittleFoo),), "*/*"), ( - ( - openapi.definitions.RequestBody( - {"application/json": LittleFoo} - ), - ), + (openapi.definitions.RequestBody({"application/json": LittleFoo}),), "application/json", ), ), @@ -60,8 +56,7 @@ class BigFoo: def test_body_decorator(app, args, content_type): @app.route("/") @openapi.body(*args, description="something") - async def handler(_): - ... + async def handler(_): ... spec = get_path(app, "/") @@ -75,19 +70,14 @@ async def handler(_): assert schema["type"] == "object" assert schema["properties"]["bar"]["type"] == "object" - assert ( - schema["properties"]["bar"]["properties"]["name"]["type"] == "string" - ) + assert schema["properties"]["bar"]["properties"]["name"]["type"] == "string" -@pytest.mark.parametrize( - "decorator", (openapi.deprecated(), openapi.deprecated) -) +@pytest.mark.parametrize("decorator", (openapi.deprecated(), openapi.deprecated)) def test_deprecated_decorator(app, decorator): @app.route("/") @decorator - async def handler(_): - ... + async def handler(_): ... spec = get_path(app, "/") assert spec["deprecated"] @@ -96,8 +86,7 @@ async def handler(_): def test_description_decorator(app): @app.route("/") @openapi.description("foo") - async def handler(_): - ... + async def handler(_): ... spec = get_path(app, "/") assert spec["description"] == "foo" @@ -107,18 +96,13 @@ async def handler(_): "args", ( ("http://example.com/docs",), - ( - openapi.definitions.ExternalDocumentation( - "http://example.com/docs" - ), - ), + (openapi.definitions.ExternalDocumentation("http://example.com/docs"),), ), ) def test_document_decorator(app, args): @app.route("/") @openapi.document(*args) - async def handler(_): - ... + async def handler(_): ... spec = get_path(app, "/") assert spec["externalDocs"]["url"] == "http://example.com/docs" @@ -135,8 +119,7 @@ async def handler(_): def test_exclude_decorator(app, decorator, excluded): @app.route("/") @decorator - async def handler(_): - ... + async def handler(_): ... if excluded: with pytest.raises(KeyError): @@ -148,8 +131,7 @@ async def handler(_): def test_operation_decorator(app): @app.route("/") @openapi.operation("foo") - async def handler(_): - ... + async def handler(_): ... spec = get_path(app, "/") assert spec["operationId"] == "foo" @@ -176,9 +158,7 @@ async def handler(_): ), ( openapi.parameter( - parameter=openapi.definitions.Parameter( - "foobar", deprecated=True - ) + parameter=openapi.definitions.Parameter("foobar", deprecated=True) ), { "name": "foobar", @@ -201,8 +181,7 @@ async def handler(_): def test_parameter_decorator(app, decorator, expected): @app.route("/") @decorator - def handler_one(_): - ... + def handler_one(_): ... parameters = get_path(app, "/")["parameters"] assert parameters[0] == expected @@ -246,9 +225,7 @@ def handler_one(_): }, ), ( - openapi.response( - content={"application/json": Bar}, description="..." - ), + openapi.response(content={"application/json": Bar}, description="..."), { "default": { "content": { @@ -335,9 +312,7 @@ def handler_one(_): }, "bar": { "type": "object", - "properties": { - "name": {"type": "string"} - }, + "properties": {"name": {"type": "string"}}, }, "adict": { "type": "object", @@ -345,9 +320,7 @@ def handler_one(_): }, "bdict": { "type": "object", - "additionalProperties": { - "type": "boolean" - }, + "additionalProperties": {"type": "boolean"}, }, "anything": {}, "choice": { @@ -368,8 +341,7 @@ def handler_one(_): def test_response_decorator(app, decorator, expected): @app.route("/") @decorator - def handler_one(_): - ... + def handler_one(_): ... responses = get_path(app, "/")["responses"] assert responses == expected @@ -378,8 +350,7 @@ def handler_one(_): def test_summary_decorator(app): @app.route("/") @openapi.summary("foo") - async def handler(_): - ... + async def handler(_): ... spec = get_path(app, "/") assert spec["summary"] == "foo" @@ -395,8 +366,7 @@ async def handler(_): def test_tag_decorator(app, decorator, tags): @app.route("/") @decorator - def handler_one(_): - ... + def handler_one(_): ... spec = get_spec(app) for tag in tags: @@ -409,8 +379,7 @@ def handler_one(_): def test_definition_decorator_body_dict_w_obj(app): @app.route("/") @openapi.definition(body={"application/json": Bar}) - async def handler(_): - ... + async def handler(_): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -437,8 +406,7 @@ def test_definition_decorator_body_dict_only_schema_head(app): } } ) - async def handler(_): - ... + async def handler(_): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -456,8 +424,7 @@ async def handler(_): def test_definition_decorator_body_dict_types_schema_head(app): @app.route("/") @openapi.definition(body={"application/json": {"schema": {"name": str}}}) - async def handler(_): - ... + async def handler(_): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -482,8 +449,7 @@ def test_definition_decorator_body_dict_only_schema_root(app): } } ) - async def handler(_): - ... + async def handler(_): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -501,8 +467,7 @@ async def handler(_): def test_definition_decorator_body_dict_types_schema_root(app): @app.route("/") @openapi.definition(body={"application/json": {"name": str}}) - async def handler(_): - ... + async def handler(_): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -520,8 +485,7 @@ async def handler(_): def test_definition_decorator_httpmethodview(app): class View(HTTPMethodView, uri="/", attach=app): @openapi.definition(body={"application/json": Bar}) - async def get(self, request): - ... + async def get(self, request): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -539,8 +503,7 @@ async def get(self, request): def test_definition_decorator_body_dict_multi(app): @app.route("/") @openapi.definition(body={"application/json": Bar, "text/plain": str}) - async def handler(_): - ... + async def handler(_): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -558,11 +521,8 @@ async def handler(_): def test_definition_decorator_body_request_body(app): @app.route("/") - @openapi.definition( - body=openapi.definitions.RequestBody(str, required=True) - ) - async def handler(_): - ... + @openapi.definition(body=openapi.definitions.RequestBody(str, required=True)) + async def handler(_): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -574,8 +534,7 @@ async def handler(_): def test_definition_decorator_body_model(app): @app.route("/") @openapi.definition(body=Bar) - async def handler(_): - ... + async def handler(_): ... body = get_path(app, "/")["requestBody"] assert body == { @@ -593,8 +552,7 @@ async def handler(_): def test_definition_decorator_deprecated(app): @app.route("/") @openapi.definition(deprecated=True) - async def handler(_): - ... + async def handler(_): ... assert get_path(app, "/")["deprecated"] @@ -602,8 +560,7 @@ async def handler(_): def test_definition_decorator_description(app): @app.route("/") @openapi.definition(description="foo") - async def handler(_): - ... + async def handler(_): ... assert get_path(app, "/")["description"] == "foo" @@ -611,8 +568,7 @@ async def handler(_): def test_definition_decorator_document_string(app): @app.route("/") @openapi.definition(document="foo") - async def handler(_): - ... + async def handler(_): ... assert get_path(app, "/")["externalDocs"] == {"url": "foo"} @@ -622,8 +578,7 @@ def test_definition_decorator_document_obj(app): @openapi.definition( document=openapi.definitions.ExternalDocumentation("foo", "bar") ) - async def handler(_): - ... + async def handler(_): ... assert get_path(app, "/")["externalDocs"] == { "url": "foo", @@ -634,8 +589,7 @@ async def handler(_): def test_definition_decorator_operation(app): @app.route("/") @openapi.definition(operation="foo") - async def handler(_): - ... + async def handler(_): ... assert get_path(app, "/")["operationId"] == "foo" @@ -643,8 +597,7 @@ async def handler(_): def test_definition_decorator_parameter_string(app): @app.route("/") @openapi.definition(parameter="foo") - async def handler(_): - ... + async def handler(_): ... parameters = get_path(app, "/")["parameters"] assert { @@ -657,8 +610,7 @@ async def handler(_): def test_definition_decorator_parameter_dict(app): @app.route("/") @openapi.definition(parameter={"name": "foo"}) - async def handler(_): - ... + async def handler(_): ... parameters = get_path(app, "/")["parameters"] assert { @@ -673,8 +625,7 @@ def test_definition_decorator_parameter_obj(app): @openapi.definition( parameter=openapi.definitions.Parameter("foo", description="bar") ) - async def handler(_): - ... + async def handler(_): ... parameters = get_path(app, "/")["parameters"] assert { @@ -688,8 +639,7 @@ async def handler(_): def test_definition_decorator_parameter_string_multi(app): @app.route("/") @openapi.definition(parameter=["foo", "bar"]) - async def handler(_): - ... + async def handler(_): ... parameters = get_path(app, "/")["parameters"] assert { @@ -709,8 +659,7 @@ def test_definition_decorator_parameter_dict_multi(app): @openapi.definition( parameter=[{"name": "foo"}, {"name": "bar", "location": "header"}] ) - async def handler(_): - ... + async def handler(_): ... parameters = get_path(app, "/")["parameters"] assert { @@ -733,8 +682,7 @@ def test_definition_decorator_parameter_obj_multi(app): openapi.definitions.Parameter("bar"), ] ) - async def handler(_): - ... + async def handler(_): ... parameters = get_path(app, "/")["parameters"] assert { @@ -752,8 +700,7 @@ async def handler(_): def test_definition_decorator_response_dict(app): @app.route("/") @openapi.definition(response={"application/json": Bar}) - async def handler(_): - ... + async def handler(_): ... responses = get_path(app, "/")["responses"] assert responses["default"] == { @@ -772,12 +719,9 @@ async def handler(_): def test_definition_decorator_response_obj(app): @app.route("/") @openapi.definition( - response=openapi.definitions.Response( - {"application/json": Bar}, status=201 - ) + response=openapi.definitions.Response({"application/json": Bar}, status=201) ) - async def handler(_): - ... + async def handler(_): ... responses = get_path(app, "/")["responses"] assert responses["201"] == { @@ -796,8 +740,7 @@ async def handler(_): def test_definition_decorator_response_model(app): @app.route("/") @openapi.definition(response=Bar) - async def handler(_): - ... + async def handler(_): ... responses = get_path(app, "/")["responses"] assert responses["default"] == { @@ -821,8 +764,7 @@ def test_definition_decorator_response_dict_multi(app): {"text/html": str}, ] ) - async def handler(_): - ... + async def handler(_): ... responses = get_path(app, "/")["responses"] @@ -845,16 +787,13 @@ def test_definition_decorator_response_obj_multi(app): @app.route("/") @openapi.definition( response=[ - openapi.definitions.Response( - {"application/json": Bar}, status=201 - ), + openapi.definitions.Response({"application/json": Bar}, status=201), openapi.definitions.Response( {"*/*": str}, status=400, description="Something bad" ), ] ) - async def handler(_): - ... + async def handler(_): ... responses = get_path(app, "/")["responses"] assert responses["201"] == { @@ -879,15 +818,13 @@ def test_definition_decorator_response_model_multi(app): @app.route("/") @openapi.definition(response=[Bar, LittleFoo]) - async def handler(_): - ... + async def handler(_): ... def test_definition_decorator_summary(app): @app.route("/") @openapi.definition(summary="foo") - async def handler(_): - ... + async def handler(_): ... assert get_path(app, "/")["summary"] == "foo" @@ -895,8 +832,7 @@ async def handler(_): def test_definition_decorator_tag_string(app): @app.route("/") @openapi.definition(tag="foo") - async def handler(_): - ... + async def handler(_): ... assert list(get_path(app, "/")["tags"]) == ["foo"] @@ -904,8 +840,7 @@ async def handler(_): def test_definition_decorator_tag_obj(app): @app.route("/") @openapi.definition(tag=openapi.definitions.Tag("foo")) - async def handler(_): - ... + async def handler(_): ... assert list(get_path(app, "/")["tags"]) == ["foo"] @@ -913,8 +848,7 @@ async def handler(_): def test_definition_decorator_tag_string_multi(app): @app.route("/") @openapi.definition(tag=["foo", "bar"]) - async def handler(_): - ... + async def handler(_): ... assert list(get_path(app, "/")["tags"]) == ["foo", "bar"] @@ -924,7 +858,6 @@ def test_definition_decorator_tag_obj_multi(app): @openapi.definition( tag=[openapi.definitions.Tag("foo"), openapi.definitions.Tag("bar")] ) - async def handler(_): - ... + async def handler(_): ... assert list(get_path(app, "/")["tags"]) == ["foo", "bar"] diff --git a/tests/extensions/openapi/test_external_docs.py b/tests/extensions/openapi/test_external_docs.py index 6b22467f..df63f9ea 100644 --- a/tests/extensions/openapi/test_external_docs.py +++ b/tests/extensions/openapi/test_external_docs.py @@ -15,9 +15,7 @@ async def handler0(request: Request): @app.route("/test1") @openapi.definition( - document=ExternalDocumentation( - "http://example.com/more", "Find more info here" - ) + document=ExternalDocumentation("http://example.com/more", "Find more info here") ) async def handler1(request: Request): return text("ok") diff --git a/tests/extensions/openapi/test_func_handler.py b/tests/extensions/openapi/test_func_handler.py index dd24d8b3..2dcf42ec 100644 --- a/tests/extensions/openapi/test_func_handler.py +++ b/tests/extensions/openapi/test_func_handler.py @@ -38,14 +38,8 @@ async def instance_method_handler(self, request): spec = get_spec(app) paths = spec["paths"] assert len(paths) == 3 + assert paths["/staticmethod"]["get"]["summary"] == "staticmethod custom summary" + assert paths["/classmethod"]["get"]["summary"] == "classmethod custom summary" assert ( - paths["/staticmethod"]["get"]["summary"] - == "staticmethod custom summary" - ) - assert ( - paths["/classmethod"]["get"]["summary"] == "classmethod custom summary" - ) - assert ( - paths["/instance_method"]["get"]["summary"] - == "instance method custom summary" + paths["/instance_method"]["get"]["summary"] == "instance method custom summary" ) diff --git a/tests/extensions/openapi/test_model_fields.py b/tests/extensions/openapi/test_model_fields.py index b7692d92..c04656d7 100644 --- a/tests/extensions/openapi/test_model_fields.py +++ b/tests/extensions/openapi/test_model_fields.py @@ -23,9 +23,7 @@ class FooDataclass: priority: int = field( metadata={"openapi": {"exclusiveMinimum": 1, "exclusiveMaximum": 10}} ) - ident: str = field( - default="XXXX", metadata={"openapi": {"example": "ABC123"}} - ) + ident: str = field(default="XXXX", metadata={"openapi": {"example": "ABC123"}}) @attrs.define @@ -58,15 +56,9 @@ class FooStruct(Struct): links: List[UUID] priority: Annotated[ int, - Meta( - extra={ - "openapi": {"exclusiveMinimum": 1, "exclusiveMaximum": 10} - } - ), + Meta(extra={"openapi": {"exclusiveMinimum": 1, "exclusiveMaximum": 10}}), ] - ident: Annotated[ - str, Meta(extra={"openapi": {"example": "ABC123"}}) - ] = "XXXX" + ident: Annotated[str, Meta(extra={"openapi": {"example": "ABC123"}})] = "XXXX" models = [ @@ -84,13 +76,12 @@ class FooStruct(Struct): def test_models(app, Foo): @app.get("/") @openapi.definition(body={"application/json": Foo}) - async def handler(_): - ... + async def handler(_): ... spec = get_spec(app) - foo_props = spec["paths"]["/"]["get"]["requestBody"]["content"][ - "application/json" - ]["schema"]["properties"] + foo_props = spec["paths"]["/"]["get"]["requestBody"]["content"]["application/json"][ + "schema" + ]["properties"] assert foo_props["links"] == { "title": "Links", diff --git a/tests/extensions/openapi/test_model_spec.py b/tests/extensions/openapi/test_model_spec.py index b140241e..f337b41b 100644 --- a/tests/extensions/openapi/test_model_spec.py +++ b/tests/extensions/openapi/test_model_spec.py @@ -81,11 +81,8 @@ class AlertResponseAttrs: ) def test_pydantic_base_model(app, AlertResponse, check_alert): @app.get("/") - @openapi.definition( - body={"application/json": openapi.Component(AlertResponse)} - ) - async def handler(_): - ... + @openapi.definition(body={"application/json": openapi.Component(AlertResponse)}) + async def handler(_): ... spec = get_spec(app) alert_response_name = AlertResponse.__name__ @@ -94,9 +91,7 @@ async def handler(_): assert spec["paths"]["/"]["get"]["requestBody"] == { "content": { "application/json": { - "schema": { - "$ref": f"#/components/schemas/{alert_response_name}" - } + "schema": {"$ref": f"#/components/schemas/{alert_response_name}"} } } } @@ -104,6 +99,6 @@ async def handler(_): if check_alert: assert alert_name in spec["components"]["schemas"] - assert spec["components"]["schemas"][alert_response_name][ - "properties" - ]["alert"] == {"$ref": f"#/components/schemas/{alert_name}"} + assert spec["components"]["schemas"][alert_response_name]["properties"][ + "alert" + ] == {"$ref": f"#/components/schemas/{alert_name}"} diff --git a/tests/extensions/openapi/test_paths.py b/tests/extensions/openapi/test_paths.py index b394a741..41033120 100644 --- a/tests/extensions/openapi/test_paths.py +++ b/tests/extensions/openapi/test_paths.py @@ -7,20 +7,16 @@ @pytest.fixture(autouse=True) def handlers(app: Sanic): @app.route("/test1") - async def handler1(_): - ... + async def handler1(_): ... @app.route("/test2/1") - async def handler2(_): - ... + async def handler2(_): ... @app.route("/test3/") - async def handler3(_): - ... + async def handler3(_): ... @app.route("/test4/2/") - async def handler4(_): - ... + async def handler4(_): ... def test_paths_with_all_uri_filter(app: Sanic): diff --git a/tests/extensions/openapi/test_schema.py b/tests/extensions/openapi/test_schema.py index 2f0ff921..5ce01bfe 100644 --- a/tests/extensions/openapi/test_schema.py +++ b/tests/extensions/openapi/test_schema.py @@ -16,16 +16,13 @@ class Foo: def show(self) -> bool: return True - def no_show_method(self) -> None: - ... + def no_show_method(self) -> None: ... @classmethod - def no_show_classmethod(self) -> None: - ... + def no_show_classmethod(self) -> None: ... @staticmethod - def no_show_staticmethod() -> None: - ... + def no_show_staticmethod() -> None: ... schema = Schema.make(Foo) serialized = schema.serialize() @@ -56,8 +53,7 @@ class Pet: class Single: pet = Pet - class Ignore: - ... + class Ignore: ... class Multiple: pets = [Pet] diff --git a/tests/extensions/openapi/test_security.py b/tests/extensions/openapi/test_security.py index 5f8a7bfe..47b25080 100644 --- a/tests/extensions/openapi/test_security.py +++ b/tests/extensions/openapi/test_security.py @@ -18,14 +18,12 @@ async def handler1(request): @openapi.secured("foo") @openapi.secured({"bar": []}) @openapi.secured(baz=[]) - async def handler2(request): - ... + async def handler2(request): ... @app.route("/three") @openapi.definition(secured="foo") @openapi.definition(secured={"bar": []}) - async def handler3(request): - ... + async def handler3(request): ... spec = get_path(app, "/one") assert {"foo": []} in spec["security"] diff --git a/tests/extensions/openapi/test_typing.py b/tests/extensions/openapi/test_typing.py index 7c4039ef..50e6fb7f 100644 --- a/tests/extensions/openapi/test_typing.py +++ b/tests/extensions/openapi/test_typing.py @@ -6,8 +6,7 @@ from sanic_ext.utils.typing import contains_annotations, flat_values -class Foo: - ... +class Foo: ... def test_dict_values_nested(): diff --git a/tests/extensions/templating/test_templating.py b/tests/extensions/templating/test_templating.py index 7954cb04..ebb54ce7 100644 --- a/tests/extensions/templating/test_templating.py +++ b/tests/extensions/templating/test_templating.py @@ -8,9 +8,7 @@ def test_default_templates(): app = Sanic("templating") app.extend( - config={ - "templating_path_to_templates": Path(__file__).parent / "templates" - } + config={"templating_path_to_templates": Path(__file__).parent / "templates"} ) @app.get("/1") @@ -20,16 +18,12 @@ async def handler(_): @app.get("/2") async def handler2(_): - return await render( - "foo.html", context={"seq": ["three", "four"]}, app=app - ) + return await render("foo.html", context={"seq": ["three", "four"]}, app=app) @app.get("/3") @app.ext.template("foo.html") async def handler3(_): - return await render( - context={"seq": ["five", "six"]}, status=201, app=app - ) + return await render(context={"seq": ["five", "six"]}, status=201, app=app) _, response = app.test_client.get("/1") assert response.content_type == "text/html; charset=utf-8" @@ -89,13 +83,11 @@ async def handler2(_): def test_config_templating_dir(): app = Sanic("templating") - app.config.TEMPLATING_PATH_TO_TEMPLATES = ( - Path(__file__).parent / "templates" - ) + app.config.TEMPLATING_PATH_TO_TEMPLATES = Path(__file__).parent / "templates" - assert app.ext.templating.environment.get_template( - "foo.html" - ).filename == str(Path(__file__).parent / "templates" / "foo.html") + assert app.ext.templating.environment.get_template("foo.html").filename == str( + Path(__file__).parent / "templates" / "foo.html" + ) def test_url_for(): @@ -115,9 +107,7 @@ async def handler(_): def test_default_context(): app = Sanic("templating-from-string") app.extend( - config={ - "templating_path_to_templates": Path(__file__).parent / "templates" - } + config={"templating_path_to_templates": Path(__file__).parent / "templates"} ) template = r"{{ request.args.get('test') }}" diff --git a/tests/extra/test_validation_dataclass.py b/tests/extra/test_validation_dataclass.py index bf4ce8c8..8c90a727 100644 --- a/tests/extra/test_validation_dataclass.py +++ b/tests/extra/test_validation_dataclass.py @@ -254,9 +254,7 @@ def test_modeling(model, okay, data): check_data(model, data, schema) -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="UnionType added in 3.10" -) +@pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType added in 3.10") def test_modeling_union_type_ModelUnionTypeStrNone(): schema = make_schema({}, models.ModelUnionTypeStrNone) @@ -266,9 +264,7 @@ def test_modeling_union_type_ModelUnionTypeStrNone(): check_data(models.ModelUnionTypeStrNone, {"foo": 0}, schema) -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="UnionType added in 3.10" -) +@pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType added in 3.10") def test_modeling_union_type_ModelUnionTypeStrIntNone(): schema = make_schema({}, models.ModelUnionTypeStrIntNone) @@ -281,9 +277,7 @@ def test_modeling_union_type_ModelUnionTypeStrIntNone(): check_data(models.ModelUnionTypeStrIntNone, {"foo": 1.1}, schema) -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="UnionType added in 3.10" -) +@pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType added in 3.10") def test_modeling_union_type_ModelUnionTypeStrInt(): schema = make_schema({}, models.ModelUnionTypeStrInt) diff --git a/tests/extra/test_validation_msgspec.py b/tests/extra/test_validation_msgspec.py index e29ad077..076fa4d5 100644 --- a/tests/extra/test_validation_msgspec.py +++ b/tests/extra/test_validation_msgspec.py @@ -248,9 +248,7 @@ def test_modeling(model, okay, data): check_data(model, data, schema) -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="UnionType added in 3.10" -) +@pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType added in 3.10") def test_modeling_union_type_ModelUnionTypeStrNone(): schema = make_schema({}, models.ModelUnionTypeStrNone) @@ -260,9 +258,7 @@ def test_modeling_union_type_ModelUnionTypeStrNone(): check_data(models.ModelUnionTypeStrNone, {"foo": 0}, schema) -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="UnionType added in 3.10" -) +@pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType added in 3.10") def test_modeling_union_type_ModelUnionTypeStrIntNone(): schema = make_schema({}, models.ModelUnionTypeStrIntNone) @@ -275,9 +271,7 @@ def test_modeling_union_type_ModelUnionTypeStrIntNone(): check_data(models.ModelUnionTypeStrIntNone, {"foo": 1.1}, schema) -@pytest.mark.skipif( - sys.version_info < (3, 10), reason="UnionType added in 3.10" -) +@pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType added in 3.10") def test_modeling_union_type_ModelUnionTypeStrInt(): schema = make_schema({}, models.ModelUnionTypeStrInt) diff --git a/tests/extra/test_validation_multiple.py b/tests/extra/test_validation_multiple.py index b983b8b5..ca43d21f 100644 --- a/tests/extra/test_validation_multiple.py +++ b/tests/extra/test_validation_multiple.py @@ -32,9 +32,7 @@ async def test(_, body: ModelA, query: ModelB): _, response = app.test_client.post("", params={"b": "bbb"}) assert response.status == 400 - _, response = app.test_client.post( - "", params={"b": "bbb"}, json={"a": "aaa"} - ) + _, response = app.test_client.post("", params={"b": "bbb"}, json={"a": "aaa"}) assert response.status == 200 assert response.json == { "body": {"a": "aaa"},