Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/scaffold.py: 39%
254 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1import importlib.util
2import json
3import os
4import pathlib
5import pkgutil
6import sys
7import typing as t
8from collections import defaultdict
9from datetime import timedelta
10from functools import update_wrapper
12from jinja2 import FileSystemLoader
13from werkzeug.exceptions import default_exceptions
14from werkzeug.exceptions import HTTPException
16from . import typing as ft
17from .cli import AppGroup
18from .globals import current_app
19from .helpers import get_root_path
20from .helpers import locked_cached_property
21from .helpers import send_from_directory
22from .templating import _default_template_ctx_processor
24if t.TYPE_CHECKING: # pragma: no cover
25 from .wrappers import Response
27# a singleton sentinel value for parameter defaults
28_sentinel = object()
30F = t.TypeVar("F", bound=t.Callable[..., t.Any])
31T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
32T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
33T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
34T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
35T_template_context_processor = t.TypeVar(
36 "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
37)
38T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
39T_url_value_preprocessor = t.TypeVar(
40 "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
41)
42T_route = t.TypeVar("T_route", bound=ft.RouteCallable)
45def setupmethod(f: F) -> F:
46 f_name = f.__name__
48 def wrapper_func(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
49 self._check_setup_finished(f_name)
50 return f(self, *args, **kwargs)
52 return t.cast(F, update_wrapper(wrapper_func, f))
55class Scaffold:
56 """Common behavior shared between :class:`~flask.Flask` and
57 :class:`~flask.blueprints.Blueprint`.
59 :param import_name: The import name of the module where this object
60 is defined. Usually :attr:`__name__` should be used.
61 :param static_folder: Path to a folder of static files to serve.
62 If this is set, a static route will be added.
63 :param static_url_path: URL prefix for the static route.
64 :param template_folder: Path to a folder containing template files.
65 for rendering. If this is set, a Jinja loader will be added.
66 :param root_path: The path that static, template, and resource files
67 are relative to. Typically not set, it is discovered based on
68 the ``import_name``.
70 .. versionadded:: 2.0
71 """
73 name: str
74 _static_folder: t.Optional[str] = None
75 _static_url_path: t.Optional[str] = None
77 #: JSON encoder class used by :func:`flask.json.dumps`. If a
78 #: blueprint sets this, it will be used instead of the app's value.
79 #:
80 #: .. deprecated:: 2.2
81 #: Will be removed in Flask 2.3.
82 json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None
84 #: JSON decoder class used by :func:`flask.json.loads`. If a
85 #: blueprint sets this, it will be used instead of the app's value.
86 #:
87 #: .. deprecated:: 2.2
88 #: Will be removed in Flask 2.3.
89 json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
91 def __init__(
92 self,
93 import_name: str,
94 static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
95 static_url_path: t.Optional[str] = None,
96 template_folder: t.Optional[t.Union[str, os.PathLike]] = None,
97 root_path: t.Optional[str] = None,
98 ):
99 #: The name of the package or module that this object belongs
100 #: to. Do not change this once it is set by the constructor.
101 self.import_name = import_name
103 self.static_folder = static_folder # type: ignore
104 self.static_url_path = static_url_path
106 #: The path to the templates folder, relative to
107 #: :attr:`root_path`, to add to the template loader. ``None`` if
108 #: templates should not be added.
109 self.template_folder = template_folder
111 if root_path is None:
112 root_path = get_root_path(self.import_name)
114 #: Absolute path to the package on the filesystem. Used to look
115 #: up resources contained in the package.
116 self.root_path = root_path
118 #: The Click command group for registering CLI commands for this
119 #: object. The commands are available from the ``flask`` command
120 #: once the application has been discovered and blueprints have
121 #: been registered.
122 self.cli = AppGroup()
124 #: A dictionary mapping endpoint names to view functions.
125 #:
126 #: To register a view function, use the :meth:`route` decorator.
127 #:
128 #: This data structure is internal. It should not be modified
129 #: directly and its format may change at any time.
130 self.view_functions: t.Dict[str, t.Callable] = {}
132 #: A data structure of registered error handlers, in the format
133 #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is
134 #: the name of a blueprint the handlers are active for, or
135 #: ``None`` for all requests. The ``code`` key is the HTTP
136 #: status code for ``HTTPException``, or ``None`` for
137 #: other exceptions. The innermost dictionary maps exception
138 #: classes to handler functions.
139 #:
140 #: To register an error handler, use the :meth:`errorhandler`
141 #: decorator.
142 #:
143 #: This data structure is internal. It should not be modified
144 #: directly and its format may change at any time.
145 self.error_handler_spec: t.Dict[
146 ft.AppOrBlueprintKey,
147 t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ft.ErrorHandlerCallable]],
148 ] = defaultdict(lambda: defaultdict(dict))
150 #: A data structure of functions to call at the beginning of
151 #: each request, in the format ``{scope: [functions]}``. The
152 #: ``scope`` key is the name of a blueprint the functions are
153 #: active for, or ``None`` for all requests.
154 #:
155 #: To register a function, use the :meth:`before_request`
156 #: decorator.
157 #:
158 #: This data structure is internal. It should not be modified
159 #: directly and its format may change at any time.
160 self.before_request_funcs: t.Dict[
161 ft.AppOrBlueprintKey, t.List[ft.BeforeRequestCallable]
162 ] = defaultdict(list)
164 #: A data structure of functions to call at the end of each
165 #: request, in the format ``{scope: [functions]}``. The
166 #: ``scope`` key is the name of a blueprint the functions are
167 #: active for, or ``None`` for all requests.
168 #:
169 #: To register a function, use the :meth:`after_request`
170 #: decorator.
171 #:
172 #: This data structure is internal. It should not be modified
173 #: directly and its format may change at any time.
174 self.after_request_funcs: t.Dict[
175 ft.AppOrBlueprintKey, t.List[ft.AfterRequestCallable]
176 ] = defaultdict(list)
178 #: A data structure of functions to call at the end of each
179 #: request even if an exception is raised, in the format
180 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
181 #: blueprint the functions are active for, or ``None`` for all
182 #: requests.
183 #:
184 #: To register a function, use the :meth:`teardown_request`
185 #: decorator.
186 #:
187 #: This data structure is internal. It should not be modified
188 #: directly and its format may change at any time.
189 self.teardown_request_funcs: t.Dict[
190 ft.AppOrBlueprintKey, t.List[ft.TeardownCallable]
191 ] = defaultdict(list)
193 #: A data structure of functions to call to pass extra context
194 #: values when rendering templates, in the format
195 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
196 #: blueprint the functions are active for, or ``None`` for all
197 #: requests.
198 #:
199 #: To register a function, use the :meth:`context_processor`
200 #: decorator.
201 #:
202 #: This data structure is internal. It should not be modified
203 #: directly and its format may change at any time.
204 self.template_context_processors: t.Dict[
205 ft.AppOrBlueprintKey, t.List[ft.TemplateContextProcessorCallable]
206 ] = defaultdict(list, {None: [_default_template_ctx_processor]})
208 #: A data structure of functions to call to modify the keyword
209 #: arguments passed to the view function, in the format
210 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
211 #: blueprint the functions are active for, or ``None`` for all
212 #: requests.
213 #:
214 #: To register a function, use the
215 #: :meth:`url_value_preprocessor` decorator.
216 #:
217 #: This data structure is internal. It should not be modified
218 #: directly and its format may change at any time.
219 self.url_value_preprocessors: t.Dict[
220 ft.AppOrBlueprintKey,
221 t.List[ft.URLValuePreprocessorCallable],
222 ] = defaultdict(list)
224 #: A data structure of functions to call to modify the keyword
225 #: arguments when generating URLs, in the format
226 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
227 #: blueprint the functions are active for, or ``None`` for all
228 #: requests.
229 #:
230 #: To register a function, use the :meth:`url_defaults`
231 #: decorator.
232 #:
233 #: This data structure is internal. It should not be modified
234 #: directly and its format may change at any time.
235 self.url_default_functions: t.Dict[
236 ft.AppOrBlueprintKey, t.List[ft.URLDefaultCallable]
237 ] = defaultdict(list)
239 def __repr__(self) -> str:
240 return f"<{type(self).__name__} {self.name!r}>"
242 def _check_setup_finished(self, f_name: str) -> None:
243 raise NotImplementedError
245 @property
246 def static_folder(self) -> t.Optional[str]:
247 """The absolute path to the configured static folder. ``None``
248 if no static folder is set.
249 """
250 if self._static_folder is not None:
251 return os.path.join(self.root_path, self._static_folder)
252 else:
253 return None
255 @static_folder.setter
256 def static_folder(self, value: t.Optional[t.Union[str, os.PathLike]]) -> None:
257 if value is not None:
258 value = os.fspath(value).rstrip(r"\/")
260 self._static_folder = value
262 @property
263 def has_static_folder(self) -> bool:
264 """``True`` if :attr:`static_folder` is set.
266 .. versionadded:: 0.5
267 """
268 return self.static_folder is not None
270 @property
271 def static_url_path(self) -> t.Optional[str]:
272 """The URL prefix that the static route will be accessible from.
274 If it was not configured during init, it is derived from
275 :attr:`static_folder`.
276 """
277 if self._static_url_path is not None:
278 return self._static_url_path
280 if self.static_folder is not None:
281 basename = os.path.basename(self.static_folder)
282 return f"/{basename}".rstrip("/")
284 return None
286 @static_url_path.setter
287 def static_url_path(self, value: t.Optional[str]) -> None:
288 if value is not None:
289 value = value.rstrip("/")
291 self._static_url_path = value
293 def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]:
294 """Used by :func:`send_file` to determine the ``max_age`` cache
295 value for a given file path if it wasn't passed.
297 By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
298 the configuration of :data:`~flask.current_app`. This defaults
299 to ``None``, which tells the browser to use conditional requests
300 instead of a timed cache, which is usually preferable.
302 .. versionchanged:: 2.0
303 The default configuration is ``None`` instead of 12 hours.
305 .. versionadded:: 0.9
306 """
307 value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
309 if value is None:
310 return None
312 if isinstance(value, timedelta):
313 return int(value.total_seconds())
315 return value
317 def send_static_file(self, filename: str) -> "Response":
318 """The view function used to serve files from
319 :attr:`static_folder`. A route is automatically registered for
320 this view at :attr:`static_url_path` if :attr:`static_folder` is
321 set.
323 .. versionadded:: 0.5
324 """
325 if not self.has_static_folder:
326 raise RuntimeError("'static_folder' must be set to serve static_files.")
328 # send_file only knows to call get_send_file_max_age on the app,
329 # call it here so it works for blueprints too.
330 max_age = self.get_send_file_max_age(filename)
331 return send_from_directory(
332 t.cast(str, self.static_folder), filename, max_age=max_age
333 )
335 @locked_cached_property
336 def jinja_loader(self) -> t.Optional[FileSystemLoader]:
337 """The Jinja loader for this object's templates. By default this
338 is a class :class:`jinja2.loaders.FileSystemLoader` to
339 :attr:`template_folder` if it is set.
341 .. versionadded:: 0.5
342 """
343 if self.template_folder is not None:
344 return FileSystemLoader(os.path.join(self.root_path, self.template_folder))
345 else:
346 return None
348 def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
349 """Open a resource file relative to :attr:`root_path` for
350 reading.
352 For example, if the file ``schema.sql`` is next to the file
353 ``app.py`` where the ``Flask`` app is defined, it can be opened
354 with:
356 .. code-block:: python
358 with app.open_resource("schema.sql") as f:
359 conn.executescript(f.read())
361 :param resource: Path to the resource relative to
362 :attr:`root_path`.
363 :param mode: Open the file in this mode. Only reading is
364 supported, valid values are "r" (or "rt") and "rb".
365 """
366 if mode not in {"r", "rt", "rb"}:
367 raise ValueError("Resources can only be opened for reading.")
369 return open(os.path.join(self.root_path, resource), mode)
371 def _method_route(
372 self,
373 method: str,
374 rule: str,
375 options: dict,
376 ) -> t.Callable[[T_route], T_route]:
377 if "methods" in options:
378 raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
380 return self.route(rule, methods=[method], **options)
382 @setupmethod
383 def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
384 """Shortcut for :meth:`route` with ``methods=["GET"]``.
386 .. versionadded:: 2.0
387 """
388 return self._method_route("GET", rule, options)
390 @setupmethod
391 def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
392 """Shortcut for :meth:`route` with ``methods=["POST"]``.
394 .. versionadded:: 2.0
395 """
396 return self._method_route("POST", rule, options)
398 @setupmethod
399 def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
400 """Shortcut for :meth:`route` with ``methods=["PUT"]``.
402 .. versionadded:: 2.0
403 """
404 return self._method_route("PUT", rule, options)
406 @setupmethod
407 def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
408 """Shortcut for :meth:`route` with ``methods=["DELETE"]``.
410 .. versionadded:: 2.0
411 """
412 return self._method_route("DELETE", rule, options)
414 @setupmethod
415 def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
416 """Shortcut for :meth:`route` with ``methods=["PATCH"]``.
418 .. versionadded:: 2.0
419 """
420 return self._method_route("PATCH", rule, options)
422 @setupmethod
423 def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
424 """Decorate a view function to register it with the given URL
425 rule and options. Calls :meth:`add_url_rule`, which has more
426 details about the implementation.
428 .. code-block:: python
430 @app.route("/")
431 def index():
432 return "Hello, World!"
434 See :ref:`url-route-registrations`.
436 The endpoint name for the route defaults to the name of the view
437 function if the ``endpoint`` parameter isn't passed.
439 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and
440 ``OPTIONS`` are added automatically.
442 :param rule: The URL rule string.
443 :param options: Extra options passed to the
444 :class:`~werkzeug.routing.Rule` object.
445 """
447 def decorator(f: T_route) -> T_route:
448 endpoint = options.pop("endpoint", None)
449 self.add_url_rule(rule, endpoint, f, **options)
450 return f
452 return decorator
454 @setupmethod
455 def add_url_rule(
456 self,
457 rule: str,
458 endpoint: t.Optional[str] = None,
459 view_func: t.Optional[ft.RouteCallable] = None,
460 provide_automatic_options: t.Optional[bool] = None,
461 **options: t.Any,
462 ) -> None:
463 """Register a rule for routing incoming requests and building
464 URLs. The :meth:`route` decorator is a shortcut to call this
465 with the ``view_func`` argument. These are equivalent:
467 .. code-block:: python
469 @app.route("/")
470 def index():
471 ...
473 .. code-block:: python
475 def index():
476 ...
478 app.add_url_rule("/", view_func=index)
480 See :ref:`url-route-registrations`.
482 The endpoint name for the route defaults to the name of the view
483 function if the ``endpoint`` parameter isn't passed. An error
484 will be raised if a function has already been registered for the
485 endpoint.
487 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is
488 always added automatically, and ``OPTIONS`` is added
489 automatically by default.
491 ``view_func`` does not necessarily need to be passed, but if the
492 rule should participate in routing an endpoint name must be
493 associated with a view function at some point with the
494 :meth:`endpoint` decorator.
496 .. code-block:: python
498 app.add_url_rule("/", endpoint="index")
500 @app.endpoint("index")
501 def index():
502 ...
504 If ``view_func`` has a ``required_methods`` attribute, those
505 methods are added to the passed and automatic methods. If it
506 has a ``provide_automatic_methods`` attribute, it is used as the
507 default if the parameter is not passed.
509 :param rule: The URL rule string.
510 :param endpoint: The endpoint name to associate with the rule
511 and view function. Used when routing and building URLs.
512 Defaults to ``view_func.__name__``.
513 :param view_func: The view function to associate with the
514 endpoint name.
515 :param provide_automatic_options: Add the ``OPTIONS`` method and
516 respond to ``OPTIONS`` requests automatically.
517 :param options: Extra options passed to the
518 :class:`~werkzeug.routing.Rule` object.
519 """
520 raise NotImplementedError
522 @setupmethod
523 def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
524 """Decorate a view function to register it for the given
525 endpoint. Used if a rule is added without a ``view_func`` with
526 :meth:`add_url_rule`.
528 .. code-block:: python
530 app.add_url_rule("/ex", endpoint="example")
532 @app.endpoint("example")
533 def example():
534 ...
536 :param endpoint: The endpoint name to associate with the view
537 function.
538 """
540 def decorator(f: F) -> F:
541 self.view_functions[endpoint] = f
542 return f
544 return decorator
546 @setupmethod
547 def before_request(self, f: T_before_request) -> T_before_request:
548 """Register a function to run before each request.
550 For example, this can be used to open a database connection, or
551 to load the logged in user from the session.
553 .. code-block:: python
555 @app.before_request
556 def load_user():
557 if "user_id" in session:
558 g.user = db.session.get(session["user_id"])
560 The function will be called without any arguments. If it returns
561 a non-``None`` value, the value is handled as if it was the
562 return value from the view, and further request handling is
563 stopped.
565 This is available on both app and blueprint objects. When used on an app, this
566 executes before every request. When used on a blueprint, this executes before
567 every request that the blueprint handles. To register with a blueprint and
568 execute before every request, use :meth:`.Blueprint.before_app_request`.
569 """
570 self.before_request_funcs.setdefault(None, []).append(f)
571 return f
573 @setupmethod
574 def after_request(self, f: T_after_request) -> T_after_request:
575 """Register a function to run after each request to this object.
577 The function is called with the response object, and must return
578 a response object. This allows the functions to modify or
579 replace the response before it is sent.
581 If a function raises an exception, any remaining
582 ``after_request`` functions will not be called. Therefore, this
583 should not be used for actions that must execute, such as to
584 close resources. Use :meth:`teardown_request` for that.
586 This is available on both app and blueprint objects. When used on an app, this
587 executes after every request. When used on a blueprint, this executes after
588 every request that the blueprint handles. To register with a blueprint and
589 execute after every request, use :meth:`.Blueprint.after_app_request`.
590 """
591 self.after_request_funcs.setdefault(None, []).append(f)
592 return f
594 @setupmethod
595 def teardown_request(self, f: T_teardown) -> T_teardown:
596 """Register a function to be called when the request context is
597 popped. Typically this happens at the end of each request, but
598 contexts may be pushed manually as well during testing.
600 .. code-block:: python
602 with app.test_request_context():
603 ...
605 When the ``with`` block exits (or ``ctx.pop()`` is called), the
606 teardown functions are called just before the request context is
607 made inactive.
609 When a teardown function was called because of an unhandled
610 exception it will be passed an error object. If an
611 :meth:`errorhandler` is registered, it will handle the exception
612 and the teardown will not receive it.
614 Teardown functions must avoid raising exceptions. If they
615 execute code that might fail they must surround that code with a
616 ``try``/``except`` block and log any errors.
618 The return values of teardown functions are ignored.
620 This is available on both app and blueprint objects. When used on an app, this
621 executes after every request. When used on a blueprint, this executes after
622 every request that the blueprint handles. To register with a blueprint and
623 execute after every request, use :meth:`.Blueprint.teardown_app_request`.
624 """
625 self.teardown_request_funcs.setdefault(None, []).append(f)
626 return f
628 @setupmethod
629 def context_processor(
630 self,
631 f: T_template_context_processor,
632 ) -> T_template_context_processor:
633 """Registers a template context processor function. These functions run before
634 rendering a template. The keys of the returned dict are added as variables
635 available in the template.
637 This is available on both app and blueprint objects. When used on an app, this
638 is called for every rendered template. When used on a blueprint, this is called
639 for templates rendered from the blueprint's views. To register with a blueprint
640 and affect every template, use :meth:`.Blueprint.app_context_processor`.
641 """
642 self.template_context_processors[None].append(f)
643 return f
645 @setupmethod
646 def url_value_preprocessor(
647 self,
648 f: T_url_value_preprocessor,
649 ) -> T_url_value_preprocessor:
650 """Register a URL value preprocessor function for all view
651 functions in the application. These functions will be called before the
652 :meth:`before_request` functions.
654 The function can modify the values captured from the matched url before
655 they are passed to the view. For example, this can be used to pop a
656 common language code value and place it in ``g`` rather than pass it to
657 every view.
659 The function is passed the endpoint name and values dict. The return
660 value is ignored.
662 This is available on both app and blueprint objects. When used on an app, this
663 is called for every request. When used on a blueprint, this is called for
664 requests that the blueprint handles. To register with a blueprint and affect
665 every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
666 """
667 self.url_value_preprocessors[None].append(f)
668 return f
670 @setupmethod
671 def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
672 """Callback function for URL defaults for all view functions of the
673 application. It's called with the endpoint and values and should
674 update the values passed in place.
676 This is available on both app and blueprint objects. When used on an app, this
677 is called for every request. When used on a blueprint, this is called for
678 requests that the blueprint handles. To register with a blueprint and affect
679 every request, use :meth:`.Blueprint.app_url_defaults`.
680 """
681 self.url_default_functions[None].append(f)
682 return f
684 @setupmethod
685 def errorhandler(
686 self, code_or_exception: t.Union[t.Type[Exception], int]
687 ) -> t.Callable[[T_error_handler], T_error_handler]:
688 """Register a function to handle errors by code or exception class.
690 A decorator that is used to register a function given an
691 error code. Example::
693 @app.errorhandler(404)
694 def page_not_found(error):
695 return 'This page does not exist', 404
697 You can also register handlers for arbitrary exceptions::
699 @app.errorhandler(DatabaseError)
700 def special_exception_handler(error):
701 return 'Database connection failed', 500
703 This is available on both app and blueprint objects. When used on an app, this
704 can handle errors from every request. When used on a blueprint, this can handle
705 errors from requests that the blueprint handles. To register with a blueprint
706 and affect every request, use :meth:`.Blueprint.app_errorhandler`.
708 .. versionadded:: 0.7
709 Use :meth:`register_error_handler` instead of modifying
710 :attr:`error_handler_spec` directly, for application wide error
711 handlers.
713 .. versionadded:: 0.7
714 One can now additionally also register custom exception types
715 that do not necessarily have to be a subclass of the
716 :class:`~werkzeug.exceptions.HTTPException` class.
718 :param code_or_exception: the code as integer for the handler, or
719 an arbitrary exception
720 """
722 def decorator(f: T_error_handler) -> T_error_handler:
723 self.register_error_handler(code_or_exception, f)
724 return f
726 return decorator
728 @setupmethod
729 def register_error_handler(
730 self,
731 code_or_exception: t.Union[t.Type[Exception], int],
732 f: ft.ErrorHandlerCallable,
733 ) -> None:
734 """Alternative error attach function to the :meth:`errorhandler`
735 decorator that is more straightforward to use for non decorator
736 usage.
738 .. versionadded:: 0.7
739 """
740 exc_class, code = self._get_exc_class_and_code(code_or_exception)
741 self.error_handler_spec[None][code][exc_class] = f
743 @staticmethod
744 def _get_exc_class_and_code(
745 exc_class_or_code: t.Union[t.Type[Exception], int]
746 ) -> t.Tuple[t.Type[Exception], t.Optional[int]]:
747 """Get the exception class being handled. For HTTP status codes
748 or ``HTTPException`` subclasses, return both the exception and
749 status code.
751 :param exc_class_or_code: Any exception class, or an HTTP status
752 code as an integer.
753 """
754 exc_class: t.Type[Exception]
756 if isinstance(exc_class_or_code, int):
757 try:
758 exc_class = default_exceptions[exc_class_or_code]
759 except KeyError:
760 raise ValueError(
761 f"'{exc_class_or_code}' is not a recognized HTTP"
762 " error code. Use a subclass of HTTPException with"
763 " that code instead."
764 ) from None
765 else:
766 exc_class = exc_class_or_code
768 if isinstance(exc_class, Exception):
769 raise TypeError(
770 f"{exc_class!r} is an instance, not a class. Handlers"
771 " can only be registered for Exception classes or HTTP"
772 " error codes."
773 )
775 if not issubclass(exc_class, Exception):
776 raise ValueError(
777 f"'{exc_class.__name__}' is not a subclass of Exception."
778 " Handlers can only be registered for Exception classes"
779 " or HTTP error codes."
780 )
782 if issubclass(exc_class, HTTPException):
783 return exc_class, exc_class.code
784 else:
785 return exc_class, None
788def _endpoint_from_view_func(view_func: t.Callable) -> str:
789 """Internal helper that returns the default endpoint for a given
790 function. This always is the function name.
791 """
792 assert view_func is not None, "expected view func if endpoint is not provided."
793 return view_func.__name__
796def _matching_loader_thinks_module_is_package(loader, mod_name):
797 """Attempt to figure out if the given name is a package or a module.
799 :param: loader: The loader that handled the name.
800 :param mod_name: The name of the package or module.
801 """
802 # Use loader.is_package if it's available.
803 if hasattr(loader, "is_package"):
804 return loader.is_package(mod_name)
806 cls = type(loader)
808 # NamespaceLoader doesn't implement is_package, but all names it
809 # loads must be packages.
810 if cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
811 return True
813 # Otherwise we need to fail with an error that explains what went
814 # wrong.
815 raise AttributeError(
816 f"'{cls.__name__}.is_package()' must be implemented for PEP 302"
817 f" import hooks."
818 )
821def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
822 # Path.is_relative_to doesn't exist until Python 3.9
823 try:
824 path.relative_to(base)
825 return True
826 except ValueError:
827 return False
830def _find_package_path(import_name):
831 """Find the path that contains the package or module."""
832 root_mod_name, _, _ = import_name.partition(".")
834 try:
835 root_spec = importlib.util.find_spec(root_mod_name)
837 if root_spec is None:
838 raise ValueError("not found")
839 # ImportError: the machinery told us it does not exist
840 # ValueError:
841 # - the module name was invalid
842 # - the module name is __main__
843 # - *we* raised `ValueError` due to `root_spec` being `None`
844 except (ImportError, ValueError):
845 pass # handled below
846 else:
847 # namespace package
848 if root_spec.origin in {"namespace", None}:
849 package_spec = importlib.util.find_spec(import_name)
850 if package_spec is not None and package_spec.submodule_search_locations:
851 # Pick the path in the namespace that contains the submodule.
852 package_path = pathlib.Path(
853 os.path.commonpath(package_spec.submodule_search_locations)
854 )
855 search_locations = (
856 location
857 for location in root_spec.submodule_search_locations
858 if _path_is_relative_to(package_path, location)
859 )
860 else:
861 # Pick the first path.
862 search_locations = iter(root_spec.submodule_search_locations)
863 return os.path.dirname(next(search_locations))
864 # a package (with __init__.py)
865 elif root_spec.submodule_search_locations:
866 return os.path.dirname(os.path.dirname(root_spec.origin))
867 # just a normal module
868 else:
869 return os.path.dirname(root_spec.origin)
871 # we were unable to find the `package_path` using PEP 451 loaders
872 loader = pkgutil.get_loader(root_mod_name)
874 if loader is None or root_mod_name == "__main__":
875 # import name is not found, or interactive/main module
876 return os.getcwd()
878 if hasattr(loader, "get_filename"):
879 filename = loader.get_filename(root_mod_name)
880 elif hasattr(loader, "archive"):
881 # zipimporter's loader.archive points to the .egg or .zip file.
882 filename = loader.archive
883 else:
884 # At least one loader is missing both get_filename and archive:
885 # Google App Engine's HardenedModulesHook, use __file__.
886 filename = importlib.import_module(root_mod_name).__file__
888 package_path = os.path.abspath(os.path.dirname(filename))
890 # If the imported name is a package, filename is currently pointing
891 # to the root of the package, need to get the current directory.
892 if _matching_loader_thinks_module_is_package(loader, root_mod_name):
893 package_path = os.path.dirname(package_path)
895 return package_path
898def find_package(import_name: str):
899 """Find the prefix that a package is installed under, and the path
900 that it would be imported from.
902 The prefix is the directory containing the standard directory
903 hierarchy (lib, bin, etc.). If the package is not installed to the
904 system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
905 ``None`` is returned.
907 The path is the entry in :attr:`sys.path` that contains the package
908 for import. If the package is not installed, it's assumed that the
909 package was imported from the current working directory.
910 """
911 package_path = _find_package_path(import_name)
912 py_prefix = os.path.abspath(sys.prefix)
914 # installed to the system
915 if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix):
916 return py_prefix, package_path
918 site_parent, site_folder = os.path.split(package_path)
920 # installed to a virtualenv
921 if site_folder.lower() == "site-packages":
922 parent, folder = os.path.split(site_parent)
924 # Windows (prefix/lib/site-packages)
925 if folder.lower() == "lib":
926 return parent, package_path
928 # Unix (prefix/lib/pythonX.Y/site-packages)
929 if os.path.basename(parent).lower() == "lib":
930 return os.path.dirname(parent), package_path
932 # something else (prefix/site-packages)
933 return site_parent, package_path
935 # not installed
936 return None, package_path