Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/scaffold.py: 39%
254 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +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[str] = 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.
564 """
565 self.before_request_funcs.setdefault(None, []).append(f)
566 return f
568 @setupmethod
569 def after_request(self, f: T_after_request) -> T_after_request:
570 """Register a function to run after each request to this object.
572 The function is called with the response object, and must return
573 a response object. This allows the functions to modify or
574 replace the response before it is sent.
576 If a function raises an exception, any remaining
577 ``after_request`` functions will not be called. Therefore, this
578 should not be used for actions that must execute, such as to
579 close resources. Use :meth:`teardown_request` for that.
580 """
581 self.after_request_funcs.setdefault(None, []).append(f)
582 return f
584 @setupmethod
585 def teardown_request(self, f: T_teardown) -> T_teardown:
586 """Register a function to be called when the request context is
587 popped. Typically this happens at the end of each request, but
588 contexts may be pushed manually as well during testing.
590 .. code-block:: python
592 with app.test_request_context():
593 ...
595 When the ``with`` block exits (or ``ctx.pop()`` is called), the
596 teardown functions are called just before the request context is
597 made inactive.
599 When a teardown function was called because of an unhandled
600 exception it will be passed an error object. If an
601 :meth:`errorhandler` is registered, it will handle the exception
602 and the teardown will not receive it.
604 Teardown functions must avoid raising exceptions. If they
605 execute code that might fail they must surround that code with a
606 ``try``/``except`` block and log any errors.
608 The return values of teardown functions are ignored.
609 """
610 self.teardown_request_funcs.setdefault(None, []).append(f)
611 return f
613 @setupmethod
614 def context_processor(
615 self,
616 f: T_template_context_processor,
617 ) -> T_template_context_processor:
618 """Registers a template context processor function."""
619 self.template_context_processors[None].append(f)
620 return f
622 @setupmethod
623 def url_value_preprocessor(
624 self,
625 f: T_url_value_preprocessor,
626 ) -> T_url_value_preprocessor:
627 """Register a URL value preprocessor function for all view
628 functions in the application. These functions will be called before the
629 :meth:`before_request` functions.
631 The function can modify the values captured from the matched url before
632 they are passed to the view. For example, this can be used to pop a
633 common language code value and place it in ``g`` rather than pass it to
634 every view.
636 The function is passed the endpoint name and values dict. The return
637 value is ignored.
638 """
639 self.url_value_preprocessors[None].append(f)
640 return f
642 @setupmethod
643 def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
644 """Callback function for URL defaults for all view functions of the
645 application. It's called with the endpoint and values and should
646 update the values passed in place.
647 """
648 self.url_default_functions[None].append(f)
649 return f
651 @setupmethod
652 def errorhandler(
653 self, code_or_exception: t.Union[t.Type[Exception], int]
654 ) -> t.Callable[[T_error_handler], T_error_handler]:
655 """Register a function to handle errors by code or exception class.
657 A decorator that is used to register a function given an
658 error code. Example::
660 @app.errorhandler(404)
661 def page_not_found(error):
662 return 'This page does not exist', 404
664 You can also register handlers for arbitrary exceptions::
666 @app.errorhandler(DatabaseError)
667 def special_exception_handler(error):
668 return 'Database connection failed', 500
670 .. versionadded:: 0.7
671 Use :meth:`register_error_handler` instead of modifying
672 :attr:`error_handler_spec` directly, for application wide error
673 handlers.
675 .. versionadded:: 0.7
676 One can now additionally also register custom exception types
677 that do not necessarily have to be a subclass of the
678 :class:`~werkzeug.exceptions.HTTPException` class.
680 :param code_or_exception: the code as integer for the handler, or
681 an arbitrary exception
682 """
684 def decorator(f: T_error_handler) -> T_error_handler:
685 self.register_error_handler(code_or_exception, f)
686 return f
688 return decorator
690 @setupmethod
691 def register_error_handler(
692 self,
693 code_or_exception: t.Union[t.Type[Exception], int],
694 f: ft.ErrorHandlerCallable,
695 ) -> None:
696 """Alternative error attach function to the :meth:`errorhandler`
697 decorator that is more straightforward to use for non decorator
698 usage.
700 .. versionadded:: 0.7
701 """
702 exc_class, code = self._get_exc_class_and_code(code_or_exception)
703 self.error_handler_spec[None][code][exc_class] = f
705 @staticmethod
706 def _get_exc_class_and_code(
707 exc_class_or_code: t.Union[t.Type[Exception], int]
708 ) -> t.Tuple[t.Type[Exception], t.Optional[int]]:
709 """Get the exception class being handled. For HTTP status codes
710 or ``HTTPException`` subclasses, return both the exception and
711 status code.
713 :param exc_class_or_code: Any exception class, or an HTTP status
714 code as an integer.
715 """
716 exc_class: t.Type[Exception]
718 if isinstance(exc_class_or_code, int):
719 try:
720 exc_class = default_exceptions[exc_class_or_code]
721 except KeyError:
722 raise ValueError(
723 f"'{exc_class_or_code}' is not a recognized HTTP"
724 " error code. Use a subclass of HTTPException with"
725 " that code instead."
726 ) from None
727 else:
728 exc_class = exc_class_or_code
730 if isinstance(exc_class, Exception):
731 raise TypeError(
732 f"{exc_class!r} is an instance, not a class. Handlers"
733 " can only be registered for Exception classes or HTTP"
734 " error codes."
735 )
737 if not issubclass(exc_class, Exception):
738 raise ValueError(
739 f"'{exc_class.__name__}' is not a subclass of Exception."
740 " Handlers can only be registered for Exception classes"
741 " or HTTP error codes."
742 )
744 if issubclass(exc_class, HTTPException):
745 return exc_class, exc_class.code
746 else:
747 return exc_class, None
750def _endpoint_from_view_func(view_func: t.Callable) -> str:
751 """Internal helper that returns the default endpoint for a given
752 function. This always is the function name.
753 """
754 assert view_func is not None, "expected view func if endpoint is not provided."
755 return view_func.__name__
758def _matching_loader_thinks_module_is_package(loader, mod_name):
759 """Attempt to figure out if the given name is a package or a module.
761 :param: loader: The loader that handled the name.
762 :param mod_name: The name of the package or module.
763 """
764 # Use loader.is_package if it's available.
765 if hasattr(loader, "is_package"):
766 return loader.is_package(mod_name)
768 cls = type(loader)
770 # NamespaceLoader doesn't implement is_package, but all names it
771 # loads must be packages.
772 if cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
773 return True
775 # Otherwise we need to fail with an error that explains what went
776 # wrong.
777 raise AttributeError(
778 f"'{cls.__name__}.is_package()' must be implemented for PEP 302"
779 f" import hooks."
780 )
783def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
784 # Path.is_relative_to doesn't exist until Python 3.9
785 try:
786 path.relative_to(base)
787 return True
788 except ValueError:
789 return False
792def _find_package_path(import_name):
793 """Find the path that contains the package or module."""
794 root_mod_name, _, _ = import_name.partition(".")
796 try:
797 root_spec = importlib.util.find_spec(root_mod_name)
799 if root_spec is None:
800 raise ValueError("not found")
801 # ImportError: the machinery told us it does not exist
802 # ValueError:
803 # - the module name was invalid
804 # - the module name is __main__
805 # - *we* raised `ValueError` due to `root_spec` being `None`
806 except (ImportError, ValueError):
807 pass # handled below
808 else:
809 # namespace package
810 if root_spec.origin in {"namespace", None}:
811 package_spec = importlib.util.find_spec(import_name)
812 if package_spec is not None and package_spec.submodule_search_locations:
813 # Pick the path in the namespace that contains the submodule.
814 package_path = pathlib.Path(
815 os.path.commonpath(package_spec.submodule_search_locations)
816 )
817 search_locations = (
818 location
819 for location in root_spec.submodule_search_locations
820 if _path_is_relative_to(package_path, location)
821 )
822 else:
823 # Pick the first path.
824 search_locations = iter(root_spec.submodule_search_locations)
825 return os.path.dirname(next(search_locations))
826 # a package (with __init__.py)
827 elif root_spec.submodule_search_locations:
828 return os.path.dirname(os.path.dirname(root_spec.origin))
829 # just a normal module
830 else:
831 return os.path.dirname(root_spec.origin)
833 # we were unable to find the `package_path` using PEP 451 loaders
834 loader = pkgutil.get_loader(root_mod_name)
836 if loader is None or root_mod_name == "__main__":
837 # import name is not found, or interactive/main module
838 return os.getcwd()
840 if hasattr(loader, "get_filename"):
841 filename = loader.get_filename(root_mod_name)
842 elif hasattr(loader, "archive"):
843 # zipimporter's loader.archive points to the .egg or .zip file.
844 filename = loader.archive
845 else:
846 # At least one loader is missing both get_filename and archive:
847 # Google App Engine's HardenedModulesHook, use __file__.
848 filename = importlib.import_module(root_mod_name).__file__
850 package_path = os.path.abspath(os.path.dirname(filename))
852 # If the imported name is a package, filename is currently pointing
853 # to the root of the package, need to get the current directory.
854 if _matching_loader_thinks_module_is_package(loader, root_mod_name):
855 package_path = os.path.dirname(package_path)
857 return package_path
860def find_package(import_name: str):
861 """Find the prefix that a package is installed under, and the path
862 that it would be imported from.
864 The prefix is the directory containing the standard directory
865 hierarchy (lib, bin, etc.). If the package is not installed to the
866 system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
867 ``None`` is returned.
869 The path is the entry in :attr:`sys.path` that contains the package
870 for import. If the package is not installed, it's assumed that the
871 package was imported from the current working directory.
872 """
873 package_path = _find_package_path(import_name)
874 py_prefix = os.path.abspath(sys.prefix)
876 # installed to the system
877 if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix):
878 return py_prefix, package_path
880 site_parent, site_folder = os.path.split(package_path)
882 # installed to a virtualenv
883 if site_folder.lower() == "site-packages":
884 parent, folder = os.path.split(site_parent)
886 # Windows (prefix/lib/site-packages)
887 if folder.lower() == "lib":
888 return parent, package_path
890 # Unix (prefix/lib/pythonX.Y/site-packages)
891 if os.path.basename(parent).lower() == "lib":
892 return os.path.dirname(parent), package_path
894 # something else (prefix/site-packages)
895 return site_parent, package_path
897 # not installed
898 return None, package_path