Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/scaffold.py: 56%
238 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +0000
1import importlib.util
2import os
3import pathlib
4import pkgutil
5import sys
6import typing as t
7from collections import defaultdict
8from functools import update_wrapper
9from json import JSONDecoder
10from json import JSONEncoder
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:
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])
33def setupmethod(f: F) -> F:
34 """Wraps a method so that it performs a check in debug mode if the
35 first request was already handled.
36 """
38 def wrapper_func(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
39 if self._is_setup_finished():
40 raise AssertionError(
41 "A setup function was called after the first request "
42 "was handled. This usually indicates a bug in the"
43 " application where a module was not imported and"
44 " decorators or other functionality was called too"
45 " late.\nTo fix this make sure to import all your view"
46 " modules, database models, and everything related at a"
47 " central place before the application starts serving"
48 " requests."
49 )
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 json_encoder: t.Optional[t.Type[JSONEncoder]] = None
81 #: JSON decoder class used by :func:`flask.json.loads`. If a
82 #: blueprint sets this, it will be used instead of the app's value.
83 json_decoder: t.Optional[t.Type[JSONDecoder]] = None
85 def __init__(
86 self,
87 import_name: str,
88 static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
89 static_url_path: t.Optional[str] = None,
90 template_folder: t.Optional[str] = None,
91 root_path: t.Optional[str] = None,
92 ):
93 #: The name of the package or module that this object belongs
94 #: to. Do not change this once it is set by the constructor.
95 self.import_name = import_name
97 self.static_folder = static_folder # type: ignore
98 self.static_url_path = static_url_path
100 #: The path to the templates folder, relative to
101 #: :attr:`root_path`, to add to the template loader. ``None`` if
102 #: templates should not be added.
103 self.template_folder = template_folder
105 if root_path is None:
106 root_path = get_root_path(self.import_name)
108 #: Absolute path to the package on the filesystem. Used to look
109 #: up resources contained in the package.
110 self.root_path = root_path
112 #: The Click command group for registering CLI commands for this
113 #: object. The commands are available from the ``flask`` command
114 #: once the application has been discovered and blueprints have
115 #: been registered.
116 self.cli = AppGroup()
118 #: A dictionary mapping endpoint names to view functions.
119 #:
120 #: To register a view function, use the :meth:`route` decorator.
121 #:
122 #: This data structure is internal. It should not be modified
123 #: directly and its format may change at any time.
124 self.view_functions: t.Dict[str, t.Callable] = {}
126 #: A data structure of registered error handlers, in the format
127 #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is
128 #: the name of a blueprint the handlers are active for, or
129 #: ``None`` for all requests. The ``code`` key is the HTTP
130 #: status code for ``HTTPException``, or ``None`` for
131 #: other exceptions. The innermost dictionary maps exception
132 #: classes to handler functions.
133 #:
134 #: To register an error handler, use the :meth:`errorhandler`
135 #: decorator.
136 #:
137 #: This data structure is internal. It should not be modified
138 #: directly and its format may change at any time.
139 self.error_handler_spec: t.Dict[
140 ft.AppOrBlueprintKey,
141 t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ft.ErrorHandlerCallable]],
142 ] = defaultdict(lambda: defaultdict(dict))
144 #: A data structure of functions to call at the beginning of
145 #: each request, in the format ``{scope: [functions]}``. The
146 #: ``scope`` key is the name of a blueprint the functions are
147 #: active for, or ``None`` for all requests.
148 #:
149 #: To register a function, use the :meth:`before_request`
150 #: decorator.
151 #:
152 #: This data structure is internal. It should not be modified
153 #: directly and its format may change at any time.
154 self.before_request_funcs: t.Dict[
155 ft.AppOrBlueprintKey, t.List[ft.BeforeRequestCallable]
156 ] = defaultdict(list)
158 #: A data structure of functions to call at the end of each
159 #: request, in the format ``{scope: [functions]}``. The
160 #: ``scope`` key is the name of a blueprint the functions are
161 #: active for, or ``None`` for all requests.
162 #:
163 #: To register a function, use the :meth:`after_request`
164 #: decorator.
165 #:
166 #: This data structure is internal. It should not be modified
167 #: directly and its format may change at any time.
168 self.after_request_funcs: t.Dict[
169 ft.AppOrBlueprintKey, t.List[ft.AfterRequestCallable]
170 ] = defaultdict(list)
172 #: A data structure of functions to call at the end of each
173 #: request even if an exception is raised, in the format
174 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
175 #: blueprint the functions are active for, or ``None`` for all
176 #: requests.
177 #:
178 #: To register a function, use the :meth:`teardown_request`
179 #: decorator.
180 #:
181 #: This data structure is internal. It should not be modified
182 #: directly and its format may change at any time.
183 self.teardown_request_funcs: t.Dict[
184 ft.AppOrBlueprintKey, t.List[ft.TeardownCallable]
185 ] = defaultdict(list)
187 #: A data structure of functions to call to pass extra context
188 #: values when rendering templates, in the format
189 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
190 #: blueprint the functions are active for, or ``None`` for all
191 #: requests.
192 #:
193 #: To register a function, use the :meth:`context_processor`
194 #: decorator.
195 #:
196 #: This data structure is internal. It should not be modified
197 #: directly and its format may change at any time.
198 self.template_context_processors: t.Dict[
199 ft.AppOrBlueprintKey, t.List[ft.TemplateContextProcessorCallable]
200 ] = defaultdict(list, {None: [_default_template_ctx_processor]})
202 #: A data structure of functions to call to modify the keyword
203 #: arguments passed to the view function, in the format
204 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
205 #: blueprint the functions are active for, or ``None`` for all
206 #: requests.
207 #:
208 #: To register a function, use the
209 #: :meth:`url_value_preprocessor` decorator.
210 #:
211 #: This data structure is internal. It should not be modified
212 #: directly and its format may change at any time.
213 self.url_value_preprocessors: t.Dict[
214 ft.AppOrBlueprintKey,
215 t.List[ft.URLValuePreprocessorCallable],
216 ] = defaultdict(list)
218 #: A data structure of functions to call to modify the keyword
219 #: arguments when generating URLs, in the format
220 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
221 #: blueprint the functions are active for, or ``None`` for all
222 #: requests.
223 #:
224 #: To register a function, use the :meth:`url_defaults`
225 #: decorator.
226 #:
227 #: This data structure is internal. It should not be modified
228 #: directly and its format may change at any time.
229 self.url_default_functions: t.Dict[
230 ft.AppOrBlueprintKey, t.List[ft.URLDefaultCallable]
231 ] = defaultdict(list)
233 def __repr__(self) -> str:
234 return f"<{type(self).__name__} {self.name!r}>"
236 def _is_setup_finished(self) -> bool:
237 raise NotImplementedError
239 @property
240 def static_folder(self) -> t.Optional[str]:
241 """The absolute path to the configured static folder. ``None``
242 if no static folder is set.
243 """
244 if self._static_folder is not None:
245 return os.path.join(self.root_path, self._static_folder)
246 else:
247 return None
249 @static_folder.setter
250 def static_folder(self, value: t.Optional[t.Union[str, os.PathLike]]) -> None:
251 if value is not None:
252 value = os.fspath(value).rstrip(r"\/")
254 self._static_folder = value
256 @property
257 def has_static_folder(self) -> bool:
258 """``True`` if :attr:`static_folder` is set.
260 .. versionadded:: 0.5
261 """
262 return self.static_folder is not None
264 @property
265 def static_url_path(self) -> t.Optional[str]:
266 """The URL prefix that the static route will be accessible from.
268 If it was not configured during init, it is derived from
269 :attr:`static_folder`.
270 """
271 if self._static_url_path is not None:
272 return self._static_url_path
274 if self.static_folder is not None:
275 basename = os.path.basename(self.static_folder)
276 return f"/{basename}".rstrip("/")
278 return None
280 @static_url_path.setter
281 def static_url_path(self, value: t.Optional[str]) -> None:
282 if value is not None:
283 value = value.rstrip("/")
285 self._static_url_path = value
287 def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]:
288 """Used by :func:`send_file` to determine the ``max_age`` cache
289 value for a given file path if it wasn't passed.
291 By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
292 the configuration of :data:`~flask.current_app`. This defaults
293 to ``None``, which tells the browser to use conditional requests
294 instead of a timed cache, which is usually preferable.
296 .. versionchanged:: 2.0
297 The default configuration is ``None`` instead of 12 hours.
299 .. versionadded:: 0.9
300 """
301 value = current_app.send_file_max_age_default
303 if value is None:
304 return None
306 return int(value.total_seconds())
308 def send_static_file(self, filename: str) -> "Response":
309 """The view function used to serve files from
310 :attr:`static_folder`. A route is automatically registered for
311 this view at :attr:`static_url_path` if :attr:`static_folder` is
312 set.
314 .. versionadded:: 0.5
315 """
316 if not self.has_static_folder:
317 raise RuntimeError("'static_folder' must be set to serve static_files.")
319 # send_file only knows to call get_send_file_max_age on the app,
320 # call it here so it works for blueprints too.
321 max_age = self.get_send_file_max_age(filename)
322 return send_from_directory(
323 t.cast(str, self.static_folder), filename, max_age=max_age
324 )
326 @locked_cached_property
327 def jinja_loader(self) -> t.Optional[FileSystemLoader]:
328 """The Jinja loader for this object's templates. By default this
329 is a class :class:`jinja2.loaders.FileSystemLoader` to
330 :attr:`template_folder` if it is set.
332 .. versionadded:: 0.5
333 """
334 if self.template_folder is not None:
335 return FileSystemLoader(os.path.join(self.root_path, self.template_folder))
336 else:
337 return None
339 def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
340 """Open a resource file relative to :attr:`root_path` for
341 reading.
343 For example, if the file ``schema.sql`` is next to the file
344 ``app.py`` where the ``Flask`` app is defined, it can be opened
345 with:
347 .. code-block:: python
349 with app.open_resource("schema.sql") as f:
350 conn.executescript(f.read())
352 :param resource: Path to the resource relative to
353 :attr:`root_path`.
354 :param mode: Open the file in this mode. Only reading is
355 supported, valid values are "r" (or "rt") and "rb".
356 """
357 if mode not in {"r", "rt", "rb"}:
358 raise ValueError("Resources can only be opened for reading.")
360 return open(os.path.join(self.root_path, resource), mode)
362 def _method_route(
363 self,
364 method: str,
365 rule: str,
366 options: dict,
367 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
368 if "methods" in options:
369 raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
371 return self.route(rule, methods=[method], **options)
373 def get(
374 self, rule: str, **options: t.Any
375 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
376 """Shortcut for :meth:`route` with ``methods=["GET"]``.
378 .. versionadded:: 2.0
379 """
380 return self._method_route("GET", rule, options)
382 def post(
383 self, rule: str, **options: t.Any
384 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
385 """Shortcut for :meth:`route` with ``methods=["POST"]``.
387 .. versionadded:: 2.0
388 """
389 return self._method_route("POST", rule, options)
391 def put(
392 self, rule: str, **options: t.Any
393 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
394 """Shortcut for :meth:`route` with ``methods=["PUT"]``.
396 .. versionadded:: 2.0
397 """
398 return self._method_route("PUT", rule, options)
400 def delete(
401 self, rule: str, **options: t.Any
402 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
403 """Shortcut for :meth:`route` with ``methods=["DELETE"]``.
405 .. versionadded:: 2.0
406 """
407 return self._method_route("DELETE", rule, options)
409 def patch(
410 self, rule: str, **options: t.Any
411 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
412 """Shortcut for :meth:`route` with ``methods=["PATCH"]``.
414 .. versionadded:: 2.0
415 """
416 return self._method_route("PATCH", rule, options)
418 def route(
419 self, rule: str, **options: t.Any
420 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
421 """Decorate a view function to register it with the given URL
422 rule and options. Calls :meth:`add_url_rule`, which has more
423 details about the implementation.
425 .. code-block:: python
427 @app.route("/")
428 def index():
429 return "Hello, World!"
431 See :ref:`url-route-registrations`.
433 The endpoint name for the route defaults to the name of the view
434 function if the ``endpoint`` parameter isn't passed.
436 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and
437 ``OPTIONS`` are added automatically.
439 :param rule: The URL rule string.
440 :param options: Extra options passed to the
441 :class:`~werkzeug.routing.Rule` object.
442 """
444 def decorator(f: ft.RouteDecorator) -> ft.RouteDecorator:
445 endpoint = options.pop("endpoint", None)
446 self.add_url_rule(rule, endpoint, f, **options)
447 return f
449 return decorator
451 @setupmethod
452 def add_url_rule(
453 self,
454 rule: str,
455 endpoint: t.Optional[str] = None,
456 view_func: t.Optional[ft.ViewCallable] = None,
457 provide_automatic_options: t.Optional[bool] = None,
458 **options: t.Any,
459 ) -> None:
460 """Register a rule for routing incoming requests and building
461 URLs. The :meth:`route` decorator is a shortcut to call this
462 with the ``view_func`` argument. These are equivalent:
464 .. code-block:: python
466 @app.route("/")
467 def index():
468 ...
470 .. code-block:: python
472 def index():
473 ...
475 app.add_url_rule("/", view_func=index)
477 See :ref:`url-route-registrations`.
479 The endpoint name for the route defaults to the name of the view
480 function if the ``endpoint`` parameter isn't passed. An error
481 will be raised if a function has already been registered for the
482 endpoint.
484 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is
485 always added automatically, and ``OPTIONS`` is added
486 automatically by default.
488 ``view_func`` does not necessarily need to be passed, but if the
489 rule should participate in routing an endpoint name must be
490 associated with a view function at some point with the
491 :meth:`endpoint` decorator.
493 .. code-block:: python
495 app.add_url_rule("/", endpoint="index")
497 @app.endpoint("index")
498 def index():
499 ...
501 If ``view_func`` has a ``required_methods`` attribute, those
502 methods are added to the passed and automatic methods. If it
503 has a ``provide_automatic_methods`` attribute, it is used as the
504 default if the parameter is not passed.
506 :param rule: The URL rule string.
507 :param endpoint: The endpoint name to associate with the rule
508 and view function. Used when routing and building URLs.
509 Defaults to ``view_func.__name__``.
510 :param view_func: The view function to associate with the
511 endpoint name.
512 :param provide_automatic_options: Add the ``OPTIONS`` method and
513 respond to ``OPTIONS`` requests automatically.
514 :param options: Extra options passed to the
515 :class:`~werkzeug.routing.Rule` object.
516 """
517 raise NotImplementedError
519 def endpoint(self, endpoint: str) -> t.Callable:
520 """Decorate a view function to register it for the given
521 endpoint. Used if a rule is added without a ``view_func`` with
522 :meth:`add_url_rule`.
524 .. code-block:: python
526 app.add_url_rule("/ex", endpoint="example")
528 @app.endpoint("example")
529 def example():
530 ...
532 :param endpoint: The endpoint name to associate with the view
533 function.
534 """
536 def decorator(f):
537 self.view_functions[endpoint] = f
538 return f
540 return decorator
542 @setupmethod
543 def before_request(self, f: ft.BeforeRequestCallable) -> ft.BeforeRequestCallable:
544 """Register a function to run before each request.
546 For example, this can be used to open a database connection, or
547 to load the logged in user from the session.
549 .. code-block:: python
551 @app.before_request
552 def load_user():
553 if "user_id" in session:
554 g.user = db.session.get(session["user_id"])
556 The function will be called without any arguments. If it returns
557 a non-``None`` value, the value is handled as if it was the
558 return value from the view, and further request handling is
559 stopped.
560 """
561 self.before_request_funcs.setdefault(None, []).append(f)
562 return f
564 @setupmethod
565 def after_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
566 """Register a function to run after each request to this object.
568 The function is called with the response object, and must return
569 a response object. This allows the functions to modify or
570 replace the response before it is sent.
572 If a function raises an exception, any remaining
573 ``after_request`` functions will not be called. Therefore, this
574 should not be used for actions that must execute, such as to
575 close resources. Use :meth:`teardown_request` for that.
576 """
577 self.after_request_funcs.setdefault(None, []).append(f)
578 return f
580 @setupmethod
581 def teardown_request(self, f: ft.TeardownCallable) -> ft.TeardownCallable:
582 """Register a function to be run at the end of each request,
583 regardless of whether there was an exception or not. These functions
584 are executed when the request context is popped, even if not an
585 actual request was performed.
587 Example::
589 ctx = app.test_request_context()
590 ctx.push()
591 ...
592 ctx.pop()
594 When ``ctx.pop()`` is executed in the above example, the teardown
595 functions are called just before the request context moves from the
596 stack of active contexts. This becomes relevant if you are using
597 such constructs in tests.
599 Teardown functions must avoid raising exceptions. If
600 they execute code that might fail they
601 will have to surround the execution of that code with try/except
602 statements and log any errors.
604 When a teardown function was called because of an exception it will
605 be passed an error object.
607 The return values of teardown functions are ignored.
609 .. admonition:: Debug Note
611 In debug mode Flask will not tear down a request on an exception
612 immediately. Instead it will keep it alive so that the interactive
613 debugger can still access it. This behavior can be controlled
614 by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable.
615 """
616 self.teardown_request_funcs.setdefault(None, []).append(f)
617 return f
619 @setupmethod
620 def context_processor(
621 self, f: ft.TemplateContextProcessorCallable
622 ) -> ft.TemplateContextProcessorCallable:
623 """Registers a template context processor function."""
624 self.template_context_processors[None].append(f)
625 return f
627 @setupmethod
628 def url_value_preprocessor(
629 self, f: ft.URLValuePreprocessorCallable
630 ) -> ft.URLValuePreprocessorCallable:
631 """Register a URL value preprocessor function for all view
632 functions in the application. These functions will be called before the
633 :meth:`before_request` functions.
635 The function can modify the values captured from the matched url before
636 they are passed to the view. For example, this can be used to pop a
637 common language code value and place it in ``g`` rather than pass it to
638 every view.
640 The function is passed the endpoint name and values dict. The return
641 value is ignored.
642 """
643 self.url_value_preprocessors[None].append(f)
644 return f
646 @setupmethod
647 def url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable:
648 """Callback function for URL defaults for all view functions of the
649 application. It's called with the endpoint and values and should
650 update the values passed in place.
651 """
652 self.url_default_functions[None].append(f)
653 return f
655 @setupmethod
656 def errorhandler(
657 self, code_or_exception: t.Union[t.Type[Exception], int]
658 ) -> t.Callable[[ft.ErrorHandlerDecorator], ft.ErrorHandlerDecorator]:
659 """Register a function to handle errors by code or exception class.
661 A decorator that is used to register a function given an
662 error code. Example::
664 @app.errorhandler(404)
665 def page_not_found(error):
666 return 'This page does not exist', 404
668 You can also register handlers for arbitrary exceptions::
670 @app.errorhandler(DatabaseError)
671 def special_exception_handler(error):
672 return 'Database connection failed', 500
674 .. versionadded:: 0.7
675 Use :meth:`register_error_handler` instead of modifying
676 :attr:`error_handler_spec` directly, for application wide error
677 handlers.
679 .. versionadded:: 0.7
680 One can now additionally also register custom exception types
681 that do not necessarily have to be a subclass of the
682 :class:`~werkzeug.exceptions.HTTPException` class.
684 :param code_or_exception: the code as integer for the handler, or
685 an arbitrary exception
686 """
688 def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator:
689 self.register_error_handler(code_or_exception, f)
690 return f
692 return decorator
694 @setupmethod
695 def register_error_handler(
696 self,
697 code_or_exception: t.Union[t.Type[Exception], int],
698 f: ft.ErrorHandlerCallable,
699 ) -> None:
700 """Alternative error attach function to the :meth:`errorhandler`
701 decorator that is more straightforward to use for non decorator
702 usage.
704 .. versionadded:: 0.7
705 """
706 if isinstance(code_or_exception, HTTPException): # old broken behavior
707 raise ValueError(
708 "Tried to register a handler for an exception instance"
709 f" {code_or_exception!r}. Handlers can only be"
710 " registered for exception classes or HTTP error codes."
711 )
713 try:
714 exc_class, code = self._get_exc_class_and_code(code_or_exception)
715 except KeyError:
716 raise KeyError(
717 f"'{code_or_exception}' is not a recognized HTTP error"
718 " code. Use a subclass of HTTPException with that code"
719 " instead."
720 ) from None
722 self.error_handler_spec[None][code][exc_class] = f
724 @staticmethod
725 def _get_exc_class_and_code(
726 exc_class_or_code: t.Union[t.Type[Exception], int]
727 ) -> t.Tuple[t.Type[Exception], t.Optional[int]]:
728 """Get the exception class being handled. For HTTP status codes
729 or ``HTTPException`` subclasses, return both the exception and
730 status code.
732 :param exc_class_or_code: Any exception class, or an HTTP status
733 code as an integer.
734 """
735 exc_class: t.Type[Exception]
736 if isinstance(exc_class_or_code, int):
737 exc_class = default_exceptions[exc_class_or_code]
738 else:
739 exc_class = exc_class_or_code
741 assert issubclass(
742 exc_class, Exception
743 ), "Custom exceptions must be subclasses of Exception."
745 if issubclass(exc_class, HTTPException):
746 return exc_class, exc_class.code
747 else:
748 return exc_class, None
751def _endpoint_from_view_func(view_func: t.Callable) -> str:
752 """Internal helper that returns the default endpoint for a given
753 function. This always is the function name.
754 """
755 assert view_func is not None, "expected view func if endpoint is not provided."
756 return view_func.__name__
759def _matching_loader_thinks_module_is_package(loader, mod_name):
760 """Attempt to figure out if the given name is a package or a module.
762 :param: loader: The loader that handled the name.
763 :param mod_name: The name of the package or module.
764 """
765 # Use loader.is_package if it's available.
766 if hasattr(loader, "is_package"):
767 return loader.is_package(mod_name)
769 cls = type(loader)
771 # NamespaceLoader doesn't implement is_package, but all names it
772 # loads must be packages.
773 if cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
774 return True
776 # Otherwise we need to fail with an error that explains what went
777 # wrong.
778 raise AttributeError(
779 f"'{cls.__name__}.is_package()' must be implemented for PEP 302"
780 f" import hooks."
781 )
784def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
785 # Path.is_relative_to doesn't exist until Python 3.9
786 try:
787 path.relative_to(base)
788 return True
789 except ValueError:
790 return False
793def _find_package_path(import_name):
794 """Find the path that contains the package or module."""
795 root_mod_name, _, _ = import_name.partition(".")
797 try:
798 root_spec = importlib.util.find_spec(root_mod_name)
800 if root_spec is None:
801 raise ValueError("not found")
802 # ImportError: the machinery told us it does not exist
803 # ValueError:
804 # - the module name was invalid
805 # - the module name is __main__
806 # - *we* raised `ValueError` due to `root_spec` being `None`
807 except (ImportError, ValueError):
808 pass # handled below
809 else:
810 # namespace package
811 if root_spec.origin in {"namespace", None}:
812 package_spec = importlib.util.find_spec(import_name)
813 if package_spec is not None and package_spec.submodule_search_locations:
814 # Pick the path in the namespace that contains the submodule.
815 package_path = pathlib.Path(
816 os.path.commonpath(package_spec.submodule_search_locations)
817 )
818 search_locations = (
819 location
820 for location in root_spec.submodule_search_locations
821 if _path_is_relative_to(package_path, location)
822 )
823 else:
824 # Pick the first path.
825 search_locations = iter(root_spec.submodule_search_locations)
826 return os.path.dirname(next(search_locations))
827 # a package (with __init__.py)
828 elif root_spec.submodule_search_locations:
829 return os.path.dirname(os.path.dirname(root_spec.origin))
830 # just a normal module
831 else:
832 return os.path.dirname(root_spec.origin)
834 # we were unable to find the `package_path` using PEP 451 loaders
835 loader = pkgutil.get_loader(root_mod_name)
837 if loader is None or root_mod_name == "__main__":
838 # import name is not found, or interactive/main module
839 return os.getcwd()
841 if hasattr(loader, "get_filename"):
842 filename = loader.get_filename(root_mod_name)
843 elif hasattr(loader, "archive"):
844 # zipimporter's loader.archive points to the .egg or .zip file.
845 filename = loader.archive
846 else:
847 # At least one loader is missing both get_filename and archive:
848 # Google App Engine's HardenedModulesHook, use __file__.
849 filename = importlib.import_module(root_mod_name).__file__
851 package_path = os.path.abspath(os.path.dirname(filename))
853 # If the imported name is a package, filename is currently pointing
854 # to the root of the package, need to get the current directory.
855 if _matching_loader_thinks_module_is_package(loader, root_mod_name):
856 package_path = os.path.dirname(package_path)
858 return package_path
861def find_package(import_name: str):
862 """Find the prefix that a package is installed under, and the path
863 that it would be imported from.
865 The prefix is the directory containing the standard directory
866 hierarchy (lib, bin, etc.). If the package is not installed to the
867 system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
868 ``None`` is returned.
870 The path is the entry in :attr:`sys.path` that contains the package
871 for import. If the package is not installed, it's assumed that the
872 package was imported from the current working directory.
873 """
874 package_path = _find_package_path(import_name)
875 py_prefix = os.path.abspath(sys.prefix)
877 # installed to the system
878 if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix):
879 return py_prefix, package_path
881 site_parent, site_folder = os.path.split(package_path)
883 # installed to a virtualenv
884 if site_folder.lower() == "site-packages":
885 parent, folder = os.path.split(site_parent)
887 # Windows (prefix/lib/site-packages)
888 if folder.lower() == "lib":
889 return parent, package_path
891 # Unix (prefix/lib/pythonX.Y/site-packages)
892 if os.path.basename(parent).lower() == "lib":
893 return os.path.dirname(parent), package_path
895 # something else (prefix/site-packages)
896 return site_parent, package_path
898 # not installed
899 return None, package_path