Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/scaffold.py: 63%
252 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-09 06:08 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-09 06:08 +0000
1from __future__ import annotations
3import importlib.util
4import os
5import pathlib
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
15from werkzeug.utils import cached_property
17from . import typing as ft
18from .cli import AppGroup
19from .globals import current_app
20from .helpers import get_root_path
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: str | None = None
75 _static_url_path: str | None = None
77 def __init__(
78 self,
79 import_name: str,
80 static_folder: str | os.PathLike | None = None,
81 static_url_path: str | None = None,
82 template_folder: str | os.PathLike | None = None,
83 root_path: str | None = None,
84 ):
85 #: The name of the package or module that this object belongs
86 #: to. Do not change this once it is set by the constructor.
87 self.import_name = import_name
89 self.static_folder = static_folder # type: ignore
90 self.static_url_path = static_url_path
92 #: The path to the templates folder, relative to
93 #: :attr:`root_path`, to add to the template loader. ``None`` if
94 #: templates should not be added.
95 self.template_folder = template_folder
97 if root_path is None:
98 root_path = get_root_path(self.import_name)
100 #: Absolute path to the package on the filesystem. Used to look
101 #: up resources contained in the package.
102 self.root_path = root_path
104 #: The Click command group for registering CLI commands for this
105 #: object. The commands are available from the ``flask`` command
106 #: once the application has been discovered and blueprints have
107 #: been registered.
108 self.cli = AppGroup()
110 #: A dictionary mapping endpoint names to view functions.
111 #:
112 #: To register a view function, use the :meth:`route` decorator.
113 #:
114 #: This data structure is internal. It should not be modified
115 #: directly and its format may change at any time.
116 self.view_functions: dict[str, t.Callable] = {}
118 #: A data structure of registered error handlers, in the format
119 #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is
120 #: the name of a blueprint the handlers are active for, or
121 #: ``None`` for all requests. The ``code`` key is the HTTP
122 #: status code for ``HTTPException``, or ``None`` for
123 #: other exceptions. The innermost dictionary maps exception
124 #: classes to handler functions.
125 #:
126 #: To register an error handler, use the :meth:`errorhandler`
127 #: decorator.
128 #:
129 #: This data structure is internal. It should not be modified
130 #: directly and its format may change at any time.
131 self.error_handler_spec: dict[
132 ft.AppOrBlueprintKey,
133 dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]],
134 ] = defaultdict(lambda: defaultdict(dict))
136 #: A data structure of functions to call at the beginning of
137 #: each request, in the format ``{scope: [functions]}``. The
138 #: ``scope`` key is the name of a blueprint the functions are
139 #: active for, or ``None`` for all requests.
140 #:
141 #: To register a function, use the :meth:`before_request`
142 #: decorator.
143 #:
144 #: This data structure is internal. It should not be modified
145 #: directly and its format may change at any time.
146 self.before_request_funcs: dict[
147 ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable]
148 ] = defaultdict(list)
150 #: A data structure of functions to call at the end of each
151 #: 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:`after_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.after_request_funcs: dict[
161 ft.AppOrBlueprintKey, list[ft.AfterRequestCallable]
162 ] = defaultdict(list)
164 #: A data structure of functions to call at the end of each
165 #: request even if an exception is raised, in the format
166 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
167 #: blueprint the functions are active for, or ``None`` for all
168 #: requests.
169 #:
170 #: To register a function, use the :meth:`teardown_request`
171 #: decorator.
172 #:
173 #: This data structure is internal. It should not be modified
174 #: directly and its format may change at any time.
175 self.teardown_request_funcs: dict[
176 ft.AppOrBlueprintKey, list[ft.TeardownCallable]
177 ] = defaultdict(list)
179 #: A data structure of functions to call to pass extra context
180 #: values when rendering templates, in the format
181 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
182 #: blueprint the functions are active for, or ``None`` for all
183 #: requests.
184 #:
185 #: To register a function, use the :meth:`context_processor`
186 #: decorator.
187 #:
188 #: This data structure is internal. It should not be modified
189 #: directly and its format may change at any time.
190 self.template_context_processors: dict[
191 ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable]
192 ] = defaultdict(list, {None: [_default_template_ctx_processor]})
194 #: A data structure of functions to call to modify the keyword
195 #: arguments passed to the view function, in the format
196 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
197 #: blueprint the functions are active for, or ``None`` for all
198 #: requests.
199 #:
200 #: To register a function, use the
201 #: :meth:`url_value_preprocessor` decorator.
202 #:
203 #: This data structure is internal. It should not be modified
204 #: directly and its format may change at any time.
205 self.url_value_preprocessors: dict[
206 ft.AppOrBlueprintKey,
207 list[ft.URLValuePreprocessorCallable],
208 ] = defaultdict(list)
210 #: A data structure of functions to call to modify the keyword
211 #: arguments when generating URLs, in the format
212 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
213 #: blueprint the functions are active for, or ``None`` for all
214 #: requests.
215 #:
216 #: To register a function, use the :meth:`url_defaults`
217 #: decorator.
218 #:
219 #: This data structure is internal. It should not be modified
220 #: directly and its format may change at any time.
221 self.url_default_functions: dict[
222 ft.AppOrBlueprintKey, list[ft.URLDefaultCallable]
223 ] = defaultdict(list)
225 def __repr__(self) -> str:
226 return f"<{type(self).__name__} {self.name!r}>"
228 def _check_setup_finished(self, f_name: str) -> None:
229 raise NotImplementedError
231 @property
232 def static_folder(self) -> str | None:
233 """The absolute path to the configured static folder. ``None``
234 if no static folder is set.
235 """
236 if self._static_folder is not None:
237 return os.path.join(self.root_path, self._static_folder)
238 else:
239 return None
241 @static_folder.setter
242 def static_folder(self, value: str | os.PathLike | None) -> None:
243 if value is not None:
244 value = os.fspath(value).rstrip(r"\/")
246 self._static_folder = value
248 @property
249 def has_static_folder(self) -> bool:
250 """``True`` if :attr:`static_folder` is set.
252 .. versionadded:: 0.5
253 """
254 return self.static_folder is not None
256 @property
257 def static_url_path(self) -> str | None:
258 """The URL prefix that the static route will be accessible from.
260 If it was not configured during init, it is derived from
261 :attr:`static_folder`.
262 """
263 if self._static_url_path is not None:
264 return self._static_url_path
266 if self.static_folder is not None:
267 basename = os.path.basename(self.static_folder)
268 return f"/{basename}".rstrip("/")
270 return None
272 @static_url_path.setter
273 def static_url_path(self, value: str | None) -> None:
274 if value is not None:
275 value = value.rstrip("/")
277 self._static_url_path = value
279 def get_send_file_max_age(self, filename: str | None) -> int | None:
280 """Used by :func:`send_file` to determine the ``max_age`` cache
281 value for a given file path if it wasn't passed.
283 By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
284 the configuration of :data:`~flask.current_app`. This defaults
285 to ``None``, which tells the browser to use conditional requests
286 instead of a timed cache, which is usually preferable.
288 .. versionchanged:: 2.0
289 The default configuration is ``None`` instead of 12 hours.
291 .. versionadded:: 0.9
292 """
293 value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
295 if value is None:
296 return None
298 if isinstance(value, timedelta):
299 return int(value.total_seconds())
301 return value
303 def send_static_file(self, filename: str) -> Response:
304 """The view function used to serve files from
305 :attr:`static_folder`. A route is automatically registered for
306 this view at :attr:`static_url_path` if :attr:`static_folder` is
307 set.
309 .. versionadded:: 0.5
310 """
311 if not self.has_static_folder:
312 raise RuntimeError("'static_folder' must be set to serve static_files.")
314 # send_file only knows to call get_send_file_max_age on the app,
315 # call it here so it works for blueprints too.
316 max_age = self.get_send_file_max_age(filename)
317 return send_from_directory(
318 t.cast(str, self.static_folder), filename, max_age=max_age
319 )
321 @cached_property
322 def jinja_loader(self) -> FileSystemLoader | None:
323 """The Jinja loader for this object's templates. By default this
324 is a class :class:`jinja2.loaders.FileSystemLoader` to
325 :attr:`template_folder` if it is set.
327 .. versionadded:: 0.5
328 """
329 if self.template_folder is not None:
330 return FileSystemLoader(os.path.join(self.root_path, self.template_folder))
331 else:
332 return None
334 def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
335 """Open a resource file relative to :attr:`root_path` for
336 reading.
338 For example, if the file ``schema.sql`` is next to the file
339 ``app.py`` where the ``Flask`` app is defined, it can be opened
340 with:
342 .. code-block:: python
344 with app.open_resource("schema.sql") as f:
345 conn.executescript(f.read())
347 :param resource: Path to the resource relative to
348 :attr:`root_path`.
349 :param mode: Open the file in this mode. Only reading is
350 supported, valid values are "r" (or "rt") and "rb".
351 """
352 if mode not in {"r", "rt", "rb"}:
353 raise ValueError("Resources can only be opened for reading.")
355 return open(os.path.join(self.root_path, resource), mode)
357 def _method_route(
358 self,
359 method: str,
360 rule: str,
361 options: dict,
362 ) -> t.Callable[[T_route], T_route]:
363 if "methods" in options:
364 raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
366 return self.route(rule, methods=[method], **options)
368 @setupmethod
369 def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
370 """Shortcut for :meth:`route` with ``methods=["GET"]``.
372 .. versionadded:: 2.0
373 """
374 return self._method_route("GET", rule, options)
376 @setupmethod
377 def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
378 """Shortcut for :meth:`route` with ``methods=["POST"]``.
380 .. versionadded:: 2.0
381 """
382 return self._method_route("POST", rule, options)
384 @setupmethod
385 def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
386 """Shortcut for :meth:`route` with ``methods=["PUT"]``.
388 .. versionadded:: 2.0
389 """
390 return self._method_route("PUT", rule, options)
392 @setupmethod
393 def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
394 """Shortcut for :meth:`route` with ``methods=["DELETE"]``.
396 .. versionadded:: 2.0
397 """
398 return self._method_route("DELETE", rule, options)
400 @setupmethod
401 def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
402 """Shortcut for :meth:`route` with ``methods=["PATCH"]``.
404 .. versionadded:: 2.0
405 """
406 return self._method_route("PATCH", rule, options)
408 @setupmethod
409 def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
410 """Decorate a view function to register it with the given URL
411 rule and options. Calls :meth:`add_url_rule`, which has more
412 details about the implementation.
414 .. code-block:: python
416 @app.route("/")
417 def index():
418 return "Hello, World!"
420 See :ref:`url-route-registrations`.
422 The endpoint name for the route defaults to the name of the view
423 function if the ``endpoint`` parameter isn't passed.
425 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and
426 ``OPTIONS`` are added automatically.
428 :param rule: The URL rule string.
429 :param options: Extra options passed to the
430 :class:`~werkzeug.routing.Rule` object.
431 """
433 def decorator(f: T_route) -> T_route:
434 endpoint = options.pop("endpoint", None)
435 self.add_url_rule(rule, endpoint, f, **options)
436 return f
438 return decorator
440 @setupmethod
441 def add_url_rule(
442 self,
443 rule: str,
444 endpoint: str | None = None,
445 view_func: ft.RouteCallable | None = None,
446 provide_automatic_options: bool | None = None,
447 **options: t.Any,
448 ) -> None:
449 """Register a rule for routing incoming requests and building
450 URLs. The :meth:`route` decorator is a shortcut to call this
451 with the ``view_func`` argument. These are equivalent:
453 .. code-block:: python
455 @app.route("/")
456 def index():
457 ...
459 .. code-block:: python
461 def index():
462 ...
464 app.add_url_rule("/", view_func=index)
466 See :ref:`url-route-registrations`.
468 The endpoint name for the route defaults to the name of the view
469 function if the ``endpoint`` parameter isn't passed. An error
470 will be raised if a function has already been registered for the
471 endpoint.
473 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is
474 always added automatically, and ``OPTIONS`` is added
475 automatically by default.
477 ``view_func`` does not necessarily need to be passed, but if the
478 rule should participate in routing an endpoint name must be
479 associated with a view function at some point with the
480 :meth:`endpoint` decorator.
482 .. code-block:: python
484 app.add_url_rule("/", endpoint="index")
486 @app.endpoint("index")
487 def index():
488 ...
490 If ``view_func`` has a ``required_methods`` attribute, those
491 methods are added to the passed and automatic methods. If it
492 has a ``provide_automatic_methods`` attribute, it is used as the
493 default if the parameter is not passed.
495 :param rule: The URL rule string.
496 :param endpoint: The endpoint name to associate with the rule
497 and view function. Used when routing and building URLs.
498 Defaults to ``view_func.__name__``.
499 :param view_func: The view function to associate with the
500 endpoint name.
501 :param provide_automatic_options: Add the ``OPTIONS`` method and
502 respond to ``OPTIONS`` requests automatically.
503 :param options: Extra options passed to the
504 :class:`~werkzeug.routing.Rule` object.
505 """
506 raise NotImplementedError
508 @setupmethod
509 def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
510 """Decorate a view function to register it for the given
511 endpoint. Used if a rule is added without a ``view_func`` with
512 :meth:`add_url_rule`.
514 .. code-block:: python
516 app.add_url_rule("/ex", endpoint="example")
518 @app.endpoint("example")
519 def example():
520 ...
522 :param endpoint: The endpoint name to associate with the view
523 function.
524 """
526 def decorator(f: F) -> F:
527 self.view_functions[endpoint] = f
528 return f
530 return decorator
532 @setupmethod
533 def before_request(self, f: T_before_request) -> T_before_request:
534 """Register a function to run before each request.
536 For example, this can be used to open a database connection, or
537 to load the logged in user from the session.
539 .. code-block:: python
541 @app.before_request
542 def load_user():
543 if "user_id" in session:
544 g.user = db.session.get(session["user_id"])
546 The function will be called without any arguments. If it returns
547 a non-``None`` value, the value is handled as if it was the
548 return value from the view, and further request handling is
549 stopped.
551 This is available on both app and blueprint objects. When used on an app, this
552 executes before every request. When used on a blueprint, this executes before
553 every request that the blueprint handles. To register with a blueprint and
554 execute before every request, use :meth:`.Blueprint.before_app_request`.
555 """
556 self.before_request_funcs.setdefault(None, []).append(f)
557 return f
559 @setupmethod
560 def after_request(self, f: T_after_request) -> T_after_request:
561 """Register a function to run after each request to this object.
563 The function is called with the response object, and must return
564 a response object. This allows the functions to modify or
565 replace the response before it is sent.
567 If a function raises an exception, any remaining
568 ``after_request`` functions will not be called. Therefore, this
569 should not be used for actions that must execute, such as to
570 close resources. Use :meth:`teardown_request` for that.
572 This is available on both app and blueprint objects. When used on an app, this
573 executes after every request. When used on a blueprint, this executes after
574 every request that the blueprint handles. To register with a blueprint and
575 execute after every request, use :meth:`.Blueprint.after_app_request`.
576 """
577 self.after_request_funcs.setdefault(None, []).append(f)
578 return f
580 @setupmethod
581 def teardown_request(self, f: T_teardown) -> T_teardown:
582 """Register a function to be called when the request context is
583 popped. Typically this happens at the end of each request, but
584 contexts may be pushed manually as well during testing.
586 .. code-block:: python
588 with app.test_request_context():
589 ...
591 When the ``with`` block exits (or ``ctx.pop()`` is called), the
592 teardown functions are called just before the request context is
593 made inactive.
595 When a teardown function was called because of an unhandled
596 exception it will be passed an error object. If an
597 :meth:`errorhandler` is registered, it will handle the exception
598 and the teardown will not receive it.
600 Teardown functions must avoid raising exceptions. If they
601 execute code that might fail they must surround that code with a
602 ``try``/``except`` block and log any errors.
604 The return values of teardown functions are ignored.
606 This is available on both app and blueprint objects. When used on an app, this
607 executes after every request. When used on a blueprint, this executes after
608 every request that the blueprint handles. To register with a blueprint and
609 execute after every request, use :meth:`.Blueprint.teardown_app_request`.
610 """
611 self.teardown_request_funcs.setdefault(None, []).append(f)
612 return f
614 @setupmethod
615 def context_processor(
616 self,
617 f: T_template_context_processor,
618 ) -> T_template_context_processor:
619 """Registers a template context processor function. These functions run before
620 rendering a template. The keys of the returned dict are added as variables
621 available in the template.
623 This is available on both app and blueprint objects. When used on an app, this
624 is called for every rendered template. When used on a blueprint, this is called
625 for templates rendered from the blueprint's views. To register with a blueprint
626 and affect every template, use :meth:`.Blueprint.app_context_processor`.
627 """
628 self.template_context_processors[None].append(f)
629 return f
631 @setupmethod
632 def url_value_preprocessor(
633 self,
634 f: T_url_value_preprocessor,
635 ) -> T_url_value_preprocessor:
636 """Register a URL value preprocessor function for all view
637 functions in the application. These functions will be called before the
638 :meth:`before_request` functions.
640 The function can modify the values captured from the matched url before
641 they are passed to the view. For example, this can be used to pop a
642 common language code value and place it in ``g`` rather than pass it to
643 every view.
645 The function is passed the endpoint name and values dict. The return
646 value is ignored.
648 This is available on both app and blueprint objects. When used on an app, this
649 is called for every request. When used on a blueprint, this is called for
650 requests that the blueprint handles. To register with a blueprint and affect
651 every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
652 """
653 self.url_value_preprocessors[None].append(f)
654 return f
656 @setupmethod
657 def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
658 """Callback function for URL defaults for all view functions of the
659 application. It's called with the endpoint and values and should
660 update the values passed in place.
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_defaults`.
666 """
667 self.url_default_functions[None].append(f)
668 return f
670 @setupmethod
671 def errorhandler(
672 self, code_or_exception: type[Exception] | int
673 ) -> t.Callable[[T_error_handler], T_error_handler]:
674 """Register a function to handle errors by code or exception class.
676 A decorator that is used to register a function given an
677 error code. Example::
679 @app.errorhandler(404)
680 def page_not_found(error):
681 return 'This page does not exist', 404
683 You can also register handlers for arbitrary exceptions::
685 @app.errorhandler(DatabaseError)
686 def special_exception_handler(error):
687 return 'Database connection failed', 500
689 This is available on both app and blueprint objects. When used on an app, this
690 can handle errors from every request. When used on a blueprint, this can handle
691 errors from requests that the blueprint handles. To register with a blueprint
692 and affect every request, use :meth:`.Blueprint.app_errorhandler`.
694 .. versionadded:: 0.7
695 Use :meth:`register_error_handler` instead of modifying
696 :attr:`error_handler_spec` directly, for application wide error
697 handlers.
699 .. versionadded:: 0.7
700 One can now additionally also register custom exception types
701 that do not necessarily have to be a subclass of the
702 :class:`~werkzeug.exceptions.HTTPException` class.
704 :param code_or_exception: the code as integer for the handler, or
705 an arbitrary exception
706 """
708 def decorator(f: T_error_handler) -> T_error_handler:
709 self.register_error_handler(code_or_exception, f)
710 return f
712 return decorator
714 @setupmethod
715 def register_error_handler(
716 self,
717 code_or_exception: type[Exception] | int,
718 f: ft.ErrorHandlerCallable,
719 ) -> None:
720 """Alternative error attach function to the :meth:`errorhandler`
721 decorator that is more straightforward to use for non decorator
722 usage.
724 .. versionadded:: 0.7
725 """
726 exc_class, code = self._get_exc_class_and_code(code_or_exception)
727 self.error_handler_spec[None][code][exc_class] = f
729 @staticmethod
730 def _get_exc_class_and_code(
731 exc_class_or_code: type[Exception] | int,
732 ) -> tuple[type[Exception], int | None]:
733 """Get the exception class being handled. For HTTP status codes
734 or ``HTTPException`` subclasses, return both the exception and
735 status code.
737 :param exc_class_or_code: Any exception class, or an HTTP status
738 code as an integer.
739 """
740 exc_class: type[Exception]
742 if isinstance(exc_class_or_code, int):
743 try:
744 exc_class = default_exceptions[exc_class_or_code]
745 except KeyError:
746 raise ValueError(
747 f"'{exc_class_or_code}' is not a recognized HTTP"
748 " error code. Use a subclass of HTTPException with"
749 " that code instead."
750 ) from None
751 else:
752 exc_class = exc_class_or_code
754 if isinstance(exc_class, Exception):
755 raise TypeError(
756 f"{exc_class!r} is an instance, not a class. Handlers"
757 " can only be registered for Exception classes or HTTP"
758 " error codes."
759 )
761 if not issubclass(exc_class, Exception):
762 raise ValueError(
763 f"'{exc_class.__name__}' is not a subclass of Exception."
764 " Handlers can only be registered for Exception classes"
765 " or HTTP error codes."
766 )
768 if issubclass(exc_class, HTTPException):
769 return exc_class, exc_class.code
770 else:
771 return exc_class, None
774def _endpoint_from_view_func(view_func: t.Callable) -> str:
775 """Internal helper that returns the default endpoint for a given
776 function. This always is the function name.
777 """
778 assert view_func is not None, "expected view func if endpoint is not provided."
779 return view_func.__name__
782def _matching_loader_thinks_module_is_package(loader, mod_name):
783 """Attempt to figure out if the given name is a package or a module.
785 :param: loader: The loader that handled the name.
786 :param mod_name: The name of the package or module.
787 """
788 # Use loader.is_package if it's available.
789 if hasattr(loader, "is_package"):
790 return loader.is_package(mod_name)
792 cls = type(loader)
794 # NamespaceLoader doesn't implement is_package, but all names it
795 # loads must be packages.
796 if cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
797 return True
799 # Otherwise we need to fail with an error that explains what went
800 # wrong.
801 raise AttributeError(
802 f"'{cls.__name__}.is_package()' must be implemented for PEP 302"
803 f" import hooks."
804 )
807def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
808 # Path.is_relative_to doesn't exist until Python 3.9
809 try:
810 path.relative_to(base)
811 return True
812 except ValueError:
813 return False
816def _find_package_path(import_name):
817 """Find the path that contains the package or module."""
818 root_mod_name, _, _ = import_name.partition(".")
820 try:
821 root_spec = importlib.util.find_spec(root_mod_name)
823 if root_spec is None:
824 raise ValueError("not found")
825 # ImportError: the machinery told us it does not exist
826 # ValueError:
827 # - the module name was invalid
828 # - the module name is __main__
829 # - *we* raised `ValueError` due to `root_spec` being `None`
830 except (ImportError, ValueError):
831 pass # handled below
832 else:
833 # namespace package
834 if root_spec.origin in {"namespace", None}:
835 package_spec = importlib.util.find_spec(import_name)
836 if package_spec is not None and package_spec.submodule_search_locations:
837 # Pick the path in the namespace that contains the submodule.
838 package_path = pathlib.Path(
839 os.path.commonpath(package_spec.submodule_search_locations)
840 )
841 search_locations = (
842 location
843 for location in root_spec.submodule_search_locations
844 if _path_is_relative_to(package_path, location)
845 )
846 else:
847 # Pick the first path.
848 search_locations = iter(root_spec.submodule_search_locations)
849 return os.path.dirname(next(search_locations))
850 # a package (with __init__.py)
851 elif root_spec.submodule_search_locations:
852 return os.path.dirname(os.path.dirname(root_spec.origin))
853 # just a normal module
854 else:
855 return os.path.dirname(root_spec.origin)
857 # we were unable to find the `package_path` using PEP 451 loaders
858 spec = importlib.util.find_spec(root_mod_name)
859 loader = spec.loader if spec is not None else None
861 if loader is None or root_mod_name == "__main__":
862 # import name is not found, or interactive/main module
863 return os.getcwd()
865 if hasattr(loader, "get_filename"):
866 filename = loader.get_filename(root_mod_name)
867 elif hasattr(loader, "archive"):
868 # zipimporter's loader.archive points to the .zip file.
869 filename = loader.archive
870 else:
871 # At least one loader is missing both get_filename and archive:
872 # Google App Engine's HardenedModulesHook, use __file__.
873 filename = importlib.import_module(root_mod_name).__file__
875 package_path = os.path.abspath(os.path.dirname(filename))
877 # If the imported name is a package, filename is currently pointing
878 # to the root of the package, need to get the current directory.
879 if _matching_loader_thinks_module_is_package(loader, root_mod_name):
880 package_path = os.path.dirname(package_path)
882 return package_path
885def find_package(import_name: str):
886 """Find the prefix that a package is installed under, and the path
887 that it would be imported from.
889 The prefix is the directory containing the standard directory
890 hierarchy (lib, bin, etc.). If the package is not installed to the
891 system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
892 ``None`` is returned.
894 The path is the entry in :attr:`sys.path` that contains the package
895 for import. If the package is not installed, it's assumed that the
896 package was imported from the current working directory.
897 """
898 package_path = _find_package_path(import_name)
899 py_prefix = os.path.abspath(sys.prefix)
901 # installed to the system
902 if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix):
903 return py_prefix, package_path
905 site_parent, site_folder = os.path.split(package_path)
907 # installed to a virtualenv
908 if site_folder.lower() == "site-packages":
909 parent, folder = os.path.split(site_parent)
911 # Windows (prefix/lib/site-packages)
912 if folder.lower() == "lib":
913 return parent, package_path
915 # Unix (prefix/lib/pythonX.Y/site-packages)
916 if os.path.basename(parent).lower() == "lib":
917 return os.path.dirname(parent), package_path
919 # something else (prefix/site-packages)
920 return site_parent, package_path
922 # not installed
923 return None, package_path