Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
scaffold.py793 linesDownload Raw Back to sansio
1from __future__ import annotations2 3import importlib.util4import os5import pathlib6import sys7import typing as t8from collections import defaultdict9from functools import update_wrapper10 11from jinja2 import BaseLoader12from jinja2 import FileSystemLoader13from werkzeug.exceptions import default_exceptions14from werkzeug.exceptions import HTTPException15from werkzeug.utils import cached_property16 17from .. import typing as ft18from ..helpers import get_root_path19from ..templating import _default_template_ctx_processor20 21if t.TYPE_CHECKING:  # pragma: no cover22    from click import Group23 24# a singleton sentinel value for parameter defaults25_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.TemplateContextProcessorCallable34)35T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)36T_url_value_preprocessor = t.TypeVar(37    "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable38)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` and54    :class:`~flask.blueprints.Blueprint`.55 56    :param import_name: The import name of the module where this object57        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 files64        are relative to. Typically not set, it is discovered based on65        the ``import_name``.66 67    .. versionadded:: 2.068    """69 70    cli: Group71    name: str72    _static_folder: str | None = None73    _static_url_path: str | None = None74 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 belongs84        #: to. Do not change this once it is set by the constructor.85        self.import_name = import_name86 87        self.static_folder = static_folder88        self.static_url_path = static_url_path89 90        #: The path to the templates folder, relative to91        #: :attr:`root_path`, to add to the template loader. ``None`` if92        #: templates should not be added.93        self.template_folder = template_folder94 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 look99        #: up resources contained in the package.100        self.root_path = root_path101 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 modified107        #: 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 format111        #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is112        #: the name of a blueprint the handlers are active for, or113        #: ``None`` for all requests. The ``code`` key is the HTTP114        #: status code for ``HTTPException``, or ``None`` for115        #: other exceptions. The innermost dictionary maps exception116        #: 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 modified122        #: 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 of129        #: each request, in the format ``{scope: [functions]}``. The130        #: ``scope`` key is the name of a blueprint the functions are131        #: 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 modified137        #: 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 each143        #: request, in the format ``{scope: [functions]}``. The144        #: ``scope`` key is the name of a blueprint the functions are145        #: 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 modified151        #: 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 each157        #: request even if an exception is raised, in the format158        #: ``{scope: [functions]}``. The ``scope`` key is the name of a159        #: blueprint the functions are active for, or ``None`` for all160        #: 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 modified166        #: 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 context172        #: values when rendering templates, in the format173        #: ``{scope: [functions]}``. The ``scope`` key is the name of a174        #: blueprint the functions are active for, or ``None`` for all175        #: 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 modified181        #: 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 keyword187        #: arguments passed to the view function, in the format188        #: ``{scope: [functions]}``. The ``scope`` key is the name of a189        #: blueprint the functions are active for, or ``None`` for all190        #: requests.191        #:192        #: To register a function, use the193        #: :meth:`url_value_preprocessor` decorator.194        #:195        #: This data structure is internal. It should not be modified196        #: 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 keyword203        #: arguments when generating URLs, in the format204        #: ``{scope: [functions]}``. The ``scope`` key is the name of a205        #: blueprint the functions are active for, or ``None`` for all206        #: 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 modified212        #: 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 NotImplementedError222 223    @property224    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 None232 233    @static_folder.setter234    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 = value239 240    @property241    def has_static_folder(self) -> bool:242        """``True`` if :attr:`static_folder` is set.243 244        .. versionadded:: 0.5245        """246        return self.static_folder is not None247 248    @property249    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 from253        :attr:`static_folder`.254        """255        if self._static_url_path is not None:256            return self._static_url_path257 258        if self.static_folder is not None:259            basename = os.path.basename(self.static_folder)260            return f"/{basename}".rstrip("/")261 262        return None263 264    @static_url_path.setter265    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 = value270 271    @cached_property272    def jinja_loader(self) -> BaseLoader | None:273        """The Jinja loader for this object's templates. By default this274        is a class :class:`jinja2.loaders.FileSystemLoader` to275        :attr:`template_folder` if it is set.276 277        .. versionadded:: 0.5278        """279        if self.template_folder is not None:280            return FileSystemLoader(os.path.join(self.root_path, self.template_folder))281        else:282            return None283 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    @setupmethod296    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.0300        """301        return self._method_route("GET", rule, options)302 303    @setupmethod304    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.0308        """309        return self._method_route("POST", rule, options)310 311    @setupmethod312    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.0316        """317        return self._method_route("PUT", rule, options)318 319    @setupmethod320    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.0324        """325        return self._method_route("DELETE", rule, options)326 327    @setupmethod328    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.0332        """333        return self._method_route("PATCH", rule, options)334 335    @setupmethod336    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 URL338        rule and options. Calls :meth:`add_url_rule`, which has more339        details about the implementation.340 341        .. code-block:: python342 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 view350        function if the ``endpoint`` parameter isn't passed.351 352        The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and353        ``OPTIONS`` are added automatically.354 355        :param rule: The URL rule string.356        :param options: Extra options passed to the357            :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 f364 365        return decorator366 367    @setupmethod368    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 building377        URLs. The :meth:`route` decorator is a shortcut to call this378        with the ``view_func`` argument. These are equivalent:379 380        .. code-block:: python381 382            @app.route("/")383            def index():384                ...385 386        .. code-block:: python387 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 view396        function if the ``endpoint`` parameter isn't passed. An error397        will be raised if a function has already been registered for the398        endpoint.399 400        The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is401        always added automatically, and ``OPTIONS`` is added402        automatically by default.403 404        ``view_func`` does not necessarily need to be passed, but if the405        rule should participate in routing an endpoint name must be406        associated with a view function at some point with the407        :meth:`endpoint` decorator.408 409        .. code-block:: python410 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, those418        methods are added to the passed and automatic methods. If it419        has a ``provide_automatic_methods`` attribute, it is used as the420        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 rule424            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 the427            endpoint name.428        :param provide_automatic_options: Add the ``OPTIONS`` method and429            respond to ``OPTIONS`` requests automatically.430        :param options: Extra options passed to the431            :class:`~werkzeug.routing.Rule` object.432        """433        raise NotImplementedError434 435    @setupmethod436    def endpoint(self, endpoint: str) -> t.Callable[[F], F]:437        """Decorate a view function to register it for the given438        endpoint. Used if a rule is added without a ``view_func`` with439        :meth:`add_url_rule`.440 441        .. code-block:: python442 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 view450            function.451        """452 453        def decorator(f: F) -> F:454            self.view_functions[endpoint] = f455            return f456 457        return decorator458 459    @setupmethod460    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, or464        to load the logged in user from the session.465 466        .. code-block:: python467 468            @app.before_request469            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 returns474        a non-``None`` value, the value is handled as if it was the475        return value from the view, and further request handling is476        stopped.477 478        This is available on both app and blueprint objects. When used on an app, this479        executes before every request. When used on a blueprint, this executes before480        every request that the blueprint handles. To register with a blueprint and481        execute before every request, use :meth:`.Blueprint.before_app_request`.482        """483        self.before_request_funcs.setdefault(None, []).append(f)484        return f485 486    @setupmethod487    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 return491        a response object. This allows the functions to modify or492        replace the response before it is sent.493 494        If a function raises an exception, any remaining495        ``after_request`` functions will not be called. Therefore, this496        should not be used for actions that must execute, such as to497        close resources. Use :meth:`teardown_request` for that.498 499        This is available on both app and blueprint objects. When used on an app, this500        executes after every request. When used on a blueprint, this executes after501        every request that the blueprint handles. To register with a blueprint and502        execute after every request, use :meth:`.Blueprint.after_app_request`.503        """504        self.after_request_funcs.setdefault(None, []).append(f)505        return f506 507    @setupmethod508    def teardown_request(self, f: T_teardown) -> T_teardown:509        """Register a function to be called when the request context is510        popped. Typically this happens at the end of each request, but511        contexts may be pushed manually as well during testing.512 513        .. code-block:: python514 515            with app.test_request_context():516                ...517 518        When the ``with`` block exits (or ``ctx.pop()`` is called), the519        teardown functions are called just before the request context is520        made inactive.521 522        When a teardown function was called because of an unhandled523        exception it will be passed an error object. If an524        :meth:`errorhandler` is registered, it will handle the exception525        and the teardown will not receive it.526 527        Teardown functions must avoid raising exceptions. If they528        execute code that might fail they must surround that code with a529        ``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, this534        executes after every request. When used on a blueprint, this executes after535        every request that the blueprint handles. To register with a blueprint and536        execute after every request, use :meth:`.Blueprint.teardown_app_request`.537        """538        self.teardown_request_funcs.setdefault(None, []).append(f)539        return f540 541    @setupmethod542    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 before547        rendering a template. The keys of the returned dict are added as variables548        available in the template.549 550        This is available on both app and blueprint objects. When used on an app, this551        is called for every rendered template. When used on a blueprint, this is called552        for templates rendered from the blueprint's views. To register with a blueprint553        and affect every template, use :meth:`.Blueprint.app_context_processor`.554        """555        self.template_context_processors[None].append(f)556        return f557 558    @setupmethod559    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 view564        functions in the application. These functions will be called before the565        :meth:`before_request` functions.566 567        The function can modify the values captured from the matched url before568        they are passed to the view. For example, this can be used to pop a569        common language code value and place it in ``g`` rather than pass it to570        every view.571 572        The function is passed the endpoint name and values dict. The return573        value is ignored.574 575        This is available on both app and blueprint objects. When used on an app, this576        is called for every request. When used on a blueprint, this is called for577        requests that the blueprint handles. To register with a blueprint and affect578        every request, use :meth:`.Blueprint.app_url_value_preprocessor`.579        """580        self.url_value_preprocessors[None].append(f)581        return f582 583    @setupmethod584    def url_defaults(self, f: T_url_defaults) -> T_url_defaults:585        """Callback function for URL defaults for all view functions of the586        application.  It's called with the endpoint and values and should587        update the values passed in place.588 589        This is available on both app and blueprint objects. When used on an app, this590        is called for every request. When used on a blueprint, this is called for591        requests that the blueprint handles. To register with a blueprint and affect592        every request, use :meth:`.Blueprint.app_url_defaults`.593        """594        self.url_default_functions[None].append(f)595        return f596 597    @setupmethod598    def errorhandler(599        self, code_or_exception: type[Exception] | int600    ) -> 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 an604        error code.  Example::605 606            @app.errorhandler(404)607            def page_not_found(error):608                return 'This page does not exist', 404609 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', 500615 616        This is available on both app and blueprint objects. When used on an app, this617        can handle errors from every request. When used on a blueprint, this can handle618        errors from requests that the blueprint handles. To register with a blueprint619        and affect every request, use :meth:`.Blueprint.app_errorhandler`.620 621        .. versionadded:: 0.7622            Use :meth:`register_error_handler` instead of modifying623            :attr:`error_handler_spec` directly, for application wide error624            handlers.625 626        .. versionadded:: 0.7627           One can now additionally also register custom exception types628           that do not necessarily have to be a subclass of the629           :class:`~werkzeug.exceptions.HTTPException` class.630 631        :param code_or_exception: the code as integer for the handler, or632                                  an arbitrary exception633        """634 635        def decorator(f: T_error_handler) -> T_error_handler:636            self.register_error_handler(code_or_exception, f)637            return f638 639        return decorator640 641    @setupmethod642    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 decorator649        usage.650 651        .. versionadded:: 0.7652        """653        exc_class, code = self._get_exc_class_and_code(code_or_exception)654        self.error_handler_spec[None][code][exc_class] = f655 656    @staticmethod657    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 codes661        or ``HTTPException`` subclasses, return both the exception and662        status code.663 664        :param exc_class_or_code: Any exception class, or an HTTP status665            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 None678        else:679            exc_class = exc_class_or_code680 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.code697        else:698            return exc_class, None699 700 701def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str:702    """Internal helper that returns the default endpoint for a given703    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 _find_package_path(import_name: str) -> str:710    """Find the path that contains the package or module."""711    root_mod_name, _, _ = import_name.partition(".")712 713    try:714        root_spec = importlib.util.find_spec(root_mod_name)715 716        if root_spec is None:717            raise ValueError("not found")718    except (ImportError, ValueError):719        # ImportError: the machinery told us it does not exist720        # ValueError:721        #    - the module name was invalid722        #    - the module name is __main__723        #    - we raised `ValueError` due to `root_spec` being `None`724        return os.getcwd()725 726    if root_spec.submodule_search_locations:727        if root_spec.origin is None or root_spec.origin == "namespace":728            # namespace package729            package_spec = importlib.util.find_spec(import_name)730 731            if package_spec is not None and package_spec.submodule_search_locations:732                # Pick the path in the namespace that contains the submodule.733                package_path = pathlib.Path(734                    os.path.commonpath(package_spec.submodule_search_locations)735                )736                search_location = next(737                    location738                    for location in root_spec.submodule_search_locations739                    if package_path.is_relative_to(location)740                )741            else:742                # Pick the first path.743                search_location = root_spec.submodule_search_locations[0]744 745            return os.path.dirname(search_location)746        else:747            # package with __init__.py748            return os.path.dirname(os.path.dirname(root_spec.origin))749    else:750        # module751        return os.path.dirname(root_spec.origin)  # type: ignore[type-var, return-value]752 753 754def find_package(import_name: str) -> tuple[str | None, str]:755    """Find the prefix that a package is installed under, and the path756    that it would be imported from.757 758    The prefix is the directory containing the standard directory759    hierarchy (lib, bin, etc.). If the package is not installed to the760    system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),761    ``None`` is returned.762 763    The path is the entry in :attr:`sys.path` that contains the package764    for import. If the package is not installed, it's assumed that the765    package was imported from the current working directory.766    """767    package_path = _find_package_path(import_name)768    py_prefix = os.path.abspath(sys.prefix)769 770    # installed to the system771    if pathlib.PurePath(package_path).is_relative_to(py_prefix):772        return py_prefix, package_path773 774    site_parent, site_folder = os.path.split(package_path)775 776    # installed to a virtualenv777    if site_folder.lower() == "site-packages":778        parent, folder = os.path.split(site_parent)779 780        # Windows (prefix/lib/site-packages)781        if folder.lower() == "lib":782            return parent, package_path783 784        # Unix (prefix/lib/pythonX.Y/site-packages)785        if os.path.basename(parent).lower() == "lib":786            return os.path.dirname(parent), package_path787 788        # something else (prefix/site-packages)789        return site_parent, package_path790 791    # not installed792    return None, package_path793 
codekingpro/portable-devtools · Team Ai