???????????????????????????? ??????????????/????????????????? ............/............... .........................../... ????????????????????????????? >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>> ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ???????????????????????????? ??????????????/????????????????? ............/............... .........................../... ????????????????????????????? >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>> ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> __pycache__/na.cpython-310.pyc000064400000006620152441450640012064 0ustar00o ;jj@sddlmZddlmZddlmZddlmZmZer0ddl m Z ddl m Z ddlm Z mZGd d d eZGd d d eZd d gZd S)) annotations)contextmanager) TYPE_CHECKING)AppData ContentStore) Generator)Path)AnyNoReturnc@seZdZdZdZdZd'ddZedZd'd d Z d'd d Z d(ddZ d)ddZ d*ddZ ed+ddZed,dd Zd-d"d#Zd'd$d%Zd&S).AppDataDisabledzPNo application cache available (most likely as we don't have write permissions).TFreturnNonecCsdSNselfrrJ/opt/saltstack/salt/lib/python3.10/site-packages/virtualenv/app_data/na.py__init__zAppDataDisabled.__init__zDno app data folder available, probably no write access to the foldercCdSz Do nothing.NrrrrrclosezAppDataDisabled.closecCrrrrrrrresetrzAppDataDisabled.resetpathr ContentStoreNAcCtSrrrrrrrpy_infozAppDataDisabled.py_info distributionstrfor_py_versioncCrrr)rr"r$rrrembed_update_log"r!z AppDataDisabled.embed_update_log to_folder Path | Noner cC|jrerror)rrr&rrrextract%r!zAppDataDisabled.extractGenerator[None]ccs dVdSrrrrrrlocked(s zAppDataDisabled.lockedcCr(rr)rrrrhouse-szAppDataDisabled.housenamecCr(rr))rr$r/rrr wheel_image1r!zAppDataDisabled.wheel_imagecCr)zNothing to clear.Nrrrrr py_info_clear4rzAppDataDisabled.py_info_clearNr r)rr r r)r"r#r$r#r r)rr r&r'r r )rr r r,)r r )r$r#r/r#r r )__name__ __module__ __qualname____doc__Z transientZ can_updater RuntimeErrorr*rrr r%r+rr-propertyr.r0r1rrrrr s"         r c@sBeZdZdddZdddZdd d Zdd d ZedddZdS)rr boolcCr)NFrrrrrexists9rzContentStoreNA.existsrcCr)zNothing to read.Nrrrrrread<szContentStoreNA.readcontentr cCr)zNothing to write.Nr)rr<rrrwrite@rzContentStoreNA.writecCr)zNothing to remove.NrrrrrremoveCrzContentStoreNA.remover,ccs dVdSrrrrrrr-Fs zContentStoreNA.lockedN)r r9r2)r<r r r)r r,) r3r4r5r:r;r=r>rr-rrrrr8s    rN) __future__r contextlibrtypingrbaserrcollections.abcrpathlibr r r r r__all__rrrrs    *__pycache__/read_only.cpython-310.pyc000064400000004301152441450640013434 0ustar00o ;j@sddlmZddlZddlmZddlmZddlm Z m Z er,ddl m Z ddlm Z Gd d d e ZGd d d e Zd gZdS) ) annotationsN) TYPE_CHECKING) NoOpFileLock)AppDataDiskFolderPyInfoStoreDisk)Path)NoReturncsJeZdZdZdfdd Zddd Zdd d ZdddZdddZZ S)ReadOnlyAppDataFfolderstrreturnNonecs:tj|sd|d}t|t|t||_dS)Nzread-only app data directory z does not exist)ospathisdir RuntimeErrorsuper__init__rlock)selfr msg __class__Q/opt/saltstack/salt/lib/python3.10/site-packages/virtualenv/app_data/read_only.pyrs   zReadOnlyAppData.__init__cCs d}t|)Nz)read-only app data does not support resetr)rrrrrresetzReadOnlyAppData.resetcCtNNotImplementedError)rrrr py_info_clearzReadOnlyAppData.py_info_clearrr_PyInfoStoreDiskReadOnlycCs t|j|Sr )r%Z py_info_at)rrrrrpy_info s zReadOnlyAppData.py_info distributionfor_py_versionr cCrr r!)rr'r(rrrembed_update_log#r$z ReadOnlyAppData.embed_update_log)r r r r)r r)rrr r%)r'r r(r r r ) __name__ __module__ __qualname__Z can_updaterrr#r&r) __classcell__rrrrr s   r c@seZdZdddZdS) r%contentr r r cCs d}t|)Nz0read-only app data python info cannot be updatedr)rr.rrrrwrite(rz_PyInfoStoreDiskReadOnly.writeN)r.r r r )r*r+r,r/rrrrr%'sr%) __future__rZos.pathrtypingrZvirtualenv.util.lockrZvia_disk_folderrrpathlibrr r r%__all__rrrrs     __pycache__/via_disk_folder.cpython-310.pyc000064400000017754152441450640014624 0ustar00o ;jk@s dZddlmZddlZddlZddlmZddlmZm Z ddl m Z ddl m Z mZddlmZdd lmZdd lmZdd lmZd d lmZmZe rZddlmZddlmZeeZ GdddeZ!GdddeeZ"Gddde"Z#Gddde"Z$gdZ%dS)u,A rough layout of the current storage goes as: :: virtualenv-app-data ├── py - │ └── *.json/lock ├── wheel │ ├── house │ │ └── *.whl │ └── -> 3.9 │ ├── img- │ │ └── image │ │ └── -> CopyPipInstall / SymlinkPipInstall │ │ └── -> pip-20.1.1-py2.py3-none-any │ └── embed │ └── 3 -> json format versioning │ └── *.json -> for every distribution contains data about newer embed versions and releases └─── unzip └── ├── py_info.py ├── debug.py └── _virtualenv.py ) annotationsN)ABC)contextmanagersuppress)sha256) TYPE_CHECKINGAny)ReentrantFileLock) safe_delete)extract) __version__)AppData ContentStore) Generator)Pathc@seZdZdZdZdZd/dd Zd0d d Zd0d d Zd1ddZ d1ddZ e d2ddZ e d3ddZ ed4ddZd5d d!Zd1d"d#Zd6d'd(Zed7d)d*Zd8d,d-Zd.S)9AppDataDiskFolderz>Store the application data on the disk within a folder layout.FTfolderstrreturnNonecCst||_dSN)r lock)selfrrW/opt/saltstack/salt/lib/python3.10/site-packages/virtualenv/app_data/via_disk_folder.py__init__8szAppDataDiskFolder.__init__cCst|jd|jjdS)N())type__name__rpathrrrr__repr__;szAppDataDiskFolder.__repr__cCs t|jjSr)rrr!r"rrr__str__> zAppDataDiskFolder.__str__cCs td|jjt|jjdS)Nzreset app data folder %s)LOGGERdebugrr!r r"rrrresetAszAppDataDiskFolder.resetcCsdS)z Do nothing.Nrr"rrrcloseEszAppDataDiskFolder.closer!rGenerator[None]ccs>|j|}| |jVWddS1swYdSrrr!)rr!Z path_lockrrrlockedHs   "zAppDataDiskFolder.locked to_folder Path | NoneGenerator[Path]ccsx|dur t|n|jdt}||j|j|j}|s't|||VWddS1s5wYdS)NZunzip)r rr lock_for_keynamer!existsr )rr!r-rootdestrrrr Ns   "zAppDataDiskFolder.extractr cCs|jddS)Npy_info4)rr"rrr py_info_atWszAppDataDiskFolder.py_info_atPyInfoStoreDiskcCs t|j|Sr)r8r7rr!rrrr5[r%zAppDataDiskFolder.py_infoc Cs|j}|4|jD]%}|jdkr0||j|r!|Wdn1s+wYq WddS1sr&r'rXr"rrrres zJSONStoreDisk.remover*ccs<|j|j dVWddS1swYdSr)rVr0rWr"rrrr,s"zJSONStoreDisk.lockedcontentcCsJ|jj}|jddd|jjtj|dddddtjdg|jRdS)NTrI) sort_keysindentr\r]zwrote %s %s at %s) rZparentrL write_textr_dumpsr&r'rX)rrhrrrrwriteszJSONStoreDisk.writeN)rVr rWrrXrYrrrP)rr[)rrrO)rr*)rhrrr) r rQrRrrTrZr2rgrerr,rorrrrrUus      rUceZdZd fdd ZZS) r8rVr r!rrrcs.tt|d}t||d|fdS)Nr\zpython info of)rrencode hexdigestsuperr)rrVr!rW __class__rrrszPyInfoStoreDisk.__init__)rVr r!rrrr rQrRr __classcell__rrrtrr8r8crp) rCrVr rArrrcst||d|fdS)Nzembed update of distribution)rsr)rrVrArtrrrs z)EmbedDistributionUpdateStoreDisk.__init__)rVr rArrrrvrrrtrrCrxrC)rrUr8)&rS __future__rr_loggingabcr contextlibrrhashlibrtypingrrZvirtualenv.util.lockr Zvirtualenv.util.pathr Zvirtualenv.util.zipappr Zvirtualenv.versionr baserrcollections.abcrpathlibr getLoggerr r&rrUr8rC__all__rrrrs,          C- __pycache__/__init__.cpython-310.pyc000064400000004434152441450640013226 0ustar00o ;j@sdZddlmZddlZddlZddlZddlmZmZddl m Z m Z ddl m Z ddlmZdd lmZdd lmZerJdd lmZdd lmZeeZdddZdddZdddZdZdS) z&Application data stored by virtualenv.) annotationsN) TYPE_CHECKINGAny)user_cache_dir user_data_dir)AppDataDisabled)ReadOnlyAppData)AppDataDiskFolder) TempAppData)Mapping)AppDataenvMapping[str, str]returnstrcCsd}||vr ||StS)NZVIRTUALENV_OVERRIDE_APP_DATA)_cache_dir_with_migration)rkeyrP/opt/saltstack/salt/lib/python3.10/site-packages/virtualenv/app_data/__init__.py_default_app_data_dirsrc Cstddd}tddd}||kr|Stj|rKtj|sKtd||z t||W|St yJ}zt d||||WYd}~Sd}~ww|S)NZ virtualenvZpypa)ZappnameZ appauthorz migrating app data from %s to %sz@could not migrate app data from %s to %s: %r, using old location) rrospathisdirLOGGERinfoshutilmoveOSErrorwarning)new_dirold_dir exceptionrrrr s"  rfolder str | Nonekwargsrr c Ks|d}|d}|rd}t||durt|}tj|}|r&t|Sztj|ddt d|Wnt yN}z t d||WYd}~nd}~wwt |tj rZt|St d|tS) N read_onlyrzunexpected keywords: {}T)exist_okzcreated app data folder %sz-could not create app data folder %s due to %rz&app data folder %s has no write access)pop TypeErrorrrrabspathr makedirsrdebugrraccessW_OKr r )r#r%Z is_read_onlyrmsgr"rrr make_app_data1s*    r0)rr r r r0)rrrr)rr)r#r$r%rrr )__doc__ __future__rloggingrrtypingrrZ platformdirsrrZnarr&r Zvia_disk_folderr Z via_tempdirr collections.abcr baser getLogger__name__rrrr0__all__rrrrs&           __pycache__/via_tempdir.cpython-310.pyc000064400000003167152441450640013774 0ustar00o ;j@sxddlmZddlZddlmZddlmZddlmZddl m Z er*ddlm Z e e ZGd d d e Zd gZdS) ) annotationsN)mkdtemp) TYPE_CHECKING) safe_delete)AppDataDiskFolder)NoReturncsDeZdZdZdZdfdd ZdddZdd d ZdddZZ S) TempAppDataTFreturnNonecs$tjtdtd|jjdS)N)Zfolderz$created temporary app data folder %s)super__init__rLOGGERdebuglockpathself __class__S/opt/saltstack/salt/lib/python3.10/site-packages/virtualenv/app_data/via_tempdir.pyr szTempAppData.__init__cCsdS)z;This is a temporary folder, is already empty to start with.NrrrrrresetszTempAppData.resetcCs td|jjt|jjdS)Nz#remove temporary app data folder %s)rrrrrrrrrcloseszTempAppData.close distributionstrfor_py_versionrcCst)N)NotImplementedError)rrrrrrembed_update_log szTempAppData.embed_update_log)r r )rrrrr r) __name__ __module__ __qualname__Z transientZ can_updater rrr __classcell__rrrrr s  r ) __future__rloggingZtempfilertypingrZvirtualenv.util.pathrZvia_disk_folderrr getLoggerrrr __all__rrrrs       __pycache__/base.cpython-310.pyc000064400000012712152441450640012377 0ustar00o ;jv@sdZddlmZddlmZmZddlmZddlm Z ddl m Z e r6ddl m Z ddlmZdd lmZGd d d eZGd d d eZd d gZdS)z&Application data stored by virtualenv.) annotations)ABCabstractmethod)contextmanager) TYPE_CHECKING) IS_ZIPAPP) Generator)Path)Anyc@seZdZdZed)ddZed)ddZed*d d Zed)d dZe d+ddZ ed,ddZ e d-ddZ e d+ddZ ed.ddZed/d0d"d#Zeed0d$d%Zeed1d'd(ZdS)2AppDataz:Abstract storage interface for the virtualenv application.returnNonecCdS)zCalled before virtualenv exits.NselfrrL/opt/saltstack/salt/lib/python3.10/site-packages/virtualenv/app_data/base.pyclosez AppData.closecCr)z2Called when the user passes in the reset app data.Nrrrrrresetrz AppData.resetpathr ContentStorecCt)zReturn a content store for cached interpreter information at the given path. :param path: the interpreter executable path :returns: a content store for the cached data NotImplementedErrorrrrrrpy_infos zAppData.py_infocCr)z)Clear all cached interpreter information.rrrrr py_info_clear'zAppData.py_info_clearboolcCr)zA``True`` if this app data store supports updating cached content.rrrrr can_update,rzAppData.can_update distributionstrfor_py_versioncCr)aReturn a content store for the embed update log of a distribution. :param distribution: the package name (e.g. ``pip``) :param for_py_version: the target Python version string :returns: a content store for the update log r)rr!r#rrrembed_update_log1 zAppData.embed_update_logcCr)z1The root directory of the application data store.rrrrrhouse=rz AppData.housecCr)zN``True`` if this app data store is transient and does not persist across runs.rrrrr transientBrzAppData.transientnamecCr)zReturn the path to a cached wheel image. :param for_py_version: the target Python version string :param name: the package name :returns: the path to the cached wheel r)rr#r(rrr wheel_imageGr%zAppData.wheel_imageN to_folder Path | NoneGenerator[Path]ccsHtr||| }|VWddS1swYdS|VdS)zEnsure a path is available on disk, extracting from zipapp if needed. :param path: the path to ensure is available :param to_folder: optional target directory for extraction :returns: yields the usable path on disk N)rextract)rrr*resultrrrensure_extractedSs  " zAppData.ensure_extractedcCr)zExtract a path from the zipapp to a location on disk. :param path: the path to extract :param to_folder: optional target directory :returns: yields the extracted path r)rrr*rrrr-cs zAppData.extractGenerator[None]cCr)z]Acquire an exclusive lock on the given path. :param path: the path to lock rrrrrlockedpszAppData.lockedr r )rr r rr r)r!r"r#r"r r)r r )r#r"r(r"r r )N)rr r*r+r r,)rr r r0)__name__ __module__ __qualname____doc__rrrrrpropertyr r$r&r'r)rr/r-r1rrrrr s8           r c@sZeZdZdZedddZedddZedd d Zedd dZee dddZ dS)rz/A store for reading and writing cached content.r rcCr)z[Check if the stored content exists. :returns: ``True`` if content exists rrrrrexists~zContentStore.existsr cCr)zHRead the stored content. :returns: the stored content rrrrrreadr:zContentStore.readcontentr cCr)zSWrite content to the store. :param content: the content to write r)rr<rrrwriter:zContentStore.writecCr)zRemove the stored content.rrrrrremoverzContentStore.remover0cCr)z0Acquire an exclusive lock on this content store.Nrrrrrr1rzContentStore.lockedNr3)r r )r<r r r r2)r r0) r4r5r6r7rr9r;r=r>rr1rrrrr{s    rN)r7 __future__rabcrr contextlibrtypingrZvirtualenv.inforcollections.abcrpathlibr r r r__all__rrrrs       j*via_tempdir.py000064400000001645152441450640007434 0ustar00from __future__ import annotations import logging from tempfile import mkdtemp from typing import TYPE_CHECKING from virtualenv.util.path import safe_delete from .via_disk_folder import AppDataDiskFolder if TYPE_CHECKING: from typing import NoReturn LOGGER = logging.getLogger(__name__) class TempAppData(AppDataDiskFolder): transient = True can_update = False def __init__(self) -> None: super().__init__(folder=mkdtemp()) LOGGER.debug("created temporary app data folder %s", self.lock.path) def reset(self) -> None: """This is a temporary folder, is already empty to start with.""" def close(self) -> None: LOGGER.debug("remove temporary app data folder %s", self.lock.path) safe_delete(self.lock.path) def embed_update_log(self, distribution: str, for_py_version: str) -> NoReturn: raise NotImplementedError __all__ = [ "TempAppData", ] na.py000064400000003552152441450640005526 0ustar00from __future__ import annotations from contextlib import contextmanager from typing import TYPE_CHECKING from .base import AppData, ContentStore if TYPE_CHECKING: from collections.abc import Generator from pathlib import Path from typing import Any, NoReturn class AppDataDisabled(AppData): """No application cache available (most likely as we don't have write permissions).""" transient = True can_update = False def __init__(self) -> None: pass error = RuntimeError("no app data folder available, probably no write access to the folder") def close(self) -> None: """Do nothing.""" def reset(self) -> None: """Do nothing.""" def py_info(self, path: Path) -> ContentStoreNA: # noqa: ARG002 return ContentStoreNA() def embed_update_log(self, distribution: str, for_py_version: str) -> ContentStoreNA: # noqa: ARG002 return ContentStoreNA() def extract(self, path: Path, to_folder: Path | None) -> NoReturn: # noqa: ARG002 raise self.error @contextmanager def locked(self, path: Path) -> Generator[None]: # noqa: ARG002 """Do nothing.""" yield @property def house(self) -> NoReturn: raise self.error def wheel_image(self, for_py_version: str, name: str) -> NoReturn: # noqa: ARG002 raise self.error def py_info_clear(self) -> None: """Nothing to clear.""" class ContentStoreNA(ContentStore): def exists(self) -> bool: return False def read(self) -> None: """Nothing to read.""" return def write(self, content: Any) -> None: # noqa: ANN401 """Nothing to write.""" def remove(self) -> None: """Nothing to remove.""" @contextmanager def locked(self) -> Generator[None]: yield __all__ = [ "AppDataDisabled", "ContentStoreNA", ] __init__.py000064400000004357152441450640006673 0ustar00"""Application data stored by virtualenv.""" from __future__ import annotations import logging import os import shutil from typing import TYPE_CHECKING, Any from platformdirs import user_cache_dir, user_data_dir from .na import AppDataDisabled from .read_only import ReadOnlyAppData from .via_disk_folder import AppDataDiskFolder from .via_tempdir import TempAppData if TYPE_CHECKING: from collections.abc import Mapping from .base import AppData LOGGER = logging.getLogger(__name__) def _default_app_data_dir(env: Mapping[str, str]) -> str: key = "VIRTUALENV_OVERRIDE_APP_DATA" if key in env: return env[key] return _cache_dir_with_migration() def _cache_dir_with_migration() -> str: new_dir = user_cache_dir(appname="virtualenv", appauthor="pypa") old_dir = user_data_dir(appname="virtualenv", appauthor="pypa") if new_dir == old_dir: return new_dir if os.path.isdir(old_dir) and not os.path.isdir(new_dir): LOGGER.info("migrating app data from %s to %s", old_dir, new_dir) try: shutil.move(old_dir, new_dir) except OSError as exception: LOGGER.warning( "could not migrate app data from %s to %s: %r, using old location", old_dir, new_dir, exception ) return old_dir return new_dir def make_app_data(folder: str | None, **kwargs: Any) -> AppData: # noqa: ANN401 is_read_only = kwargs.pop("read_only") env = kwargs.pop("env") if kwargs: # py3+ kwonly msg = "unexpected keywords: {}" raise TypeError(msg) if folder is None: folder = _default_app_data_dir(env) folder = os.path.abspath(folder) if is_read_only: return ReadOnlyAppData(folder) try: os.makedirs(folder, exist_ok=True) LOGGER.debug("created app data folder %s", folder) except OSError as exception: LOGGER.info("could not create app data folder %s due to %r", folder, exception) if os.access(folder, os.W_OK): return AppDataDiskFolder(folder) LOGGER.debug("app data folder %s has no write access", folder) return TempAppData() __all__ = ( "AppDataDisabled", "AppDataDiskFolder", "ReadOnlyAppData", "TempAppData", "make_app_data", ) read_only.py000064400000002423152441450640007100 0ustar00from __future__ import annotations import os.path from typing import TYPE_CHECKING from virtualenv.util.lock import NoOpFileLock from .via_disk_folder import AppDataDiskFolder, PyInfoStoreDisk if TYPE_CHECKING: from pathlib import Path from typing import NoReturn class ReadOnlyAppData(AppDataDiskFolder): can_update = False def __init__(self, folder: str) -> None: if not os.path.isdir(folder): msg = f"read-only app data directory {folder} does not exist" raise RuntimeError(msg) super().__init__(folder) self.lock = NoOpFileLock(folder) def reset(self) -> None: msg = "read-only app data does not support reset" raise RuntimeError(msg) def py_info_clear(self) -> None: raise NotImplementedError def py_info(self, path: Path) -> _PyInfoStoreDiskReadOnly: return _PyInfoStoreDiskReadOnly(self.py_info_at, path) def embed_update_log(self, distribution: str, for_py_version: str) -> NoReturn: raise NotImplementedError class _PyInfoStoreDiskReadOnly(PyInfoStoreDisk): def write(self, content: str) -> NoReturn: # noqa: ARG002 msg = "read-only app data python info cannot be updated" raise RuntimeError(msg) __all__ = [ "ReadOnlyAppData", ] via_disk_folder.py000064400000014153152441450640010253 0ustar00r"""A rough layout of the current storage goes as: :: virtualenv-app-data ├── py - │ └── *.json/lock ├── wheel │ ├── house │ │ └── *.whl │ └── -> 3.9 │ ├── img- │ │ └── image │ │ └── -> CopyPipInstall / SymlinkPipInstall │ │ └── -> pip-20.1.1-py2.py3-none-any │ └── embed │ └── 3 -> json format versioning │ └── *.json -> for every distribution contains data about newer embed versions and releases └─── unzip └── ├── py_info.py ├── debug.py └── _virtualenv.py """ # noqa: D415 from __future__ import annotations import json import logging from abc import ABC from contextlib import contextmanager, suppress from hashlib import sha256 from typing import TYPE_CHECKING, Any from virtualenv.util.lock import ReentrantFileLock from virtualenv.util.path import safe_delete from virtualenv.util.zipapp import extract from virtualenv.version import __version__ from .base import AppData, ContentStore if TYPE_CHECKING: from collections.abc import Generator from pathlib import Path LOGGER = logging.getLogger(__name__) class AppDataDiskFolder(AppData): """Store the application data on the disk within a folder layout.""" transient = False can_update = True def __init__(self, folder: str) -> None: self.lock = ReentrantFileLock(folder) def __repr__(self) -> str: return f"{type(self).__name__}({self.lock.path})" def __str__(self) -> str: return str(self.lock.path) def reset(self) -> None: LOGGER.debug("reset app data folder %s", self.lock.path) safe_delete(self.lock.path) def close(self) -> None: """Do nothing.""" @contextmanager def locked(self, path: Path) -> Generator[None]: path_lock = self.lock / path # ty: ignore[unsupported-operator] with path_lock: yield path_lock.path @contextmanager def extract(self, path: Path, to_folder: Path | None) -> Generator[Path]: root = ReentrantFileLock(to_folder()) if to_folder is not None else self.lock / "unzip" / __version__ # ty: ignore[call-non-callable] with root.lock_for_key(path.name): dest = root.path / path.name if not dest.exists(): extract(path, dest) yield dest @property def py_info_at(self) -> ReentrantFileLock: return self.lock / "py_info" / "4" # ty: ignore[invalid-return-type] def py_info(self, path: Path) -> PyInfoStoreDisk: return PyInfoStoreDisk(self.py_info_at, path) def py_info_clear(self) -> None: """clear py info.""" py_info_folder = self.py_info_at with py_info_folder: for filename in py_info_folder.path.iterdir(): if filename.suffix == ".json": with py_info_folder.lock_for_key(filename.stem): if filename.exists(): filename.unlink() def embed_update_log(self, distribution: str, for_py_version: str) -> EmbedDistributionUpdateStoreDisk: return EmbedDistributionUpdateStoreDisk(self.lock / "wheel" / for_py_version / "embed" / "3", distribution) # ty: ignore[invalid-argument-type] @property def house(self) -> Path: path = self.lock.path / "wheel" / "house" path.mkdir(parents=True, exist_ok=True) return path def wheel_image(self, for_py_version: str, name: str) -> Path: return self.lock.path / "wheel" / for_py_version / "image" / "1" / name class JSONStoreDisk(ContentStore, ABC): def __init__(self, in_folder: ReentrantFileLock, key: str, msg_args: tuple[str, ...]) -> None: self.in_folder = in_folder self.key = key self.msg_args = (*msg_args, self.file) @property def file(self) -> Path: return self.in_folder.path / f"{self.key}.json" def exists(self) -> bool: return self.file.exists() def read(self) -> Any: # noqa: ANN401 data, bad_format = None, False try: data = json.loads(self.file.read_text(encoding="utf-8")) except ValueError: bad_format = True except Exception: # noqa: BLE001, S110 pass else: LOGGER.debug("got %s %s from %s", *self.msg_args) return data if bad_format: with suppress(OSError): # reading and writing on the same file may cause race on multiple processes self.remove() return None def remove(self) -> None: self.file.unlink() LOGGER.debug("removed %s %s at %s", *self.msg_args) @contextmanager def locked(self) -> Generator[None]: with self.in_folder.lock_for_key(self.key): yield def write(self, content: Any) -> None: # noqa: ANN401 folder = self.file.parent folder.mkdir(parents=True, exist_ok=True) self.file.write_text(json.dumps(content, sort_keys=True, indent=2), encoding="utf-8") LOGGER.debug("wrote %s %s at %s", *self.msg_args) class PyInfoStoreDisk(JSONStoreDisk): def __init__(self, in_folder: ReentrantFileLock, path: Path) -> None: key = sha256(str(path).encode("utf-8")).hexdigest() super().__init__(in_folder, key, ("python info of", path)) # ty: ignore[invalid-argument-type] class EmbedDistributionUpdateStoreDisk(JSONStoreDisk): def __init__(self, in_folder: ReentrantFileLock, distribution: str) -> None: super().__init__( in_folder, distribution, ("embed update of distribution", distribution), ) __all__ = [ "AppDataDiskFolder", "JSONStoreDisk", "PyInfoStoreDisk", ] base.py000064400000010566152441450640006045 0ustar00"""Application data stored by virtualenv.""" from __future__ import annotations from abc import ABC, abstractmethod from contextlib import contextmanager from typing import TYPE_CHECKING from virtualenv.info import IS_ZIPAPP if TYPE_CHECKING: from collections.abc import Generator from pathlib import Path from typing import Any class AppData(ABC): """Abstract storage interface for the virtualenv application.""" @abstractmethod def close(self) -> None: """Called before virtualenv exits.""" @abstractmethod def reset(self) -> None: """Called when the user passes in the reset app data.""" @abstractmethod def py_info(self, path: Path) -> ContentStore: """Return a content store for cached interpreter information at the given path. :param path: the interpreter executable path :returns: a content store for the cached data """ raise NotImplementedError @abstractmethod def py_info_clear(self) -> None: """Clear all cached interpreter information.""" raise NotImplementedError @property def can_update(self) -> bool: """``True`` if this app data store supports updating cached content.""" raise NotImplementedError @abstractmethod def embed_update_log(self, distribution: str, for_py_version: str) -> ContentStore: """Return a content store for the embed update log of a distribution. :param distribution: the package name (e.g. ``pip``) :param for_py_version: the target Python version string :returns: a content store for the update log """ raise NotImplementedError @property def house(self) -> Path: """The root directory of the application data store.""" raise NotImplementedError @property def transient(self) -> bool: """``True`` if this app data store is transient and does not persist across runs.""" raise NotImplementedError @abstractmethod def wheel_image(self, for_py_version: str, name: str) -> Path: """Return the path to a cached wheel image. :param for_py_version: the target Python version string :param name: the package name :returns: the path to the cached wheel """ raise NotImplementedError @contextmanager def ensure_extracted(self, path: Path, to_folder: Path | None = None) -> Generator[Path]: """Ensure a path is available on disk, extracting from zipapp if needed. :param path: the path to ensure is available :param to_folder: optional target directory for extraction :returns: yields the usable path on disk """ if IS_ZIPAPP: with self.extract(path, to_folder) as result: yield result else: yield path @abstractmethod @contextmanager def extract(self, path: Path, to_folder: Path | None) -> Generator[Path]: """Extract a path from the zipapp to a location on disk. :param path: the path to extract :param to_folder: optional target directory :returns: yields the extracted path """ raise NotImplementedError @abstractmethod @contextmanager def locked(self, path: Path) -> Generator[None]: """Acquire an exclusive lock on the given path. :param path: the path to lock """ raise NotImplementedError class ContentStore(ABC): """A store for reading and writing cached content.""" @abstractmethod def exists(self) -> bool: """Check if the stored content exists. :returns: ``True`` if content exists """ raise NotImplementedError @abstractmethod def read(self) -> Any: # noqa: ANN401 """Read the stored content. :returns: the stored content """ raise NotImplementedError @abstractmethod def write(self, content: Any) -> None: # noqa: ANN401 """Write content to the store. :param content: the content to write """ raise NotImplementedError @abstractmethod def remove(self) -> None: """Remove the stored content.""" raise NotImplementedError @abstractmethod @contextmanager def locked(self) -> Generator[None]: """Acquire an exclusive lock on this content store.""" __all__ = [ "AppData", "ContentStore", ]