diff --git a/sanic/app.py b/sanic/app.py index cb765b6a..1bbd5e29 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -135,13 +135,8 @@ class Sanic(BaseSanic): register: Optional[bool] = None, dumps: Optional[Callable[..., str]] = None, ) -> None: - super().__init__() + super().__init__(name=name) - if name is None: - raise SanicException( - "Sanic instance cannot be unnamed. " - "Please use Sanic(name='your_application_name') instead.", - ) # logging if configure_logging: logging.config.dictConfig(log_config or LOGGING_CONFIG_DEFAULTS) @@ -161,7 +156,6 @@ class Sanic(BaseSanic): self.is_running = False self.is_stopping = False self.listeners: Dict[str, List[ListenerType]] = defaultdict(list) - self.name = name self.named_request_middleware: Dict[str, Deque[MiddlewareType]] = {} self.named_response_middleware: Dict[str, Deque[MiddlewareType]] = {} self.request_class = request_class diff --git a/sanic/base.py b/sanic/base.py index 368288c4..fd33838f 100644 --- a/sanic/base.py +++ b/sanic/base.py @@ -1,6 +1,9 @@ +import re + from typing import Any, Tuple from warnings import warn +from sanic.exceptions import SanicException from sanic.mixins.exceptions import ExceptionMixin from sanic.mixins.listeners import ListenerMixin from sanic.mixins.middleware import MiddlewareMixin @@ -8,6 +11,9 @@ from sanic.mixins.routes import RouteMixin from sanic.mixins.signals import SignalMixin +VALID_NAME = re.compile(r"^[a-zA-Z][a-zA-Z0-9_\-]*$") + + class BaseSanic( RouteMixin, MiddlewareMixin, @@ -17,7 +23,25 @@ class BaseSanic( ): __fake_slots__: Tuple[str, ...] - def __init__(self, *args, **kwargs) -> None: + def __init__(self, name: str = None, *args, **kwargs) -> None: + class_name = self.__class__.__name__ + + if name is None: + raise SanicException( + f"{class_name} instance cannot be unnamed. " + "Please use Sanic(name='your_application_name') instead.", + ) + + if not VALID_NAME.match(name): + warn( + f"{class_name} instance named '{name}' uses a format that is" + f"deprecated. Starting in version 21.12, {class_name} objects " + "be named only using alphanumeric characters, _, or -.", + DeprecationWarning, + ) + + self.name = name + for base in BaseSanic.__bases__: base.__init__(self, *args, **kwargs) # type: ignore @@ -36,6 +60,7 @@ class BaseSanic( f"Setting variables on {self.__class__.__name__} instances is " "deprecated and will be removed in version 21.9. You should " f"change your {self.__class__.__name__} instance to use " - f"instance.ctx.{name} instead." + f"instance.ctx.{name} instead.", + DeprecationWarning, ) super().__setattr__(name, value) diff --git a/sanic/blueprints.py b/sanic/blueprints.py index c17a0637..eba91c39 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -62,19 +62,20 @@ class Blueprint(BaseSanic): "strict_slashes", "url_prefix", "version", + "version_prefix", "websocket_routes", ) def __init__( self, - name: str, + name: str = None, url_prefix: Optional[str] = None, host: Optional[str] = None, version: Optional[Union[int, str, float]] = None, strict_slashes: Optional[bool] = None, version_prefix: str = "/v", ): - super().__init__() + super().__init__(name=name) self._apps: Set[Sanic] = set() self.ctx = SimpleNamespace() @@ -82,7 +83,6 @@ class Blueprint(BaseSanic): self.host = host self.listeners: Dict[str, List[ListenerType]] = {} self.middlewares: List[MiddlewareType] = [] - self.name = name self.routes: List[Route] = [] self.statics: List[RouteHandler] = [] self.strict_slashes = strict_slashes diff --git a/sanic/mixins/routes.py b/sanic/mixins/routes.py index 30f5cd3f..e468be69 100644 --- a/sanic/mixins/routes.py +++ b/sanic/mixins/routes.py @@ -26,10 +26,11 @@ from sanic.views import CompositionView class RouteMixin: + name: str + def __init__(self, *args, **kwargs) -> None: self._future_routes: Set[FutureRoute] = set() self._future_statics: Set[FutureStatic] = set() - self.name = "" self.strict_slashes: Optional[bool] = False def _apply_route(self, route: FutureRoute) -> List[Route]: diff --git a/tests/test_app.py b/tests/test_app.py index 9acdb0cd..187267da 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -389,7 +389,7 @@ def test_app_no_registry_env(): def test_app_set_attribute_warning(app): - with pytest.warns(UserWarning) as record: + with pytest.warns(DeprecationWarning) as record: app.foo = 1 assert len(record) == 1 diff --git a/tests/test_base.py b/tests/test_base.py index b7e1158a..72567621 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -41,3 +41,48 @@ def test_bp_repr_with_values(bp): 'Blueprint(name="my_bp", url_prefix="/foo", host="example.com", ' "version=3, strict_slashes=True)" ) + + +@pytest.mark.parametrize( + "name", + ( + "something", + "some-thing", + "some_thing", + "Something", + "SomeThing", + "Some-Thing", + "Some_Thing", + "SomeThing123", + "something123", + "some-thing123", + "some_thing123", + "some-Thing123", + "some_Thing123", + ), +) +def test_names_okay(name): + app = Sanic(name) + bp = Blueprint(name) + + assert app.name == name + assert bp.name == name + + +@pytest.mark.parametrize( + "name", + ( + "123something", + "some thing", + "something!", + ), +) +def test_names_not_okay(name): + with pytest.warns(DeprecationWarning): + app = Sanic(name) + + with pytest.warns(DeprecationWarning): + bp = Blueprint(name) + + assert app.name == name + assert bp.name == name diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index a49f95f2..fec7b50a 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -1028,7 +1028,7 @@ def test_blueprint_registered_multiple_apps(): def test_bp_set_attribute_warning(): bp = Blueprint("bp") - with pytest.warns(UserWarning) as record: + with pytest.warns(DeprecationWarning) as record: bp.foo = 1 assert len(record) == 1