1from __future__ import annotations 
    2 
    3import os 
    4import typing as t 
    5from collections import defaultdict 
    6from functools import update_wrapper 
    7 
    8from .. import typing as ft 
    9from .scaffold import _endpoint_from_view_func 
    10from .scaffold import _sentinel 
    11from .scaffold import Scaffold 
    12from .scaffold import setupmethod 
    13 
    14if t.TYPE_CHECKING:  # pragma: no cover 
    15    from .app import App 
    16 
    17DeferredSetupFunction = t.Callable[["BlueprintSetupState"], None] 
    18T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any]) 
    19T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) 
    20T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) 
    21T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) 
    22T_template_context_processor = t.TypeVar( 
    23    "T_template_context_processor", bound=ft.TemplateContextProcessorCallable 
    24) 
    25T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) 
    26T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) 
    27T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) 
    28T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) 
    29T_url_value_preprocessor = t.TypeVar( 
    30    "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable 
    31) 
    32 
    33 
    34class BlueprintSetupState: 
    35    """Temporary holder object for registering a blueprint with the 
    36    application.  An instance of this class is created by the 
    37    :meth:`~flask.Blueprint.make_setup_state` method and later passed 
    38    to all register callback functions. 
    39    """ 
    40 
    41    def __init__( 
    42        self, 
    43        blueprint: Blueprint, 
    44        app: App, 
    45        options: t.Any, 
    46        first_registration: bool, 
    47    ) -> None: 
    48        #: a reference to the current application 
    49        self.app = app 
    50 
    51        #: a reference to the blueprint that created this setup state. 
    52        self.blueprint = blueprint 
    53 
    54        #: a dictionary with all options that were passed to the 
    55        #: :meth:`~flask.Flask.register_blueprint` method. 
    56        self.options = options 
    57 
    58        #: as blueprints can be registered multiple times with the 
    59        #: application and not everything wants to be registered 
    60        #: multiple times on it, this attribute can be used to figure 
    61        #: out if the blueprint was registered in the past already. 
    62        self.first_registration = first_registration 
    63 
    64        subdomain = self.options.get("subdomain") 
    65        if subdomain is None: 
    66            subdomain = self.blueprint.subdomain 
    67 
    68        #: The subdomain that the blueprint should be active for, ``None`` 
    69        #: otherwise. 
    70        self.subdomain = subdomain 
    71 
    72        url_prefix = self.options.get("url_prefix") 
    73        if url_prefix is None: 
    74            url_prefix = self.blueprint.url_prefix 
    75        #: The prefix that should be used for all URLs defined on the 
    76        #: blueprint. 
    77        self.url_prefix = url_prefix 
    78 
    79        self.name = self.options.get("name", blueprint.name) 
    80        self.name_prefix = self.options.get("name_prefix", "") 
    81 
    82        #: A dictionary with URL defaults that is added to each and every 
    83        #: URL that was defined with the blueprint. 
    84        self.url_defaults = dict(self.blueprint.url_values_defaults) 
    85        self.url_defaults.update(self.options.get("url_defaults", ())) 
    86 
    87    def add_url_rule( 
    88        self, 
    89        rule: str, 
    90        endpoint: str | None = None, 
    91        view_func: ft.RouteCallable | None = None, 
    92        **options: t.Any, 
    93    ) -> None: 
    94        """A helper method to register a rule (and optionally a view function) 
    95        to the application.  The endpoint is automatically prefixed with the 
    96        blueprint's name. 
    97        """ 
    98        if self.url_prefix is not None: 
    99            if rule: 
    100                rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/"))) 
    101            else: 
    102                rule = self.url_prefix 
    103        options.setdefault("subdomain", self.subdomain) 
    104        if endpoint is None: 
    105            endpoint = _endpoint_from_view_func(view_func)  # type: ignore 
    106        defaults = self.url_defaults 
    107        if "defaults" in options: 
    108            defaults = dict(defaults, **options.pop("defaults")) 
    109 
    110        self.app.add_url_rule( 
    111            rule, 
    112            f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), 
    113            view_func, 
    114            defaults=defaults, 
    115            **options, 
    116        ) 
    117 
    118 
    119class Blueprint(Scaffold): 
    120    """Represents a blueprint, a collection of routes and other 
    121    app-related functions that can be registered on a real application 
    122    later. 
    123 
    124    A blueprint is an object that allows defining application functions 
    125    without requiring an application object ahead of time. It uses the 
    126    same decorators as :class:`~flask.Flask`, but defers the need for an 
    127    application by recording them for later registration. 
    128 
    129    Decorating a function with a blueprint creates a deferred function 
    130    that is called with :class:`~flask.blueprints.BlueprintSetupState` 
    131    when the blueprint is registered on an application. 
    132 
    133    See :doc:`/blueprints` for more information. 
    134 
    135    :param name: The name of the blueprint. Will be prepended to each 
    136        endpoint name. 
    137    :param import_name: The name of the blueprint package, usually 
    138        ``__name__``. This helps locate the ``root_path`` for the 
    139        blueprint. 
    140    :param static_folder: A folder with static files that should be 
    141        served by the blueprint's static route. The path is relative to 
    142        the blueprint's root path. Blueprint static files are disabled 
    143        by default. 
    144    :param static_url_path: The url to serve static files from. 
    145        Defaults to ``static_folder``. If the blueprint does not have 
    146        a ``url_prefix``, the app's static route will take precedence, 
    147        and the blueprint's static files won't be accessible. 
    148    :param template_folder: A folder with templates that should be added 
    149        to the app's template search path. The path is relative to the 
    150        blueprint's root path. Blueprint templates are disabled by 
    151        default. Blueprint templates have a lower precedence than those 
    152        in the app's templates folder. 
    153    :param url_prefix: A path to prepend to all of the blueprint's URLs, 
    154        to make them distinct from the rest of the app's routes. 
    155    :param subdomain: A subdomain that blueprint routes will match on by 
    156        default. 
    157    :param url_defaults: A dict of default values that blueprint routes 
    158        will receive by default. 
    159    :param root_path: By default, the blueprint will automatically set 
    160        this based on ``import_name``. In certain situations this 
    161        automatic detection can fail, so the path can be specified 
    162        manually instead. 
    163 
    164    .. versionchanged:: 1.1.0 
    165        Blueprints have a ``cli`` group to register nested CLI commands. 
    166        The ``cli_group`` parameter controls the name of the group under 
    167        the ``flask`` command. 
    168 
    169    .. versionadded:: 0.7 
    170    """ 
    171 
    172    _got_registered_once = False 
    173 
    174    def __init__( 
    175        self, 
    176        name: str, 
    177        import_name: str, 
    178        static_folder: str | os.PathLike[str] | None = None, 
    179        static_url_path: str | None = None, 
    180        template_folder: str | os.PathLike[str] | None = None, 
    181        url_prefix: str | None = None, 
    182        subdomain: str | None = None, 
    183        url_defaults: dict[str, t.Any] | None = None, 
    184        root_path: str | None = None, 
    185        cli_group: str | None = _sentinel,  # type: ignore[assignment] 
    186    ): 
    187        super().__init__( 
    188            import_name=import_name, 
    189            static_folder=static_folder, 
    190            static_url_path=static_url_path, 
    191            template_folder=template_folder, 
    192            root_path=root_path, 
    193        ) 
    194 
    195        if not name: 
    196            raise ValueError("'name' may not be empty.") 
    197 
    198        if "." in name: 
    199            raise ValueError("'name' may not contain a dot '.' character.") 
    200 
    201        self.name = name 
    202        self.url_prefix = url_prefix 
    203        self.subdomain = subdomain 
    204        self.deferred_functions: list[DeferredSetupFunction] = [] 
    205 
    206        if url_defaults is None: 
    207            url_defaults = {} 
    208 
    209        self.url_values_defaults = url_defaults 
    210        self.cli_group = cli_group 
    211        self._blueprints: list[tuple[Blueprint, dict[str, t.Any]]] = [] 
    212 
    213    def _check_setup_finished(self, f_name: str) -> None: 
    214        if self._got_registered_once: 
    215            raise AssertionError( 
    216                f"The setup method '{f_name}' can no longer be called on the blueprint" 
    217                f" '{self.name}'. It has already been registered at least once, any" 
    218                " changes will not be applied consistently.\n" 
    219                "Make sure all imports, decorators, functions, etc. needed to set up" 
    220                " the blueprint are done before registering it." 
    221            ) 
    222 
    223    @setupmethod 
    224    def record(self, func: DeferredSetupFunction) -> None: 
    225        """Registers a function that is called when the blueprint is 
    226        registered on the application.  This function is called with the 
    227        state as argument as returned by the :meth:`make_setup_state` 
    228        method. 
    229        """ 
    230        self.deferred_functions.append(func) 
    231 
    232    @setupmethod 
    233    def record_once(self, func: DeferredSetupFunction) -> None: 
    234        """Works like :meth:`record` but wraps the function in another 
    235        function that will ensure the function is only called once.  If the 
    236        blueprint is registered a second time on the application, the 
    237        function passed is not called. 
    238        """ 
    239 
    240        def wrapper(state: BlueprintSetupState) -> None: 
    241            if state.first_registration: 
    242                func(state) 
    243 
    244        self.record(update_wrapper(wrapper, func)) 
    245 
    246    def make_setup_state( 
    247        self, app: App, options: dict[str, t.Any], first_registration: bool = False 
    248    ) -> BlueprintSetupState: 
    249        """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` 
    250        object that is later passed to the register callback functions. 
    251        Subclasses can override this to return a subclass of the setup state. 
    252        """ 
    253        return BlueprintSetupState(self, app, options, first_registration) 
    254 
    255    @setupmethod 
    256    def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: 
    257        """Register a :class:`~flask.Blueprint` on this blueprint. Keyword 
    258        arguments passed to this method will override the defaults set 
    259        on the blueprint. 
    260 
    261        .. versionchanged:: 2.0.1 
    262            The ``name`` option can be used to change the (pre-dotted) 
    263            name the blueprint is registered with. This allows the same 
    264            blueprint to be registered multiple times with unique names 
    265            for ``url_for``. 
    266 
    267        .. versionadded:: 2.0 
    268        """ 
    269        if blueprint is self: 
    270            raise ValueError("Cannot register a blueprint on itself") 
    271        self._blueprints.append((blueprint, options)) 
    272 
    273    def register(self, app: App, options: dict[str, t.Any]) -> None: 
    274        """Called by :meth:`Flask.register_blueprint` to register all 
    275        views and callbacks registered on the blueprint with the 
    276        application. Creates a :class:`.BlueprintSetupState` and calls 
    277        each :meth:`record` callback with it. 
    278 
    279        :param app: The application this blueprint is being registered 
    280            with. 
    281        :param options: Keyword arguments forwarded from 
    282            :meth:`~Flask.register_blueprint`. 
    283 
    284        .. versionchanged:: 2.3 
    285            Nested blueprints now correctly apply subdomains. 
    286 
    287        .. versionchanged:: 2.1 
    288            Registering the same blueprint with the same name multiple 
    289            times is an error. 
    290 
    291        .. versionchanged:: 2.0.1 
    292            Nested blueprints are registered with their dotted name. 
    293            This allows different blueprints with the same name to be 
    294            nested at different locations. 
    295 
    296        .. versionchanged:: 2.0.1 
    297            The ``name`` option can be used to change the (pre-dotted) 
    298            name the blueprint is registered with. This allows the same 
    299            blueprint to be registered multiple times with unique names 
    300            for ``url_for``. 
    301        """ 
    302        name_prefix = options.get("name_prefix", "") 
    303        self_name = options.get("name", self.name) 
    304        name = f"{name_prefix}.{self_name}".lstrip(".") 
    305 
    306        if name in app.blueprints: 
    307            bp_desc = "this" if app.blueprints[name] is self else "a different" 
    308            existing_at = f" '{name}'" if self_name != name else "" 
    309 
    310            raise ValueError( 
    311                f"The name '{self_name}' is already registered for" 
    312                f" {bp_desc} blueprint{existing_at}. Use 'name=' to" 
    313                f" provide a unique name." 
    314            ) 
    315 
    316        first_bp_registration = not any(bp is self for bp in app.blueprints.values()) 
    317        first_name_registration = name not in app.blueprints 
    318 
    319        app.blueprints[name] = self 
    320        self._got_registered_once = True 
    321        state = self.make_setup_state(app, options, first_bp_registration) 
    322 
    323        if self.has_static_folder: 
    324            state.add_url_rule( 
    325                f"{self.static_url_path}/<path:filename>", 
    326                view_func=self.send_static_file,  # type: ignore[attr-defined] 
    327                endpoint="static", 
    328            ) 
    329 
    330        # Merge blueprint data into parent. 
    331        if first_bp_registration or first_name_registration: 
    332            self._merge_blueprint_funcs(app, name) 
    333 
    334        for deferred in self.deferred_functions: 
    335            deferred(state) 
    336 
    337        cli_resolved_group = options.get("cli_group", self.cli_group) 
    338 
    339        if self.cli.commands: 
    340            if cli_resolved_group is None: 
    341                app.cli.commands.update(self.cli.commands) 
    342            elif cli_resolved_group is _sentinel: 
    343                self.cli.name = name 
    344                app.cli.add_command(self.cli) 
    345            else: 
    346                self.cli.name = cli_resolved_group 
    347                app.cli.add_command(self.cli) 
    348 
    349        for blueprint, bp_options in self._blueprints: 
    350            bp_options = bp_options.copy() 
    351            bp_url_prefix = bp_options.get("url_prefix") 
    352            bp_subdomain = bp_options.get("subdomain") 
    353 
    354            if bp_subdomain is None: 
    355                bp_subdomain = blueprint.subdomain 
    356 
    357            if state.subdomain is not None and bp_subdomain is not None: 
    358                bp_options["subdomain"] = bp_subdomain + "." + state.subdomain 
    359            elif bp_subdomain is not None: 
    360                bp_options["subdomain"] = bp_subdomain 
    361            elif state.subdomain is not None: 
    362                bp_options["subdomain"] = state.subdomain 
    363 
    364            if bp_url_prefix is None: 
    365                bp_url_prefix = blueprint.url_prefix 
    366 
    367            if state.url_prefix is not None and bp_url_prefix is not None: 
    368                bp_options["url_prefix"] = ( 
    369                    state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") 
    370                ) 
    371            elif bp_url_prefix is not None: 
    372                bp_options["url_prefix"] = bp_url_prefix 
    373            elif state.url_prefix is not None: 
    374                bp_options["url_prefix"] = state.url_prefix 
    375 
    376            bp_options["name_prefix"] = name 
    377            blueprint.register(app, bp_options) 
    378 
    379    def _merge_blueprint_funcs(self, app: App, name: str) -> None: 
    380        def extend( 
    381            bp_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], 
    382            parent_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], 
    383        ) -> None: 
    384            for key, values in bp_dict.items(): 
    385                key = name if key is None else f"{name}.{key}" 
    386                parent_dict[key].extend(values) 
    387 
    388        for key, value in self.error_handler_spec.items(): 
    389            key = name if key is None else f"{name}.{key}" 
    390            value = defaultdict( 
    391                dict, 
    392                { 
    393                    code: {exc_class: func for exc_class, func in code_values.items()} 
    394                    for code, code_values in value.items() 
    395                }, 
    396            ) 
    397            app.error_handler_spec[key] = value 
    398 
    399        for endpoint, func in self.view_functions.items(): 
    400            app.view_functions[endpoint] = func 
    401 
    402        extend(self.before_request_funcs, app.before_request_funcs) 
    403        extend(self.after_request_funcs, app.after_request_funcs) 
    404        extend( 
    405            self.teardown_request_funcs, 
    406            app.teardown_request_funcs, 
    407        ) 
    408        extend(self.url_default_functions, app.url_default_functions) 
    409        extend(self.url_value_preprocessors, app.url_value_preprocessors) 
    410        extend(self.template_context_processors, app.template_context_processors) 
    411 
    412    @setupmethod 
    413    def add_url_rule( 
    414        self, 
    415        rule: str, 
    416        endpoint: str | None = None, 
    417        view_func: ft.RouteCallable | None = None, 
    418        provide_automatic_options: bool | None = None, 
    419        **options: t.Any, 
    420    ) -> None: 
    421        """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for 
    422        full documentation. 
    423 
    424        The URL rule is prefixed with the blueprint's URL prefix. The endpoint name, 
    425        used with :func:`url_for`, is prefixed with the blueprint's name. 
    426        """ 
    427        if endpoint and "." in endpoint: 
    428            raise ValueError("'endpoint' may not contain a dot '.' character.") 
    429 
    430        if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: 
    431            raise ValueError("'view_func' name may not contain a dot '.' character.") 
    432 
    433        self.record( 
    434            lambda s: s.add_url_rule( 
    435                rule, 
    436                endpoint, 
    437                view_func, 
    438                provide_automatic_options=provide_automatic_options, 
    439                **options, 
    440            ) 
    441        ) 
    442 
    443    @setupmethod 
    444    def app_template_filter( 
    445        self, name: str | None = None 
    446    ) -> t.Callable[[T_template_filter], T_template_filter]: 
    447        """Register a template filter, available in any template rendered by the 
    448        application. Equivalent to :meth:`.Flask.template_filter`. 
    449 
    450        :param name: the optional name of the filter, otherwise the 
    451                     function name will be used. 
    452        """ 
    453 
    454        def decorator(f: T_template_filter) -> T_template_filter: 
    455            self.add_app_template_filter(f, name=name) 
    456            return f 
    457 
    458        return decorator 
    459 
    460    @setupmethod 
    461    def add_app_template_filter( 
    462        self, f: ft.TemplateFilterCallable, name: str | None = None 
    463    ) -> None: 
    464        """Register a template filter, available in any template rendered by the 
    465        application. Works like the :meth:`app_template_filter` decorator. Equivalent to 
    466        :meth:`.Flask.add_template_filter`. 
    467 
    468        :param name: the optional name of the filter, otherwise the 
    469                     function name will be used. 
    470        """ 
    471 
    472        def register_template(state: BlueprintSetupState) -> None: 
    473            state.app.jinja_env.filters[name or f.__name__] = f 
    474 
    475        self.record_once(register_template) 
    476 
    477    @setupmethod 
    478    def app_template_test( 
    479        self, name: str | None = None 
    480    ) -> t.Callable[[T_template_test], T_template_test]: 
    481        """Register a template test, available in any template rendered by the 
    482        application. Equivalent to :meth:`.Flask.template_test`. 
    483 
    484        .. versionadded:: 0.10 
    485 
    486        :param name: the optional name of the test, otherwise the 
    487                     function name will be used. 
    488        """ 
    489 
    490        def decorator(f: T_template_test) -> T_template_test: 
    491            self.add_app_template_test(f, name=name) 
    492            return f 
    493 
    494        return decorator 
    495 
    496    @setupmethod 
    497    def add_app_template_test( 
    498        self, f: ft.TemplateTestCallable, name: str | None = None 
    499    ) -> None: 
    500        """Register a template test, available in any template rendered by the 
    501        application. Works like the :meth:`app_template_test` decorator. Equivalent to 
    502        :meth:`.Flask.add_template_test`. 
    503 
    504        .. versionadded:: 0.10 
    505 
    506        :param name: the optional name of the test, otherwise the 
    507                     function name will be used. 
    508        """ 
    509 
    510        def register_template(state: BlueprintSetupState) -> None: 
    511            state.app.jinja_env.tests[name or f.__name__] = f 
    512 
    513        self.record_once(register_template) 
    514 
    515    @setupmethod 
    516    def app_template_global( 
    517        self, name: str | None = None 
    518    ) -> t.Callable[[T_template_global], T_template_global]: 
    519        """Register a template global, available in any template rendered by the 
    520        application. Equivalent to :meth:`.Flask.template_global`. 
    521 
    522        .. versionadded:: 0.10 
    523 
    524        :param name: the optional name of the global, otherwise the 
    525                     function name will be used. 
    526        """ 
    527 
    528        def decorator(f: T_template_global) -> T_template_global: 
    529            self.add_app_template_global(f, name=name) 
    530            return f 
    531 
    532        return decorator 
    533 
    534    @setupmethod 
    535    def add_app_template_global( 
    536        self, f: ft.TemplateGlobalCallable, name: str | None = None 
    537    ) -> None: 
    538        """Register a template global, available in any template rendered by the 
    539        application. Works like the :meth:`app_template_global` decorator. Equivalent to 
    540        :meth:`.Flask.add_template_global`. 
    541 
    542        .. versionadded:: 0.10 
    543 
    544        :param name: the optional name of the global, otherwise the 
    545                     function name will be used. 
    546        """ 
    547 
    548        def register_template(state: BlueprintSetupState) -> None: 
    549            state.app.jinja_env.globals[name or f.__name__] = f 
    550 
    551        self.record_once(register_template) 
    552 
    553    @setupmethod 
    554    def before_app_request(self, f: T_before_request) -> T_before_request: 
    555        """Like :meth:`before_request`, but before every request, not only those handled 
    556        by the blueprint. Equivalent to :meth:`.Flask.before_request`. 
    557        """ 
    558        self.record_once( 
    559            lambda s: s.app.before_request_funcs.setdefault(None, []).append(f) 
    560        ) 
    561        return f 
    562 
    563    @setupmethod 
    564    def after_app_request(self, f: T_after_request) -> T_after_request: 
    565        """Like :meth:`after_request`, but after every request, not only those handled 
    566        by the blueprint. Equivalent to :meth:`.Flask.after_request`. 
    567        """ 
    568        self.record_once( 
    569            lambda s: s.app.after_request_funcs.setdefault(None, []).append(f) 
    570        ) 
    571        return f 
    572 
    573    @setupmethod 
    574    def teardown_app_request(self, f: T_teardown) -> T_teardown: 
    575        """Like :meth:`teardown_request`, but after every request, not only those 
    576        handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`. 
    577        """ 
    578        self.record_once( 
    579            lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f) 
    580        ) 
    581        return f 
    582 
    583    @setupmethod 
    584    def app_context_processor( 
    585        self, f: T_template_context_processor 
    586    ) -> T_template_context_processor: 
    587        """Like :meth:`context_processor`, but for templates rendered by every view, not 
    588        only by the blueprint. Equivalent to :meth:`.Flask.context_processor`. 
    589        """ 
    590        self.record_once( 
    591            lambda s: s.app.template_context_processors.setdefault(None, []).append(f) 
    592        ) 
    593        return f 
    594 
    595    @setupmethod 
    596    def app_errorhandler( 
    597        self, code: type[Exception] | int 
    598    ) -> t.Callable[[T_error_handler], T_error_handler]: 
    599        """Like :meth:`errorhandler`, but for every request, not only those handled by 
    600        the blueprint. Equivalent to :meth:`.Flask.errorhandler`. 
    601        """ 
    602 
    603        def decorator(f: T_error_handler) -> T_error_handler: 
    604            def from_blueprint(state: BlueprintSetupState) -> None: 
    605                state.app.errorhandler(code)(f) 
    606 
    607            self.record_once(from_blueprint) 
    608            return f 
    609 
    610        return decorator 
    611 
    612    @setupmethod 
    613    def app_url_value_preprocessor( 
    614        self, f: T_url_value_preprocessor 
    615    ) -> T_url_value_preprocessor: 
    616        """Like :meth:`url_value_preprocessor`, but for every request, not only those 
    617        handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`. 
    618        """ 
    619        self.record_once( 
    620            lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) 
    621        ) 
    622        return f 
    623 
    624    @setupmethod 
    625    def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults: 
    626        """Like :meth:`url_defaults`, but for every request, not only those handled by 
    627        the blueprint. Equivalent to :meth:`.Flask.url_defaults`. 
    628        """ 
    629        self.record_once( 
    630            lambda s: s.app.url_default_functions.setdefault(None, []).append(f) 
    631        ) 
    632        return f