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