Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/sansio/scaffold.py: 42%
214 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 06:29 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 06:29 +0000
1from __future__ import annotations
3import importlib.util
4import os
5import pathlib
6import sys
7import typing as t
8from collections import defaultdict
9from functools import update_wrapper
11import click
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 ..helpers import get_root_path
20from ..templating import _default_template_ctx_processor
22# a singleton sentinel value for parameter defaults
23_sentinel = object()
25F = t.TypeVar("F", bound=t.Callable[..., t.Any])
26T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any])
27T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
28T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
29T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
30T_template_context_processor = t.TypeVar(
31 "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
32)
33T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
34T_url_value_preprocessor = t.TypeVar(
35 "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
36)
37T_route = t.TypeVar("T_route", bound=ft.RouteCallable)
40def setupmethod(f: F) -> F:
41 f_name = f.__name__
43 def wrapper_func(self: Scaffold, *args: t.Any, **kwargs: t.Any) -> t.Any:
44 self._check_setup_finished(f_name)
45 return f(self, *args, **kwargs)
47 return t.cast(F, update_wrapper(wrapper_func, f))
50class Scaffold:
51 """Common behavior shared between :class:`~flask.Flask` and
52 :class:`~flask.blueprints.Blueprint`.
54 :param import_name: The import name of the module where this object
55 is defined. Usually :attr:`__name__` should be used.
56 :param static_folder: Path to a folder of static files to serve.
57 If this is set, a static route will be added.
58 :param static_url_path: URL prefix for the static route.
59 :param template_folder: Path to a folder containing template files.
60 for rendering. If this is set, a Jinja loader will be added.
61 :param root_path: The path that static, template, and resource files
62 are relative to. Typically not set, it is discovered based on
63 the ``import_name``.
65 .. versionadded:: 2.0
66 """
68 name: str
69 _static_folder: str | None = None
70 _static_url_path: str | None = None
72 def __init__(
73 self,
74 import_name: str,
75 static_folder: str | os.PathLike[str] | None = None,
76 static_url_path: str | None = None,
77 template_folder: str | os.PathLike[str] | None = None,
78 root_path: str | None = None,
79 ):
80 #: The name of the package or module that this object belongs
81 #: to. Do not change this once it is set by the constructor.
82 self.import_name = import_name
84 self.static_folder = static_folder # type: ignore
85 self.static_url_path = static_url_path
87 #: The path to the templates folder, relative to
88 #: :attr:`root_path`, to add to the template loader. ``None`` if
89 #: templates should not be added.
90 self.template_folder = template_folder
92 if root_path is None:
93 root_path = get_root_path(self.import_name)
95 #: Absolute path to the package on the filesystem. Used to look
96 #: up resources contained in the package.
97 self.root_path = root_path
99 #: The Click command group for registering CLI commands for this
100 #: object. The commands are available from the ``flask`` command
101 #: once the application has been discovered and blueprints have
102 #: been registered.
103 self.cli: click.Group = AppGroup()
105 #: A dictionary mapping endpoint names to view functions.
106 #:
107 #: To register a view function, use the :meth:`route` decorator.
108 #:
109 #: This data structure is internal. It should not be modified
110 #: directly and its format may change at any time.
111 self.view_functions: dict[str, ft.RouteCallable] = {}
113 #: A data structure of registered error handlers, in the format
114 #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is
115 #: the name of a blueprint the handlers are active for, or
116 #: ``None`` for all requests. The ``code`` key is the HTTP
117 #: status code for ``HTTPException``, or ``None`` for
118 #: other exceptions. The innermost dictionary maps exception
119 #: classes to handler functions.
120 #:
121 #: To register an error handler, use the :meth:`errorhandler`
122 #: decorator.
123 #:
124 #: This data structure is internal. It should not be modified
125 #: directly and its format may change at any time.
126 self.error_handler_spec: dict[
127 ft.AppOrBlueprintKey,
128 dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]],
129 ] = defaultdict(lambda: defaultdict(dict))
131 #: A data structure of functions to call at the beginning of
132 #: each request, in the format ``{scope: [functions]}``. The
133 #: ``scope`` key is the name of a blueprint the functions are
134 #: active for, or ``None`` for all requests.
135 #:
136 #: To register a function, use the :meth:`before_request`
137 #: decorator.
138 #:
139 #: This data structure is internal. It should not be modified
140 #: directly and its format may change at any time.
141 self.before_request_funcs: dict[
142 ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable]
143 ] = defaultdict(list)
145 #: A data structure of functions to call at the end of each
146 #: request, in the format ``{scope: [functions]}``. The
147 #: ``scope`` key is the name of a blueprint the functions are
148 #: active for, or ``None`` for all requests.
149 #:
150 #: To register a function, use the :meth:`after_request`
151 #: decorator.
152 #:
153 #: This data structure is internal. It should not be modified
154 #: directly and its format may change at any time.
155 self.after_request_funcs: dict[
156 ft.AppOrBlueprintKey, list[ft.AfterRequestCallable[t.Any]]
157 ] = defaultdict(list)
159 #: A data structure of functions to call at the end of each
160 #: request even if an exception is raised, in the format
161 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
162 #: blueprint the functions are active for, or ``None`` for all
163 #: requests.
164 #:
165 #: To register a function, use the :meth:`teardown_request`
166 #: decorator.
167 #:
168 #: This data structure is internal. It should not be modified
169 #: directly and its format may change at any time.
170 self.teardown_request_funcs: dict[
171 ft.AppOrBlueprintKey, list[ft.TeardownCallable]
172 ] = defaultdict(list)
174 #: A data structure of functions to call to pass extra context
175 #: values when rendering templates, in the format
176 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
177 #: blueprint the functions are active for, or ``None`` for all
178 #: requests.
179 #:
180 #: To register a function, use the :meth:`context_processor`
181 #: decorator.
182 #:
183 #: This data structure is internal. It should not be modified
184 #: directly and its format may change at any time.
185 self.template_context_processors: dict[
186 ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable]
187 ] = defaultdict(list, {None: [_default_template_ctx_processor]})
189 #: A data structure of functions to call to modify the keyword
190 #: arguments passed to the view function, in the format
191 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
192 #: blueprint the functions are active for, or ``None`` for all
193 #: requests.
194 #:
195 #: To register a function, use the
196 #: :meth:`url_value_preprocessor` decorator.
197 #:
198 #: This data structure is internal. It should not be modified
199 #: directly and its format may change at any time.
200 self.url_value_preprocessors: dict[
201 ft.AppOrBlueprintKey,
202 list[ft.URLValuePreprocessorCallable],
203 ] = defaultdict(list)
205 #: A data structure of functions to call to modify the keyword
206 #: arguments when generating URLs, in the format
207 #: ``{scope: [functions]}``. The ``scope`` key is the name of a
208 #: blueprint the functions are active for, or ``None`` for all
209 #: requests.
210 #:
211 #: To register a function, use the :meth:`url_defaults`
212 #: decorator.
213 #:
214 #: This data structure is internal. It should not be modified
215 #: directly and its format may change at any time.
216 self.url_default_functions: dict[
217 ft.AppOrBlueprintKey, list[ft.URLDefaultCallable]
218 ] = defaultdict(list)
220 def __repr__(self) -> str:
221 return f"<{type(self).__name__} {self.name!r}>"
223 def _check_setup_finished(self, f_name: str) -> None:
224 raise NotImplementedError
226 @property
227 def static_folder(self) -> str | None:
228 """The absolute path to the configured static folder. ``None``
229 if no static folder is set.
230 """
231 if self._static_folder is not None:
232 return os.path.join(self.root_path, self._static_folder)
233 else:
234 return None
236 @static_folder.setter
237 def static_folder(self, value: str | os.PathLike[str] | None) -> None:
238 if value is not None:
239 value = os.fspath(value).rstrip(r"\/")
241 self._static_folder = value
243 @property
244 def has_static_folder(self) -> bool:
245 """``True`` if :attr:`static_folder` is set.
247 .. versionadded:: 0.5
248 """
249 return self.static_folder is not None
251 @property
252 def static_url_path(self) -> str | None:
253 """The URL prefix that the static route will be accessible from.
255 If it was not configured during init, it is derived from
256 :attr:`static_folder`.
257 """
258 if self._static_url_path is not None:
259 return self._static_url_path
261 if self.static_folder is not None:
262 basename = os.path.basename(self.static_folder)
263 return f"/{basename}".rstrip("/")
265 return None
267 @static_url_path.setter
268 def static_url_path(self, value: str | None) -> None:
269 if value is not None:
270 value = value.rstrip("/")
272 self._static_url_path = value
274 @cached_property
275 def jinja_loader(self) -> FileSystemLoader | None:
276 """The Jinja loader for this object's templates. By default this
277 is a class :class:`jinja2.loaders.FileSystemLoader` to
278 :attr:`template_folder` if it is set.
280 .. versionadded:: 0.5
281 """
282 if self.template_folder is not None:
283 return FileSystemLoader(os.path.join(self.root_path, self.template_folder))
284 else:
285 return None
287 def _method_route(
288 self,
289 method: str,
290 rule: str,
291 options: dict[str, t.Any],
292 ) -> t.Callable[[T_route], T_route]:
293 if "methods" in options:
294 raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
296 return self.route(rule, methods=[method], **options)
298 @setupmethod
299 def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
300 """Shortcut for :meth:`route` with ``methods=["GET"]``.
302 .. versionadded:: 2.0
303 """
304 return self._method_route("GET", rule, options)
306 @setupmethod
307 def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
308 """Shortcut for :meth:`route` with ``methods=["POST"]``.
310 .. versionadded:: 2.0
311 """
312 return self._method_route("POST", rule, options)
314 @setupmethod
315 def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
316 """Shortcut for :meth:`route` with ``methods=["PUT"]``.
318 .. versionadded:: 2.0
319 """
320 return self._method_route("PUT", rule, options)
322 @setupmethod
323 def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
324 """Shortcut for :meth:`route` with ``methods=["DELETE"]``.
326 .. versionadded:: 2.0
327 """
328 return self._method_route("DELETE", rule, options)
330 @setupmethod
331 def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
332 """Shortcut for :meth:`route` with ``methods=["PATCH"]``.
334 .. versionadded:: 2.0
335 """
336 return self._method_route("PATCH", rule, options)
338 @setupmethod
339 def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
340 """Decorate a view function to register it with the given URL
341 rule and options. Calls :meth:`add_url_rule`, which has more
342 details about the implementation.
344 .. code-block:: python
346 @app.route("/")
347 def index():
348 return "Hello, World!"
350 See :ref:`url-route-registrations`.
352 The endpoint name for the route defaults to the name of the view
353 function if the ``endpoint`` parameter isn't passed.
355 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and
356 ``OPTIONS`` are added automatically.
358 :param rule: The URL rule string.
359 :param options: Extra options passed to the
360 :class:`~werkzeug.routing.Rule` object.
361 """
363 def decorator(f: T_route) -> T_route:
364 endpoint = options.pop("endpoint", None)
365 self.add_url_rule(rule, endpoint, f, **options)
366 return f
368 return decorator
370 @setupmethod
371 def add_url_rule(
372 self,
373 rule: str,
374 endpoint: str | None = None,
375 view_func: ft.RouteCallable | None = None,
376 provide_automatic_options: bool | None = None,
377 **options: t.Any,
378 ) -> None:
379 """Register a rule for routing incoming requests and building
380 URLs. The :meth:`route` decorator is a shortcut to call this
381 with the ``view_func`` argument. These are equivalent:
383 .. code-block:: python
385 @app.route("/")
386 def index():
387 ...
389 .. code-block:: python
391 def index():
392 ...
394 app.add_url_rule("/", view_func=index)
396 See :ref:`url-route-registrations`.
398 The endpoint name for the route defaults to the name of the view
399 function if the ``endpoint`` parameter isn't passed. An error
400 will be raised if a function has already been registered for the
401 endpoint.
403 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is
404 always added automatically, and ``OPTIONS`` is added
405 automatically by default.
407 ``view_func`` does not necessarily need to be passed, but if the
408 rule should participate in routing an endpoint name must be
409 associated with a view function at some point with the
410 :meth:`endpoint` decorator.
412 .. code-block:: python
414 app.add_url_rule("/", endpoint="index")
416 @app.endpoint("index")
417 def index():
418 ...
420 If ``view_func`` has a ``required_methods`` attribute, those
421 methods are added to the passed and automatic methods. If it
422 has a ``provide_automatic_methods`` attribute, it is used as the
423 default if the parameter is not passed.
425 :param rule: The URL rule string.
426 :param endpoint: The endpoint name to associate with the rule
427 and view function. Used when routing and building URLs.
428 Defaults to ``view_func.__name__``.
429 :param view_func: The view function to associate with the
430 endpoint name.
431 :param provide_automatic_options: Add the ``OPTIONS`` method and
432 respond to ``OPTIONS`` requests automatically.
433 :param options: Extra options passed to the
434 :class:`~werkzeug.routing.Rule` object.
435 """
436 raise NotImplementedError
438 @setupmethod
439 def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
440 """Decorate a view function to register it for the given
441 endpoint. Used if a rule is added without a ``view_func`` with
442 :meth:`add_url_rule`.
444 .. code-block:: python
446 app.add_url_rule("/ex", endpoint="example")
448 @app.endpoint("example")
449 def example():
450 ...
452 :param endpoint: The endpoint name to associate with the view
453 function.
454 """
456 def decorator(f: F) -> F:
457 self.view_functions[endpoint] = f
458 return f
460 return decorator
462 @setupmethod
463 def before_request(self, f: T_before_request) -> T_before_request:
464 """Register a function to run before each request.
466 For example, this can be used to open a database connection, or
467 to load the logged in user from the session.
469 .. code-block:: python
471 @app.before_request
472 def load_user():
473 if "user_id" in session:
474 g.user = db.session.get(session["user_id"])
476 The function will be called without any arguments. If it returns
477 a non-``None`` value, the value is handled as if it was the
478 return value from the view, and further request handling is
479 stopped.
481 This is available on both app and blueprint objects. When used on an app, this
482 executes before every request. When used on a blueprint, this executes before
483 every request that the blueprint handles. To register with a blueprint and
484 execute before every request, use :meth:`.Blueprint.before_app_request`.
485 """
486 self.before_request_funcs.setdefault(None, []).append(f)
487 return f
489 @setupmethod
490 def after_request(self, f: T_after_request) -> T_after_request:
491 """Register a function to run after each request to this object.
493 The function is called with the response object, and must return
494 a response object. This allows the functions to modify or
495 replace the response before it is sent.
497 If a function raises an exception, any remaining
498 ``after_request`` functions will not be called. Therefore, this
499 should not be used for actions that must execute, such as to
500 close resources. Use :meth:`teardown_request` for that.
502 This is available on both app and blueprint objects. When used on an app, this
503 executes after every request. When used on a blueprint, this executes after
504 every request that the blueprint handles. To register with a blueprint and
505 execute after every request, use :meth:`.Blueprint.after_app_request`.
506 """
507 self.after_request_funcs.setdefault(None, []).append(f)
508 return f
510 @setupmethod
511 def teardown_request(self, f: T_teardown) -> T_teardown:
512 """Register a function to be called when the request context is
513 popped. Typically this happens at the end of each request, but
514 contexts may be pushed manually as well during testing.
516 .. code-block:: python
518 with app.test_request_context():
519 ...
521 When the ``with`` block exits (or ``ctx.pop()`` is called), the
522 teardown functions are called just before the request context is
523 made inactive.
525 When a teardown function was called because of an unhandled
526 exception it will be passed an error object. If an
527 :meth:`errorhandler` is registered, it will handle the exception
528 and the teardown will not receive it.
530 Teardown functions must avoid raising exceptions. If they
531 execute code that might fail they must surround that code with a
532 ``try``/``except`` block and log any errors.
534 The return values of teardown functions are ignored.
536 This is available on both app and blueprint objects. When used on an app, this
537 executes after every request. When used on a blueprint, this executes after
538 every request that the blueprint handles. To register with a blueprint and
539 execute after every request, use :meth:`.Blueprint.teardown_app_request`.
540 """
541 self.teardown_request_funcs.setdefault(None, []).append(f)
542 return f
544 @setupmethod
545 def context_processor(
546 self,
547 f: T_template_context_processor,
548 ) -> T_template_context_processor:
549 """Registers a template context processor function. These functions run before
550 rendering a template. The keys of the returned dict are added as variables
551 available in the template.
553 This is available on both app and blueprint objects. When used on an app, this
554 is called for every rendered template. When used on a blueprint, this is called
555 for templates rendered from the blueprint's views. To register with a blueprint
556 and affect every template, use :meth:`.Blueprint.app_context_processor`.
557 """
558 self.template_context_processors[None].append(f)
559 return f
561 @setupmethod
562 def url_value_preprocessor(
563 self,
564 f: T_url_value_preprocessor,
565 ) -> T_url_value_preprocessor:
566 """Register a URL value preprocessor function for all view
567 functions in the application. These functions will be called before the
568 :meth:`before_request` functions.
570 The function can modify the values captured from the matched url before
571 they are passed to the view. For example, this can be used to pop a
572 common language code value and place it in ``g`` rather than pass it to
573 every view.
575 The function is passed the endpoint name and values dict. The return
576 value is ignored.
578 This is available on both app and blueprint objects. When used on an app, this
579 is called for every request. When used on a blueprint, this is called for
580 requests that the blueprint handles. To register with a blueprint and affect
581 every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
582 """
583 self.url_value_preprocessors[None].append(f)
584 return f
586 @setupmethod
587 def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
588 """Callback function for URL defaults for all view functions of the
589 application. It's called with the endpoint and values and should
590 update the values passed in place.
592 This is available on both app and blueprint objects. When used on an app, this
593 is called for every request. When used on a blueprint, this is called for
594 requests that the blueprint handles. To register with a blueprint and affect
595 every request, use :meth:`.Blueprint.app_url_defaults`.
596 """
597 self.url_default_functions[None].append(f)
598 return f
600 @setupmethod
601 def errorhandler(
602 self, code_or_exception: type[Exception] | int
603 ) -> t.Callable[[T_error_handler], T_error_handler]:
604 """Register a function to handle errors by code or exception class.
606 A decorator that is used to register a function given an
607 error code. Example::
609 @app.errorhandler(404)
610 def page_not_found(error):
611 return 'This page does not exist', 404
613 You can also register handlers for arbitrary exceptions::
615 @app.errorhandler(DatabaseError)
616 def special_exception_handler(error):
617 return 'Database connection failed', 500
619 This is available on both app and blueprint objects. When used on an app, this
620 can handle errors from every request. When used on a blueprint, this can handle
621 errors from requests that the blueprint handles. To register with a blueprint
622 and affect every request, use :meth:`.Blueprint.app_errorhandler`.
624 .. versionadded:: 0.7
625 Use :meth:`register_error_handler` instead of modifying
626 :attr:`error_handler_spec` directly, for application wide error
627 handlers.
629 .. versionadded:: 0.7
630 One can now additionally also register custom exception types
631 that do not necessarily have to be a subclass of the
632 :class:`~werkzeug.exceptions.HTTPException` class.
634 :param code_or_exception: the code as integer for the handler, or
635 an arbitrary exception
636 """
638 def decorator(f: T_error_handler) -> T_error_handler:
639 self.register_error_handler(code_or_exception, f)
640 return f
642 return decorator
644 @setupmethod
645 def register_error_handler(
646 self,
647 code_or_exception: type[Exception] | int,
648 f: ft.ErrorHandlerCallable,
649 ) -> None:
650 """Alternative error attach function to the :meth:`errorhandler`
651 decorator that is more straightforward to use for non decorator
652 usage.
654 .. versionadded:: 0.7
655 """
656 exc_class, code = self._get_exc_class_and_code(code_or_exception)
657 self.error_handler_spec[None][code][exc_class] = f
659 @staticmethod
660 def _get_exc_class_and_code(
661 exc_class_or_code: type[Exception] | int,
662 ) -> tuple[type[Exception], int | None]:
663 """Get the exception class being handled. For HTTP status codes
664 or ``HTTPException`` subclasses, return both the exception and
665 status code.
667 :param exc_class_or_code: Any exception class, or an HTTP status
668 code as an integer.
669 """
670 exc_class: type[Exception]
672 if isinstance(exc_class_or_code, int):
673 try:
674 exc_class = default_exceptions[exc_class_or_code]
675 except KeyError:
676 raise ValueError(
677 f"'{exc_class_or_code}' is not a recognized HTTP"
678 " error code. Use a subclass of HTTPException with"
679 " that code instead."
680 ) from None
681 else:
682 exc_class = exc_class_or_code
684 if isinstance(exc_class, Exception):
685 raise TypeError(
686 f"{exc_class!r} is an instance, not a class. Handlers"
687 " can only be registered for Exception classes or HTTP"
688 " error codes."
689 )
691 if not issubclass(exc_class, Exception):
692 raise ValueError(
693 f"'{exc_class.__name__}' is not a subclass of Exception."
694 " Handlers can only be registered for Exception classes"
695 " or HTTP error codes."
696 )
698 if issubclass(exc_class, HTTPException):
699 return exc_class, exc_class.code
700 else:
701 return exc_class, None
704def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str:
705 """Internal helper that returns the default endpoint for a given
706 function. This always is the function name.
707 """
708 assert view_func is not None, "expected view func if endpoint is not provided."
709 return view_func.__name__
712def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
713 # Path.is_relative_to doesn't exist until Python 3.9
714 try:
715 path.relative_to(base)
716 return True
717 except ValueError:
718 return False
721def _find_package_path(import_name: str) -> str:
722 """Find the path that contains the package or module."""
723 root_mod_name, _, _ = import_name.partition(".")
725 try:
726 root_spec = importlib.util.find_spec(root_mod_name)
728 if root_spec is None:
729 raise ValueError("not found")
730 except (ImportError, ValueError):
731 # ImportError: the machinery told us it does not exist
732 # ValueError:
733 # - the module name was invalid
734 # - the module name is __main__
735 # - we raised `ValueError` due to `root_spec` being `None`
736 return os.getcwd()
738 if root_spec.submodule_search_locations:
739 if root_spec.origin is None or root_spec.origin == "namespace":
740 # namespace package
741 package_spec = importlib.util.find_spec(import_name)
743 if package_spec is not None and package_spec.submodule_search_locations:
744 # Pick the path in the namespace that contains the submodule.
745 package_path = pathlib.Path(
746 os.path.commonpath(package_spec.submodule_search_locations)
747 )
748 search_location = next(
749 location
750 for location in root_spec.submodule_search_locations
751 if _path_is_relative_to(package_path, location)
752 )
753 else:
754 # Pick the first path.
755 search_location = root_spec.submodule_search_locations[0]
757 return os.path.dirname(search_location)
758 else:
759 # package with __init__.py
760 return os.path.dirname(os.path.dirname(root_spec.origin))
761 else:
762 # module
763 return os.path.dirname(root_spec.origin) # type: ignore[type-var, return-value]
766def find_package(import_name: str) -> tuple[str | None, str]:
767 """Find the prefix that a package is installed under, and the path
768 that it would be imported from.
770 The prefix is the directory containing the standard directory
771 hierarchy (lib, bin, etc.). If the package is not installed to the
772 system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
773 ``None`` is returned.
775 The path is the entry in :attr:`sys.path` that contains the package
776 for import. If the package is not installed, it's assumed that the
777 package was imported from the current working directory.
778 """
779 package_path = _find_package_path(import_name)
780 py_prefix = os.path.abspath(sys.prefix)
782 # installed to the system
783 if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix):
784 return py_prefix, package_path
786 site_parent, site_folder = os.path.split(package_path)
788 # installed to a virtualenv
789 if site_folder.lower() == "site-packages":
790 parent, folder = os.path.split(site_parent)
792 # Windows (prefix/lib/site-packages)
793 if folder.lower() == "lib":
794 return parent, package_path
796 # Unix (prefix/lib/pythonX.Y/site-packages)
797 if os.path.basename(parent).lower() == "lib":
798 return os.path.dirname(parent), package_path
800 # something else (prefix/site-packages)
801 return site_parent, package_path
803 # not installed
804 return None, package_path