Vendor in pip 23.2

This commit is contained in:
Matt Davis
2023-07-22 00:36:19 -04:00
committed by Oz Tiram
parent 301d2041e7
commit 5618166fbc
107 changed files with 3031 additions and 1757 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import List, Optional
__version__ = "23.1.2"
__version__ = "23.2"
def main(args: Optional[List[str]] = None) -> int:
-7
View File
@@ -1,6 +1,5 @@
import os
import sys
import warnings
# Remove '' and current working directory from the first entry
# of sys.path, if present to avoid using current directory
@@ -20,12 +19,6 @@ if __package__ == "":
sys.path.insert(0, path)
if __name__ == "__main__":
# Work around the error reported in #9540, pending a proper fix.
# Note: It is essential the warning filter is set *before* importing
# pip, as the deprecation happens at import time, not runtime.
warnings.filterwarnings(
"ignore", category=DeprecationWarning, module=".*packaging\\.version"
)
import importlib.util
import sys
spec = importlib.util.spec_from_file_location(
+31 -11
View File
@@ -194,7 +194,17 @@ class CacheEntry:
self.origin: Optional[DirectUrl] = None
origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME
if origin_direct_url_path.exists():
self.origin = DirectUrl.from_json(origin_direct_url_path.read_text())
try:
self.origin = DirectUrl.from_json(
origin_direct_url_path.read_text(encoding="utf-8")
)
except Exception as e:
logger.warning(
"Ignoring invalid cache entry origin file %s for %s (%s)",
origin_direct_url_path,
link.filename,
e,
)
class WheelCache(Cache):
@@ -257,16 +267,26 @@ class WheelCache(Cache):
@staticmethod
def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None:
origin_path = Path(cache_dir) / ORIGIN_JSON_NAME
if origin_path.is_file():
origin = DirectUrl.from_json(origin_path.read_text())
# TODO: use DirectUrl.equivalent when https://github.com/pypa/pip/pull/10564
# is merged.
if origin.url != download_info.url:
if origin_path.exists():
try:
origin = DirectUrl.from_json(origin_path.read_text(encoding="utf-8"))
except Exception as e:
logger.warning(
"Origin URL %s in cache entry %s does not match download URL %s. "
"This is likely a pip bug or a cache corruption issue.",
origin.url,
cache_dir,
download_info.url,
"Could not read origin file %s in cache entry (%s). "
"Will attempt to overwrite it.",
origin_path,
e,
)
else:
# TODO: use DirectUrl.equivalent when
# https://github.com/pypa/pip/pull/10564 is merged.
if origin.url != download_info.url:
logger.warning(
"Origin URL %s in cache entry %s does not match download URL "
"%s. This is likely a pip bug or a cache corruption issue. "
"Will overwrite it with the new value.",
origin.url,
cache_dir,
download_info.url,
)
origin_path.write_text(download_info.to_json(), encoding="utf-8")
@@ -131,6 +131,17 @@ class Command(CommandContextMixIn):
", ".join(sorted(always_enabled_features)),
)
# Make sure that the --python argument isn't specified after the
# subcommand. We can tell, because if --python was specified,
# we should only reach this point if we're running in the created
# subprocess, which has the _PIP_RUNNING_IN_SUBPROCESS environment
# variable set.
if options.python and "_PIP_RUNNING_IN_SUBPROCESS" not in os.environ:
logger.critical(
"The --python option must be placed before the pip subcommand name"
)
sys.exit(ERROR)
# TODO: Try to get these passing down from the command?
# without resorting to os.environ to hold these.
# This also affects isolated builds and it should.
@@ -7,6 +7,7 @@ from pipenv.patched.pip._internal.cli.status_codes import ERROR, SUCCESS
from pipenv.patched.pip._internal.operations.check import (
check_package_set,
create_package_set_from_installed,
warn_legacy_versions_and_specifiers,
)
from pipenv.patched.pip._internal.utils.misc import write_output
@@ -21,6 +22,7 @@ class CheckCommand(Command):
def run(self, options: Values, args: List[str]) -> int:
package_set, parsing_probs = create_package_set_from_installed()
warn_legacy_versions_and_specifiers(package_set)
missing, conflicting = check_package_set(package_set)
for project_name in missing:
@@ -22,15 +22,10 @@ COMPLETION_SCRIPTS = {
complete -o default -F _pip_completion {prog}
""",
"zsh": """
function _pip_completion {{
local words cword
read -Ac words
read -cn cword
reply=( $( COMP_WORDS="$words[*]" \\
COMP_CWORD=$(( cword-1 )) \\
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ))
}}
compctl -K _pip_completion {prog}
#compdef -P pip[0-9.]#
compadd $( COMP_WORDS="$words[*]" \\
COMP_CWORD=$((CURRENT-1)) \\
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )
""",
"fish": """
function __fish_complete_pip
@@ -137,6 +137,10 @@ class DownloadCommand(RequirementCommand):
assert req.name is not None
preparer.save_linked_requirement(req)
downloaded.append(req.name)
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
requirement_set.warn_legacy_versions_and_specifiers()
if downloaded:
write_output("Successfully downloaded %s", " ".join(downloaded))
@@ -1,6 +1,6 @@
import sys
from optparse import Values
from typing import List
from typing import AbstractSet, List
from pipenv.patched.pip._internal.cli import cmdoptions
from pipenv.patched.pip._internal.cli.base_command import Command
@@ -8,7 +8,18 @@ from pipenv.patched.pip._internal.cli.status_codes import SUCCESS
from pipenv.patched.pip._internal.operations.freeze import freeze
from pipenv.patched.pip._internal.utils.compat import stdlib_pkgs
DEV_PKGS = {"pip", "setuptools", "distribute", "wheel"}
def _should_suppress_build_backends() -> bool:
return sys.version_info < (3, 12)
def _dev_pkgs() -> AbstractSet[str]:
pkgs = {"pip"}
if _should_suppress_build_backends():
pkgs |= {"setuptools", "distribute", "wheel"}
return pkgs
class FreezeCommand(Command):
@@ -61,7 +72,7 @@ class FreezeCommand(Command):
action="store_true",
help=(
"Do not skip these packages in the output:"
" {}".format(", ".join(DEV_PKGS))
" {}".format(", ".join(_dev_pkgs()))
),
)
self.cmd_opts.add_option(
@@ -77,7 +88,7 @@ class FreezeCommand(Command):
def run(self, options: Values, args: List[str]) -> int:
skip = set(stdlib_pkgs)
if not options.freeze_all:
skip.update(DEV_PKGS)
skip.update(_dev_pkgs())
if options.excludes:
skip.update(options.excludes)
@@ -387,6 +387,9 @@ class InstallCommand(RequirementCommand):
json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
if options.dry_run:
# In non dry-run mode, the legacy versions and specifiers check
# will be done as part of conflict detection.
requirement_set.warn_legacy_versions_and_specifiers()
would_install_items = sorted(
(r.metadata["name"], r.metadata["version"])
for r in requirement_set.requirements_to_install
@@ -103,7 +103,10 @@ class ListCommand(IndexGroupCommand):
dest="list_format",
default="columns",
choices=("columns", "freeze", "json"),
help="Select the output format among: columns (default), freeze, or json",
help=(
"Select the output format among: columns (default), freeze, or json. "
"The 'freeze' format cannot be used with the --outdated option."
),
)
self.cmd_opts.add_option(
@@ -157,7 +160,7 @@ class ListCommand(IndexGroupCommand):
if options.outdated and options.list_format == "freeze":
raise CommandError(
"List format 'freeze' can not be used with the --outdated option."
"List format 'freeze' cannot be used with the --outdated option."
)
cmdoptions.check_list_path_option(options)
@@ -153,6 +153,9 @@ class WheelCommand(RequirementCommand):
elif should_build_for_wheel_command(req):
reqs_to_build.append(req)
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
requirement_set.warn_legacy_versions_and_specifiers()
# build wheels
build_successes, build_failures = build(
reqs_to_build,
@@ -210,8 +210,15 @@ class Configuration:
# Ensure directory exists.
ensure_dir(os.path.dirname(fname))
with open(fname, "w") as f:
parser.write(f)
# Ensure directory's permission(need to be writeable)
try:
with open(fname, "w") as f:
parser.write(f)
except OSError as error:
raise ConfigurationError(
f"An error occurred while writing to the configuration file "
f"{fname}: {error}"
)
#
# Private routines
+1 -1
View File
@@ -544,7 +544,7 @@ class HashMissing(HashError):
# so the output can be directly copied into the requirements file.
package = (
self.req.original_link
if self.req.original_link
if self.req.is_direct
# In case someone feeds something downright stupid
# to InstallRequirement's constructor.
else getattr(self.req, "req", None)
@@ -125,7 +125,6 @@ class LinkEvaluator:
target_python: TargetPython,
allow_yanked: bool,
ignore_requires_python: Optional[bool] = None,
ignore_compatibility: Optional[bool] = None,
) -> None:
"""
:param project_name: The user supplied package name.
@@ -143,8 +142,6 @@ class LinkEvaluator:
:param ignore_requires_python: Whether to ignore incompatible
PEP 503 "data-requires-python" values in HTML links. Defaults
to False.
:param ignore_compatibility: Whether to ignore
compatibility of python versions and allow all versions of packages.
"""
if ignore_requires_python is None:
ignore_requires_python = False
@@ -154,7 +151,6 @@ class LinkEvaluator:
self._ignore_requires_python = ignore_requires_python
self._formats = formats
self._target_python = target_python
self._ignore_compatibility = ignore_compatibility
self.project_name = project_name
@@ -185,10 +181,10 @@ class LinkEvaluator:
LinkType.format_unsupported,
f"unsupported archive format: {ext}",
)
if "binary" not in self._formats and ext == WHEEL_EXTENSION and not self._ignore_compatibility:
if "binary" not in self._formats and ext == WHEEL_EXTENSION :
reason = f"No binaries permitted for {self.project_name}"
return (LinkType.format_unsupported, reason)
if "macosx10" in link.path and ext == ".zip" and not self._ignore_compatibility:
if "macosx10" in link.path and ext == ".zip":
return (LinkType.format_unsupported, "macosx10 one")
if ext == WHEEL_EXTENSION:
try:
@@ -203,7 +199,7 @@ class LinkEvaluator:
return (LinkType.different_project, reason)
supported_tags = self._target_python.get_tags()
if not wheel.supported(supported_tags) and not self._ignore_compatibility:
if not wheel.supported(supported_tags):
# Include the wheel's tags in the reason string to
# simplify troubleshooting compatibility issues.
file_tags = ", ".join(wheel.get_formatted_file_tags())
@@ -244,7 +240,7 @@ class LinkEvaluator:
version_info=self._target_python.py_version_info,
ignore_requires_python=self._ignore_requires_python,
)
if not supports_python and not self._ignore_compatibility:
if not supports_python:
reason = f"{version} Requires-Python {link.requires_python}"
return (LinkType.requires_python_mismatch, reason)
@@ -493,7 +489,6 @@ class CandidateEvaluator:
def _sort_key(
self, candidate: InstallationCandidate,
ignore_compatibility: bool = True
) -> CandidateSortingKey:
"""
Function to pass as the `key` argument to a call to sorted() to sort
@@ -539,11 +534,10 @@ class CandidateEvaluator:
)
)
except ValueError:
if not ignore_compatibility:
raise UnsupportedWheel(
"{} is not a supported wheel for this platform. It "
"can't be sorted.".format(wheel.filename)
)
raise UnsupportedWheel(
"{} is not a supported wheel for this platform. It "
"can't be sorted.".format(wheel.filename)
)
pri = -(support_num)
if self._prefer_binary:
binary_preference = 1
@@ -611,7 +605,6 @@ class PackageFinder:
format_control: Optional[FormatControl] = None,
candidate_prefs: Optional[CandidatePreferences] = None,
ignore_requires_python: Optional[bool] = None,
ignore_compatibility: Optional[bool] = False
) -> None:
"""
This constructor is primarily meant to be used by the create() class
@@ -633,7 +626,6 @@ class PackageFinder:
self._ignore_requires_python = ignore_requires_python
self._link_collector = link_collector
self._target_python = target_python
self._ignore_compatibility = ignore_compatibility
self.format_control = format_control
@@ -734,7 +726,6 @@ class PackageFinder:
target_python=self._target_python,
allow_yanked=self._allow_yanked,
ignore_requires_python=self._ignore_requires_python,
ignore_compatibility=self._ignore_compatibility
)
def _sort_links(self, links: Iterable[Link]) -> List[Link]:
@@ -151,7 +151,7 @@ def _emit_egg_deprecation(location: Optional[str]) -> None:
deprecated(
reason=f"Loading egg at {location} is deprecated.",
replacement="to use pip for package installation.",
gone_in=None,
gone_in="23.3",
)
@@ -174,7 +174,7 @@ class Environment(BaseEnvironment):
for location in self._paths:
yield from finder.find(location)
for dist in finder.find_eggs(location):
# _emit_egg_deprecation(dist.location) # TODO: Enable this.
_emit_egg_deprecation(dist.location)
yield dist
# This must go last because that's how pkg_resources tie-breaks.
yield from finder.find_linked(location)
@@ -22,7 +22,7 @@ class InstallationReport:
# is_direct is true if the requirement was a direct URL reference (which
# includes editable requirements), and false if the requirement was
# downloaded from a PEP 503 index or --find-links.
"is_direct": bool(ireq.original_link),
"is_direct": ireq.is_direct,
# requested is true if the requirement was specified by the user (aka
# top level requirement), and false if it was installed as a dependency of a
# requirement. https://peps.python.org/pep-0376/#requested
+82 -32
View File
@@ -69,18 +69,6 @@ class LinkHash:
def __post_init__(self) -> None:
assert self.name in _SUPPORTED_HASHES
@classmethod
def parse_pep658_hash(cls, dist_info_metadata: str) -> Optional["LinkHash"]:
"""Parse a PEP 658 data-dist-info-metadata hash."""
if dist_info_metadata == "true":
return None
name, sep, value = dist_info_metadata.partition("=")
if not sep:
return None
if name not in _SUPPORTED_HASHES:
return None
return cls(name=name, value=value)
@classmethod
@functools.lru_cache(maxsize=None)
def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]:
@@ -107,6 +95,28 @@ class LinkHash:
return hashes.is_hash_allowed(self.name, hex_digest=self.value)
@dataclass(frozen=True)
class MetadataFile:
"""Information about a core metadata file associated with a distribution."""
hashes: Optional[Dict[str, str]]
def __post_init__(self) -> None:
if self.hashes is not None:
assert all(name in _SUPPORTED_HASHES for name in self.hashes)
def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
# Remove any unsupported hash types from the mapping. If this leaves no
# supported hashes, return None
if hashes is None:
return None
hashes = {n: v for n, v in hashes.items() if n in _SUPPORTED_HASHES}
if not hashes:
return None
return hashes
def _clean_url_path_part(part: str) -> str:
"""
Clean a "part" of a URL path (i.e. after splitting on "@" characters).
@@ -179,7 +189,7 @@ class Link(KeyBasedCompareMixin):
"comes_from",
"requires_python",
"yanked_reason",
"dist_info_metadata",
"metadata_file_data",
"cache_link_parsing",
"egg_fragment",
]
@@ -190,7 +200,7 @@ class Link(KeyBasedCompareMixin):
comes_from: Optional[Union[str, "IndexContent"]] = None,
requires_python: Optional[str] = None,
yanked_reason: Optional[str] = None,
dist_info_metadata: Optional[str] = None,
metadata_file_data: Optional[MetadataFile] = None,
cache_link_parsing: bool = True,
hashes: Optional[Mapping[str, str]] = None,
) -> None:
@@ -208,11 +218,10 @@ class Link(KeyBasedCompareMixin):
a simple repository HTML link. If the file has been yanked but
no reason was provided, this should be the empty string. See
PEP 592 for more information and the specification.
:param dist_info_metadata: the metadata attached to the file, or None if no such
metadata is provided. This is the value of the "data-dist-info-metadata"
attribute, if present, in a simple repository HTML link. This may be parsed
into its own `Link` by `self.metadata_link()`. See PEP 658 for more
information and the specification.
:param metadata_file_data: the metadata attached to the file, or None if
no such metadata is provided. This argument, if not None, indicates
that a separate metadata file exists, and also optionally supplies
hashes for that file.
:param cache_link_parsing: A flag that is used elsewhere to determine
whether resources retrieved from this link should be cached. PyPI
URLs should generally have this set to False, for example.
@@ -220,6 +229,10 @@ class Link(KeyBasedCompareMixin):
determine the validity of a download.
"""
# The comes_from, requires_python, and metadata_file_data arguments are
# only used by classmethods of this class, and are not used in client
# code directly.
# url can be a UNC windows share
if url.startswith("\\\\"):
url = path_to_url(url)
@@ -239,7 +252,7 @@ class Link(KeyBasedCompareMixin):
self.comes_from = comes_from
self.requires_python = requires_python if requires_python else None
self.yanked_reason = yanked_reason
self.dist_info_metadata = dist_info_metadata
self.metadata_file_data = metadata_file_data
super().__init__(key=url, defining_class=Link)
@@ -262,9 +275,25 @@ class Link(KeyBasedCompareMixin):
url = _ensure_quoted_url(urllib.parse.urljoin(page_url, file_url))
pyrequire = file_data.get("requires-python")
yanked_reason = file_data.get("yanked")
dist_info_metadata = file_data.get("dist-info-metadata")
hashes = file_data.get("hashes", {})
# PEP 714: Indexes must use the name core-metadata, but
# clients should support the old name as a fallback for compatibility.
metadata_info = file_data.get("core-metadata")
if metadata_info is None:
metadata_info = file_data.get("dist-info-metadata")
# The metadata info value may be a boolean, or a dict of hashes.
if isinstance(metadata_info, dict):
# The file exists, and hashes have been supplied
metadata_file_data = MetadataFile(supported_hashes(metadata_info))
elif metadata_info:
# The file exists, but there are no hashes
metadata_file_data = MetadataFile(None)
else:
# False or not present: the file does not exist
metadata_file_data = None
# The Link.yanked_reason expects an empty string instead of a boolean.
if yanked_reason and not isinstance(yanked_reason, str):
yanked_reason = ""
@@ -278,7 +307,7 @@ class Link(KeyBasedCompareMixin):
requires_python=pyrequire,
yanked_reason=yanked_reason,
hashes=hashes,
dist_info_metadata=dist_info_metadata,
metadata_file_data=metadata_file_data,
)
@classmethod
@@ -298,14 +327,39 @@ class Link(KeyBasedCompareMixin):
url = _ensure_quoted_url(urllib.parse.urljoin(base_url, href))
pyrequire = anchor_attribs.get("data-requires-python")
yanked_reason = anchor_attribs.get("data-yanked")
dist_info_metadata = anchor_attribs.get("data-dist-info-metadata")
# PEP 714: Indexes must use the name data-core-metadata, but
# clients should support the old name as a fallback for compatibility.
metadata_info = anchor_attribs.get("data-core-metadata")
if metadata_info is None:
metadata_info = anchor_attribs.get("data-dist-info-metadata")
# The metadata info value may be the string "true", or a string of
# the form "hashname=hashval"
if metadata_info == "true":
# The file exists, but there are no hashes
metadata_file_data = MetadataFile(None)
elif metadata_info is None:
# The file does not exist
metadata_file_data = None
else:
# The file exists, and hashes have been supplied
hashname, sep, hashval = metadata_info.partition("=")
if sep == "=":
metadata_file_data = MetadataFile(supported_hashes({hashname: hashval}))
else:
# Error - data is wrong. Treat as no hashes supplied.
logger.debug(
"Index returned invalid data-dist-info-metadata value: %s",
metadata_info,
)
metadata_file_data = MetadataFile(None)
return cls(
url,
comes_from=page_url,
requires_python=pyrequire,
yanked_reason=yanked_reason,
dist_info_metadata=dist_info_metadata,
metadata_file_data=metadata_file_data,
)
def __str__(self) -> str:
@@ -407,17 +461,13 @@ class Link(KeyBasedCompareMixin):
return match.group(1)
def metadata_link(self) -> Optional["Link"]:
"""Implementation of PEP 658 parsing."""
# Note that Link.from_element() parsing the "data-dist-info-metadata" attribute
# from an HTML anchor tag is typically how the Link.dist_info_metadata attribute
# gets set.
if self.dist_info_metadata is None:
"""Return a link to the associated core metadata file (if any)."""
if self.metadata_file_data is None:
return None
metadata_url = f"{self.url_without_fragment}.metadata"
metadata_link_hash = LinkHash.parse_pep658_hash(self.dist_info_metadata)
if metadata_link_hash is None:
if self.metadata_file_data.hashes is None:
return Link(metadata_url)
return Link(metadata_url, hashes=metadata_link_hash.as_dict())
return Link(metadata_url, hashes=self.metadata_file_data.hashes)
def as_hashes(self) -> Hashes:
return Hashes({k: [v] for k, v in self._hashes.items()})
@@ -136,6 +136,6 @@ class SearchScope:
index_urls = self.index_urls
if project_name in self.index_lookup:
index_urls = [self.index_lookup[project_name]]
else:
elif self.index_urls:
index_urls = [self.index_urls[0]]
return [mkurl_pypi_url(url) for url in index_urls]
+3 -1
View File
@@ -514,7 +514,9 @@ class MultiDomainBasicAuth(AuthBase):
# Consume content and release the original connection to allow our new
# request to reuse the same one.
resp.content
# The result of the assignment isn't used, it's just needed to consume
# the content.
_ = resp.content
resp.raw.release_conn()
# Add our new username and password to the request
@@ -5,12 +5,15 @@ import logging
from typing import Callable, Dict, List, NamedTuple, Optional, Set, Tuple
from pipenv.patched.pip._vendor.packaging.requirements import Requirement
from pipenv.patched.pip._vendor.packaging.specifiers import LegacySpecifier
from pipenv.patched.pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pipenv.patched.pip._vendor.packaging.version import LegacyVersion
from pipenv.patched.pip._internal.distributions import make_distribution_for_install_requirement
from pipenv.patched.pip._internal.metadata import get_default_environment
from pipenv.patched.pip._internal.metadata.base import DistributionVersion
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
from pipenv.patched.pip._internal.utils.deprecation import deprecated
logger = logging.getLogger(__name__)
@@ -57,6 +60,8 @@ def check_package_set(
package name and returns a boolean.
"""
warn_legacy_versions_and_specifiers(package_set)
missing = {}
conflicting = {}
@@ -147,3 +152,36 @@ def _create_whitelist(
break
return packages_affected
def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None:
for project_name, package_details in package_set.items():
if isinstance(package_details.version, LegacyVersion):
deprecated(
reason=(
f"{project_name} {package_details.version} "
f"has a non-standard version number."
),
replacement=(
f"to upgrade to a newer version of {project_name} "
f"or contact the author to suggest that they "
f"release a version with a conforming version number"
),
issue=12063,
gone_in="23.3",
)
for dep in package_details.dependencies:
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
deprecated(
reason=(
f"{project_name} {package_details.version} "
f"has a non-standard dependency specifier {dep}."
),
replacement=(
f"to upgrade to a newer version of {project_name} "
f"or contact the author to suggest that they "
f"release a version with a conforming dependency specifiers"
),
issue=12063,
gone_in="23.3",
)
@@ -352,7 +352,7 @@ class RequirementPreparer:
# a surprising hash mismatch in the future.
# file:/// URLs aren't pinnable, so don't complain about them
# not being pinned.
if req.original_link is None and not req.is_pinned:
if not req.is_direct and not req.is_pinned:
raise HashUnpinned()
# If known-good hashes are missing for this requirement,
@@ -410,7 +410,7 @@ class RequirementPreparer:
# NB: raw_name will fall back to the name from the install requirement if
# the Name: field is not present, but it's noted in the raw_name docstring
# that that should NEVER happen anyway.
if metadata_dist.raw_name != req.req.name:
if canonicalize_name(metadata_dist.raw_name) != canonicalize_name(req.req.name):
raise MetadataInconsistent(
req, "Name", req.req.name, metadata_dist.raw_name
)
@@ -471,6 +471,19 @@ class RequirementPreparer:
logger.debug("Downloading link %s to %s", link, filepath)
req = links_to_fully_download[link]
req.local_file_path = filepath
# TODO: This needs fixing for sdists
# This is an emergency fix for #11847, which reports that
# distributions get downloaded twice when metadata is loaded
# from a PEP 658 standalone metadata file. Setting _downloaded
# fixes this for wheels, but breaks the sdist case (tests
# test_download_metadata). As PyPI is currently only serving
# metadata for wheels, this is not an immediate issue.
# Fixing the problem properly looks like it will require a
# complete refactoring of the `prepare_linked_requirements_more`
# logic, and I haven't a clue where to start on that, so for now
# I have fixed the issue *just* for wheels.
if req.is_wheel:
self._downloaded[req.link.url] = filepath
# This step is necessary to ensure all lazy wheels are processed
# successfully by the 'download', 'wheel', and 'install' commands.
@@ -104,6 +104,8 @@ class InstallRequirement:
if link.is_file:
self.source_dir = os.path.normpath(os.path.abspath(link.file_path))
# original_link is the direct URL that was provided by the user for the
# requirement, either directly or via a constraints file.
if link is None and req and req.url:
# PEP 508 URL requirement
link = Link(req.url)
@@ -244,6 +246,11 @@ class InstallRequirement:
def specifier(self) -> SpecifierSet:
return self.req.specifier
@property
def is_direct(self) -> bool:
"""Whether this requirement was specified as a direct URL."""
return self.original_link is not None
@property
def is_pinned(self) -> bool:
"""Return whether I am pinned to an exact version.
@@ -293,7 +300,7 @@ class InstallRequirement:
good_hashes = self.hash_options.copy()
if trust_internet:
link = self.link
elif self.original_link and self.user_supplied:
elif self.is_direct and self.user_supplied:
link = self.original_link
else:
link = None
@@ -507,7 +514,7 @@ class InstallRequirement:
self.unpacked_source_directory,
backend,
backend_path=backend_path,
python_executable=os.getenv('PIP_PYTHON_PATH', sys.executable)
python_executable=os.getenv('PIPENV_PYTHON_PATH', sys.executable)
)
def isolated_editable_sanity_check(self) -> None:
@@ -805,7 +812,7 @@ class InstallRequirement:
req_description=str(self.req),
pycompile=pycompile,
warn_script_location=warn_script_location,
direct_url=self.download_info if self.original_link else None,
direct_url=self.download_info if self.is_direct else None,
requested=self.user_supplied,
)
self.install_succeeded = True
@@ -2,9 +2,12 @@ import logging
from collections import OrderedDict
from typing import Dict, List
from pipenv.patched.pip._vendor.packaging.specifiers import LegacySpecifier
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.pip._vendor.packaging.version import LegacyVersion
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
from pipenv.patched.pip._internal.utils.deprecation import deprecated
logger = logging.getLogger(__name__)
@@ -80,3 +83,37 @@ class RequirementSet:
for install_req in self.all_requirements
if not install_req.constraint and not install_req.satisfied_by
]
def warn_legacy_versions_and_specifiers(self) -> None:
for req in self.requirements_to_install:
version = req.get_dist().version
if isinstance(version, LegacyVersion):
deprecated(
reason=(
f"pip has selected the non standard version {version} "
f"of {req}. In the future this version will be "
f"ignored as it isn't standard compliant."
),
replacement=(
"set or update constraints to select another version "
"or contact the package author to fix the version number"
),
issue=12063,
gone_in="23.3",
)
for dep in req.get_dist().iter_dependencies():
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
deprecated(
reason=(
f"pip has selected {req} {version} which has non "
f"standard dependency specifier {dep}. "
f"In the future this version of {req} will be "
f"ignored as it isn't standard compliant."
),
replacement=(
"set or update constraints to select another version "
"or contact the package author to fix the version number"
),
issue=12063,
gone_in="23.3",
)
@@ -345,6 +345,7 @@ class AlreadyInstalledCandidate(Candidate):
self.dist = dist
self._ireq = _make_install_req_from_dist(dist, template)
self._factory = factory
self._version = None
# This is just logging some messages, so we can do it eagerly.
# The returned dist would be exactly the same as self.dist because we
@@ -380,7 +381,9 @@ class AlreadyInstalledCandidate(Candidate):
@property
def version(self) -> CandidateVersion:
return self.dist.version
if self._version is None:
self._version = self.dist.version
return self._version
@property
def is_editable(self) -> bool:
@@ -20,7 +20,7 @@ class PipReporter(BaseReporter):
"requirements. This could take a while."
),
8: (
"pip is looking at multiple versions of {package_name} to "
"pip is still looking at multiple versions of {package_name} to "
"determine which version is compatible with other "
"requirements. This could take a while."
),
@@ -159,6 +159,9 @@ class Resolver(BaseResolver):
reqs = req_set.all_requirements
self.factory.preparer.prepare_linked_requirements_more(reqs)
for req in reqs:
req.prepared = True
req.needs_more_preparation = False
return req_set
def get_installation_order(
+4 -4
View File
@@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import os
import sys
from typing import Optional, Tuple
@@ -20,8 +17,11 @@ def glibc_version_string_confstr() -> Optional[str]:
if sys.platform == "win32":
return None
try:
gnu_libc_version = os.confstr("CS_GNU_LIBC_VERSION")
if gnu_libc_version is None:
return None
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17":
_, version = os.confstr("CS_GNU_LIBC_VERSION").split()
_, version = gnu_libc_version.split()
except (AttributeError, OSError, ValueError):
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
return None
+7 -2
View File
@@ -127,10 +127,15 @@ def get_prog() -> str:
# Tenacity raises RetryError by default, explicitly raise the original exception
@retry(reraise=True, stop=stop_after_delay(3), wait=wait_fixed(0.5))
def rmtree(dir: str, ignore_errors: bool = False) -> None:
shutil.rmtree(dir, ignore_errors=ignore_errors, onerror=rmtree_errorhandler)
if sys.version_info >= (3, 12):
shutil.rmtree(dir, ignore_errors=ignore_errors, onexc=rmtree_errorhandler)
else:
shutil.rmtree(dir, ignore_errors=ignore_errors, onerror=rmtree_errorhandler)
def rmtree_errorhandler(func: Callable[..., Any], path: str, exc_info: ExcInfo) -> None:
def rmtree_errorhandler(
func: Callable[..., Any], path: str, exc_info: Union[ExcInfo, BaseException]
) -> None:
"""On Windows, the files in .svn are read-only, so when rmtree() tries to
remove them, an exception is thrown. We catch that here, remove the
read-only attribute, and hopefully continue without problems."""
@@ -31,7 +31,7 @@ class Mercurial(VersionControl):
@staticmethod
def get_base_rev_args(rev: str) -> List[str]:
return [rev]
return ["-r", rev]
def fetch_new(
self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
@@ -1,4 +1,4 @@
from .core import contents, where
__all__ = ["contents", "where"]
__version__ = "2022.12.07"
__version__ = "2023.05.07"
@@ -4525,3 +4525,65 @@ BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu
9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O
be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=
-----END CERTIFICATE-----
# Issuer: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY
# Subject: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY
# Label: "BJCA Global Root CA1"
# Serial: 113562791157148395269083148143378328608
# MD5 Fingerprint: 42:32:99:76:43:33:36:24:35:07:82:9b:28:f9:d0:90
# SHA1 Fingerprint: d5:ec:8d:7b:4c:ba:79:f4:e7:e8:cb:9d:6b:ae:77:83:10:03:21:6a
# SHA256 Fingerprint: f3:89:6f:88:fe:7c:0a:88:27:66:a7:fa:6a:d2:74:9f:b5:7a:7f:3e:98:fb:76:9c:1f:a7:b0:9c:2c:44:d5:ae
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU
MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI
T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz
MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF
SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh
bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z
xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ
spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5
58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR
at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll
5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq
nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK
V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/
pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO
z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn
jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+
WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF
7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4
YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli
awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u
+2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88
X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN
SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo
P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI
+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz
znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9
eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2
YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy
r/6zcCwupvI=
-----END CERTIFICATE-----
# Issuer: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY
# Subject: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY
# Label: "BJCA Global Root CA2"
# Serial: 58605626836079930195615843123109055211
# MD5 Fingerprint: 5e:0a:f6:47:5f:a6:14:e8:11:01:95:3f:4d:01:eb:3c
# SHA1 Fingerprint: f4:27:86:eb:6e:b8:6d:88:31:67:02:fb:ba:66:a4:53:00:aa:7a:a6
# SHA256 Fingerprint: 57:4d:f6:93:1e:27:80:39:66:7b:72:0a:fd:c1:60:0f:c2:7e:b6:6d:d3:09:29:79:fb:73:85:64:87:21:28:82
-----BEGIN CERTIFICATE-----
MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw
CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ
VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy
MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ
TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS
b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B
IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+
+kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK
sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA
94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B
43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==
-----END CERTIFICATE-----
@@ -1,5 +1,3 @@
Copyright Jason R. Coombs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
@@ -13,11 +13,8 @@ The package resource API is designed to work with normal filesystem packages,
.zip files and with custom PEP 302 loaders that support the ``get_data()``
method.
This module is deprecated. Users are directed to
`importlib.resources <https://docs.python.org/3/library/importlib.resources.html>`_
and
`importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_
instead.
This module is deprecated. Users are directed to :mod:`importlib.resources`,
:mod:`importlib.metadata` and :pypi:`packaging` instead.
"""
import sys
@@ -118,7 +115,12 @@ _namespace_handlers = None
_namespace_packages = None
warnings.warn("pkg_resources is deprecated as an API", DeprecationWarning)
warnings.warn(
"pkg_resources is deprecated as an API. "
"See https://setuptools.pypa.io/en/latest/pkg_resources.html",
DeprecationWarning,
stacklevel=2
)
_PEP440_FALLBACK = re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I)
@@ -1659,10 +1661,9 @@ is not allowed.
# for compatibility, warn; in future
# raise ValueError(msg)
warnings.warn(
issue_warning(
msg[:-1] + " and will raise exceptions in a future release.",
DeprecationWarning,
stacklevel=4,
)
def _get(self, path):
@@ -6,17 +6,20 @@ from __future__ import annotations
import os
import sys
from pathlib import Path
if sys.version_info >= (3, 8): # pragma: no cover (py38+)
from typing import Literal
else: # pragma: no cover (py38+)
from pipenv.patched.pip._vendor.typing_extensions import Literal
from typing import TYPE_CHECKING
from .api import PlatformDirsABC
from .version import __version__
from .version import __version_tuple__ as __version_info__
if TYPE_CHECKING:
from pathlib import Path
if sys.version_info >= (3, 8): # pragma: no cover (py38+)
from typing import Literal
else: # pragma: no cover (py38+)
from pipenv.patched.pip._vendor.typing_extensions import Literal
def _set_platform_dir_class() -> type[PlatformDirsABC]:
if sys.platform == "win32":
@@ -48,8 +51,8 @@ def user_data_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
roaming: bool = False,
ensure_exists: bool = False,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -72,8 +75,8 @@ def site_data_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
multipath: bool = False,
ensure_exists: bool = False,
multipath: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -96,8 +99,8 @@ def user_config_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
roaming: bool = False,
ensure_exists: bool = False,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -120,8 +123,8 @@ def site_config_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
multipath: bool = False,
ensure_exists: bool = False,
multipath: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -144,8 +147,8 @@ def user_cache_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
opinion: bool = True,
ensure_exists: bool = False,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -168,8 +171,8 @@ def site_cache_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
opinion: bool = True,
ensure_exists: bool = False,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -192,8 +195,8 @@ def user_state_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
roaming: bool = False,
ensure_exists: bool = False,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -216,8 +219,8 @@ def user_log_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
opinion: bool = True,
ensure_exists: bool = False,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -237,18 +240,36 @@ def user_log_dir(
def user_documents_dir() -> str:
"""
:returns: documents directory tied to the user
"""
""":returns: documents directory tied to the user"""
return PlatformDirs().user_documents_dir
def user_downloads_dir() -> str:
""":returns: downloads directory tied to the user"""
return PlatformDirs().user_downloads_dir
def user_pictures_dir() -> str:
""":returns: pictures directory tied to the user"""
return PlatformDirs().user_pictures_dir
def user_videos_dir() -> str:
""":returns: videos directory tied to the user"""
return PlatformDirs().user_videos_dir
def user_music_dir() -> str:
""":returns: music directory tied to the user"""
return PlatformDirs().user_music_dir
def user_runtime_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
opinion: bool = True,
ensure_exists: bool = False,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> str:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -271,8 +292,8 @@ def user_data_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
roaming: bool = False,
ensure_exists: bool = False,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -295,8 +316,8 @@ def site_data_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
multipath: bool = False,
ensure_exists: bool = False,
multipath: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -319,8 +340,8 @@ def user_config_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
roaming: bool = False,
ensure_exists: bool = False,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -343,8 +364,8 @@ def site_config_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
multipath: bool = False,
ensure_exists: bool = False,
multipath: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -367,8 +388,8 @@ def site_cache_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
opinion: bool = True,
ensure_exists: bool = False,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -391,8 +412,8 @@ def user_cache_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
opinion: bool = True,
ensure_exists: bool = False,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -415,8 +436,8 @@ def user_state_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
roaming: bool = False,
ensure_exists: bool = False,
roaming: bool = False, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -439,8 +460,8 @@ def user_log_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
opinion: bool = True,
ensure_exists: bool = False,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -460,18 +481,36 @@ def user_log_path(
def user_documents_path() -> Path:
"""
:returns: documents path tied to the user
"""
""":returns: documents path tied to the user"""
return PlatformDirs().user_documents_path
def user_downloads_path() -> Path:
""":returns: downloads path tied to the user"""
return PlatformDirs().user_downloads_path
def user_pictures_path() -> Path:
""":returns: pictures path tied to the user"""
return PlatformDirs().user_pictures_path
def user_videos_path() -> Path:
""":returns: videos path tied to the user"""
return PlatformDirs().user_videos_path
def user_music_path() -> Path:
""":returns: music path tied to the user"""
return PlatformDirs().user_music_path
def user_runtime_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
opinion: bool = True,
ensure_exists: bool = False,
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> Path:
"""
:param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`.
@@ -502,6 +541,10 @@ __all__ = [
"user_state_dir",
"user_log_dir",
"user_documents_dir",
"user_downloads_dir",
"user_pictures_dir",
"user_videos_dir",
"user_music_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
@@ -512,6 +555,10 @@ __all__ = [
"user_state_path",
"user_log_path",
"user_documents_path",
"user_downloads_path",
"user_pictures_path",
"user_videos_path",
"user_music_path",
"user_runtime_path",
"site_data_path",
"site_config_path",
@@ -1,3 +1,4 @@
"""Main entry point."""
from __future__ import annotations
from pipenv.patched.pip._vendor.platformdirs import PlatformDirs, __version__
@@ -9,6 +10,10 @@ PROPS = (
"user_state_dir",
"user_log_dir",
"user_documents_dir",
"user_downloads_dir",
"user_pictures_dir",
"user_videos_dir",
"user_music_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
@@ -17,30 +22,31 @@ PROPS = (
def main() -> None:
"""Run main entry point."""
app_name = "MyApp"
app_author = "MyCompany"
print(f"-- platformdirs {__version__} --")
print(f"-- platformdirs {__version__} --") # noqa: T201
print("-- app dirs (with optional 'version')")
print("-- app dirs (with optional 'version')") # noqa: T201
dirs = PlatformDirs(app_name, app_author, version="1.0")
for prop in PROPS:
print(f"{prop}: {getattr(dirs, prop)}")
print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201
print("\n-- app dirs (without optional 'version')")
print("\n-- app dirs (without optional 'version')") # noqa: T201
dirs = PlatformDirs(app_name, app_author)
for prop in PROPS:
print(f"{prop}: {getattr(dirs, prop)}")
print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201
print("\n-- app dirs (without optional 'appauthor')")
print("\n-- app dirs (without optional 'appauthor')") # noqa: T201
dirs = PlatformDirs(app_name)
for prop in PROPS:
print(f"{prop}: {getattr(dirs, prop)}")
print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201
print("\n-- app dirs (with disabled 'appauthor')")
print("\n-- app dirs (with disabled 'appauthor')") # noqa: T201
dirs = PlatformDirs(app_name, appauthor=False)
for prop in PROPS:
print(f"{prop}: {getattr(dirs, prop)}")
print(f"{prop}: {getattr(dirs, prop)}") # noqa: T201
if __name__ == "__main__":
@@ -1,3 +1,4 @@
"""Android."""
from __future__ import annotations
import os
@@ -30,7 +31,8 @@ class Android(PlatformDirsABC):
@property
def user_config_dir(self) -> str:
"""
:return: config directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/shared_prefs/<AppName>``
:return: config directory tied to the user, e.g. \
``/data/user/<userid>/<packagename>/shared_prefs/<AppName>``
"""
return self._append_app_name_and_version(cast(str, _android_folder()), "shared_prefs")
@@ -62,16 +64,34 @@ class Android(PlatformDirsABC):
"""
path = self.user_cache_dir
if self.opinion:
path = os.path.join(path, "log")
path = os.path.join(path, "log") # noqa: PTH118
return path
@property
def user_documents_dir(self) -> str:
"""
:return: documents directory tied to the user e.g. ``/storage/emulated/0/Documents``
"""
""":return: documents directory tied to the user e.g. ``/storage/emulated/0/Documents``"""
return _android_documents_folder()
@property
def user_downloads_dir(self) -> str:
""":return: downloads directory tied to the user e.g. ``/storage/emulated/0/Downloads``"""
return _android_downloads_folder()
@property
def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user e.g. ``/storage/emulated/0/Pictures``"""
return _android_pictures_folder()
@property
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user e.g. ``/storage/emulated/0/DCIM/Camera``"""
return _android_videos_folder()
@property
def user_music_dir(self) -> str:
""":return: music directory tied to the user e.g. ``/storage/emulated/0/Music``"""
return _android_music_folder()
@property
def user_runtime_dir(self) -> str:
"""
@@ -80,20 +100,20 @@ class Android(PlatformDirsABC):
"""
path = self.user_cache_dir
if self.opinion:
path = os.path.join(path, "tmp")
path = os.path.join(path, "tmp") # noqa: PTH118
return path
@lru_cache(maxsize=1)
def _android_folder() -> str | None:
""":return: base folder for the Android OS or None if cannot be found"""
""":return: base folder for the Android OS or None if it cannot be found"""
try:
# First try to get path to android app via pyjnius
from jnius import autoclass
Context = autoclass("android.content.Context") # noqa: N806
result: str | None = Context.getFilesDir().getParentFile().getAbsolutePath()
except Exception:
context = autoclass("android.content.Context")
result: str | None = context.getFilesDir().getParentFile().getAbsolutePath()
except Exception: # noqa: BLE001
# if fails find an android folder looking path on the sys.path
pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
for path in sys.path:
@@ -112,15 +132,79 @@ def _android_documents_folder() -> str:
try:
from jnius import autoclass
Context = autoclass("android.content.Context") # noqa: N806
Environment = autoclass("android.os.Environment") # noqa: N806
documents_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
except Exception:
context = autoclass("android.content.Context")
environment = autoclass("android.os.Environment")
documents_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
except Exception: # noqa: BLE001
documents_dir = "/storage/emulated/0/Documents"
return documents_dir
@lru_cache(maxsize=1)
def _android_downloads_folder() -> str:
""":return: downloads folder for the Android OS"""
# Get directories with pyjnius
try:
from jnius import autoclass
context = autoclass("android.content.Context")
environment = autoclass("android.os.Environment")
downloads_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
except Exception: # noqa: BLE001
downloads_dir = "/storage/emulated/0/Downloads"
return downloads_dir
@lru_cache(maxsize=1)
def _android_pictures_folder() -> str:
""":return: pictures folder for the Android OS"""
# Get directories with pyjnius
try:
from jnius import autoclass
context = autoclass("android.content.Context")
environment = autoclass("android.os.Environment")
pictures_dir: str = context.getExternalFilesDir(environment.DIRECTORY_PICTURES).getAbsolutePath()
except Exception: # noqa: BLE001
pictures_dir = "/storage/emulated/0/Pictures"
return pictures_dir
@lru_cache(maxsize=1)
def _android_videos_folder() -> str:
""":return: videos folder for the Android OS"""
# Get directories with pyjnius
try:
from jnius import autoclass
context = autoclass("android.content.Context")
environment = autoclass("android.os.Environment")
videos_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DCIM).getAbsolutePath()
except Exception: # noqa: BLE001
videos_dir = "/storage/emulated/0/DCIM/Camera"
return videos_dir
@lru_cache(maxsize=1)
def _android_music_folder() -> str:
""":return: music folder for the Android OS"""
# Get directories with pyjnius
try:
from jnius import autoclass
context = autoclass("android.content.Context")
environment = autoclass("android.os.Environment")
music_dir: str = context.getExternalFilesDir(environment.DIRECTORY_MUSIC).getAbsolutePath()
except Exception: # noqa: BLE001
music_dir = "/storage/emulated/0/Music"
return music_dir
__all__ = [
"Android",
]
+57 -13
View File
@@ -1,29 +1,33 @@
"""Base API."""
from __future__ import annotations
import os
import sys
from abc import ABC, abstractmethod
from pathlib import Path
from typing import TYPE_CHECKING
if sys.version_info >= (3, 8): # pragma: no branch
from typing import Literal # pragma: no cover
if TYPE_CHECKING:
import sys
if sys.version_info >= (3, 8): # pragma: no cover (py38+)
from typing import Literal
else: # pragma: no cover (py38+)
from pipenv.patched.pip._vendor.typing_extensions import Literal
class PlatformDirsABC(ABC):
"""
Abstract base class for platform directories.
"""
"""Abstract base class for platform directories."""
def __init__(
def __init__( # noqa: PLR0913
self,
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
version: str | None = None,
roaming: bool = False,
multipath: bool = False,
opinion: bool = True,
ensure_exists: bool = False,
):
roaming: bool = False, # noqa: FBT001, FBT002
multipath: bool = False, # noqa: FBT001, FBT002
opinion: bool = True, # noqa: FBT001, FBT002
ensure_exists: bool = False, # noqa: FBT001, FBT002
) -> None:
"""
Create a new platform directory.
@@ -70,7 +74,7 @@ class PlatformDirsABC(ABC):
params.append(self.appname)
if self.version:
params.append(self.version)
path = os.path.join(base[0], *params)
path = os.path.join(base[0], *params) # noqa: PTH118
self._optionally_create_directory(path)
return path
@@ -123,6 +127,26 @@ class PlatformDirsABC(ABC):
def user_documents_dir(self) -> str:
""":return: documents directory tied to the user"""
@property
@abstractmethod
def user_downloads_dir(self) -> str:
""":return: downloads directory tied to the user"""
@property
@abstractmethod
def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user"""
@property
@abstractmethod
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user"""
@property
@abstractmethod
def user_music_dir(self) -> str:
""":return: music directory tied to the user"""
@property
@abstractmethod
def user_runtime_dir(self) -> str:
@@ -173,6 +197,26 @@ class PlatformDirsABC(ABC):
""":return: documents path tied to the user"""
return Path(self.user_documents_dir)
@property
def user_downloads_path(self) -> Path:
""":return: downloads path tied to the user"""
return Path(self.user_downloads_dir)
@property
def user_pictures_path(self) -> Path:
""":return: pictures path tied to the user"""
return Path(self.user_pictures_dir)
@property
def user_videos_path(self) -> Path:
""":return: videos path tied to the user"""
return Path(self.user_videos_dir)
@property
def user_music_path(self) -> Path:
""":return: music path tied to the user"""
return Path(self.user_music_dir)
@property
def user_runtime_path(self) -> Path:
""":return: runtime path tied to the user"""
@@ -1,6 +1,7 @@
"""macOS."""
from __future__ import annotations
import os
import os.path
from .api import PlatformDirsABC
@@ -17,7 +18,7 @@ class MacOS(PlatformDirsABC):
@property
def user_data_dir(self) -> str:
""":return: data directory tied to the user, e.g. ``~/Library/Application Support/$appname/$version``"""
return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support"))
return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support")) # noqa: PTH111
@property
def site_data_dir(self) -> str:
@@ -37,7 +38,7 @@ class MacOS(PlatformDirsABC):
@property
def user_cache_dir(self) -> str:
""":return: cache directory tied to the user, e.g. ``~/Library/Caches/$appname/$version``"""
return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches"))
return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches")) # noqa: PTH111
@property
def site_cache_dir(self) -> str:
@@ -52,17 +53,37 @@ class MacOS(PlatformDirsABC):
@property
def user_log_dir(self) -> str:
""":return: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``"""
return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs"))
return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs")) # noqa: PTH111
@property
def user_documents_dir(self) -> str:
""":return: documents directory tied to the user, e.g. ``~/Documents``"""
return os.path.expanduser("~/Documents")
return os.path.expanduser("~/Documents") # noqa: PTH111
@property
def user_downloads_dir(self) -> str:
""":return: downloads directory tied to the user, e.g. ``~/Downloads``"""
return os.path.expanduser("~/Downloads") # noqa: PTH111
@property
def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user, e.g. ``~/Pictures``"""
return os.path.expanduser("~/Pictures") # noqa: PTH111
@property
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user, e.g. ``~/Movies``"""
return os.path.expanduser("~/Movies") # noqa: PTH111
@property
def user_music_dir(self) -> str:
""":return: music directory tied to the user, e.g. ``~/Music``"""
return os.path.expanduser("~/Music") # noqa: PTH111
@property
def user_runtime_dir(self) -> str:
""":return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``"""
return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems"))
return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems")) # noqa: PTH111
__all__ = [
+63 -34
View File
@@ -1,3 +1,4 @@
"""Unix."""
from __future__ import annotations
import os
@@ -7,12 +8,14 @@ from pathlib import Path
from .api import PlatformDirsABC
if sys.platform.startswith("linux"): # pragma: no branch # no op check, only to please the type checker
from os import getuid
else:
if sys.platform == "win32":
def getuid() -> int:
raise RuntimeError("should only be used on Linux")
msg = "should only be used on Unix"
raise RuntimeError(msg)
else:
from os import getuid
class Unix(PlatformDirsABC):
@@ -36,7 +39,7 @@ class Unix(PlatformDirsABC):
"""
path = os.environ.get("XDG_DATA_HOME", "")
if not path.strip():
path = os.path.expanduser("~/.local/share")
path = os.path.expanduser("~/.local/share") # noqa: PTH111
return self._append_app_name_and_version(path)
@property
@@ -56,7 +59,7 @@ class Unix(PlatformDirsABC):
path_list = path.split(os.pathsep)
if not self.multipath:
path_list = path_list[0:1]
path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list]
path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list] # noqa: PTH111
return os.pathsep.join(path_list)
@property
@@ -67,7 +70,7 @@ class Unix(PlatformDirsABC):
"""
path = os.environ.get("XDG_CONFIG_HOME", "")
if not path.strip():
path = os.path.expanduser("~/.config")
path = os.path.expanduser("~/.config") # noqa: PTH111
return self._append_app_name_and_version(path)
@property
@@ -91,15 +94,13 @@ class Unix(PlatformDirsABC):
"""
path = os.environ.get("XDG_CACHE_HOME", "")
if not path.strip():
path = os.path.expanduser("~/.cache")
path = os.path.expanduser("~/.cache") # noqa: PTH111
return self._append_app_name_and_version(path)
@property
def site_cache_dir(self) -> str:
"""
:return: cache directory shared by users, e.g. ``/var/tmp/$appname/$version``
"""
return self._append_app_name_and_version("/var/tmp")
""":return: cache directory shared by users, e.g. ``/var/tmp/$appname/$version``"""
return self._append_app_name_and_version("/var/tmp") # noqa: S108
@property
def user_state_dir(self) -> str:
@@ -109,41 +110,60 @@ class Unix(PlatformDirsABC):
"""
path = os.environ.get("XDG_STATE_HOME", "")
if not path.strip():
path = os.path.expanduser("~/.local/state")
path = os.path.expanduser("~/.local/state") # noqa: PTH111
return self._append_app_name_and_version(path)
@property
def user_log_dir(self) -> str:
"""
:return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it
"""
""":return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it"""
path = self.user_state_dir
if self.opinion:
path = os.path.join(path, "log")
path = os.path.join(path, "log") # noqa: PTH118
return path
@property
def user_documents_dir(self) -> str:
"""
:return: documents directory tied to the user, e.g. ``~/Documents``
"""
documents_dir = _get_user_dirs_folder("XDG_DOCUMENTS_DIR")
if documents_dir is None:
documents_dir = os.environ.get("XDG_DOCUMENTS_DIR", "").strip()
if not documents_dir:
documents_dir = os.path.expanduser("~/Documents")
""":return: documents directory tied to the user, e.g. ``~/Documents``"""
return _get_user_media_dir("XDG_DOCUMENTS_DIR", "~/Documents")
return documents_dir
@property
def user_downloads_dir(self) -> str:
""":return: downloads directory tied to the user, e.g. ``~/Downloads``"""
return _get_user_media_dir("XDG_DOWNLOAD_DIR", "~/Downloads")
@property
def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user, e.g. ``~/Pictures``"""
return _get_user_media_dir("XDG_PICTURES_DIR", "~/Pictures")
@property
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user, e.g. ``~/Videos``"""
return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos")
@property
def user_music_dir(self) -> str:
""":return: music directory tied to the user, e.g. ``~/Music``"""
return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music")
@property
def user_runtime_dir(self) -> str:
"""
:return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or
``$XDG_RUNTIME_DIR/$appname/$version``
``$XDG_RUNTIME_DIR/$appname/$version``.
For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/user/$(id -u)/$appname/$version`` if
exists, otherwise ``/tmp/runtime-$(id -u)/$appname/$version``, if``$XDG_RUNTIME_DIR``
is not set.
"""
path = os.environ.get("XDG_RUNTIME_DIR", "")
if not path.strip():
path = f"/run/user/{getuid()}"
if sys.platform.startswith(("freebsd", "openbsd", "netbsd")):
path = f"/var/run/user/{getuid()}"
if not Path(path).exists():
path = f"/tmp/runtime-{getuid()}" # noqa: S108
else:
path = f"/run/user/{getuid()}"
return self._append_app_name_and_version(path)
@property
@@ -168,13 +188,23 @@ class Unix(PlatformDirsABC):
return Path(directory)
def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str:
media_dir = _get_user_dirs_folder(env_var)
if media_dir is None:
media_dir = os.environ.get(env_var, "").strip()
if not media_dir:
media_dir = os.path.expanduser(fallback_tilde_path) # noqa: PTH111
return media_dir
def _get_user_dirs_folder(key: str) -> str | None:
"""Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/"""
user_dirs_config_path = os.path.join(Unix().user_config_dir, "user-dirs.dirs")
if os.path.exists(user_dirs_config_path):
"""Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/."""
user_dirs_config_path = Path(Unix().user_config_dir) / "user-dirs.dirs"
if user_dirs_config_path.exists():
parser = ConfigParser()
with open(user_dirs_config_path) as stream:
with user_dirs_config_path.open() as stream:
# Add fake section header, so ConfigParser doesn't complain
parser.read_string(f"[top]\n{stream.read()}")
@@ -183,8 +213,7 @@ def _get_user_dirs_folder(key: str) -> str | None:
path = parser["top"][key].strip('"')
# Handle relative home paths
path = path.replace("$HOME", os.path.expanduser("~"))
return path
return path.replace("$HOME", os.path.expanduser("~")) # noqa: PTH111
return None
@@ -1,4 +1,4 @@
# file generated by setuptools_scm
# don't change, don't track in version control
__version__ = version = '3.2.0'
__version_tuple__ = version_tuple = (3, 2, 0)
__version__ = version = '3.8.1'
__version_tuple__ = version_tuple = (3, 8, 1)
@@ -1,16 +1,21 @@
"""Windows."""
from __future__ import annotations
import ctypes
import os
import sys
from functools import lru_cache
from typing import Callable
from typing import TYPE_CHECKING
from .api import PlatformDirsABC
if TYPE_CHECKING:
from collections.abc import Callable
class Windows(PlatformDirsABC):
"""`MSDN on where to store app data files
"""
`MSDN on where to store app data files
<http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120>`_.
Makes use of the
`appname <platformdirs.api.PlatformDirsABC.appname>`,
@@ -43,7 +48,7 @@ class Windows(PlatformDirsABC):
params.append(opinion_value)
if self.version:
params.append(self.version)
path = os.path.join(path, *params)
path = os.path.join(path, *params) # noqa: PTH118
self._optionally_create_directory(path)
return path
@@ -85,36 +90,53 @@ class Windows(PlatformDirsABC):
@property
def user_log_dir(self) -> str:
"""
:return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it
"""
""":return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it"""
path = self.user_data_dir
if self.opinion:
path = os.path.join(path, "Logs")
path = os.path.join(path, "Logs") # noqa: PTH118
self._optionally_create_directory(path)
return path
@property
def user_documents_dir(self) -> str:
"""
:return: documents directory tied to the user e.g. ``%USERPROFILE%\\Documents``
"""
""":return: documents directory tied to the user e.g. ``%USERPROFILE%\\Documents``"""
return os.path.normpath(get_win_folder("CSIDL_PERSONAL"))
@property
def user_downloads_dir(self) -> str:
""":return: downloads directory tied to the user e.g. ``%USERPROFILE%\\Downloads``"""
return os.path.normpath(get_win_folder("CSIDL_DOWNLOADS"))
@property
def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user e.g. ``%USERPROFILE%\\Pictures``"""
return os.path.normpath(get_win_folder("CSIDL_MYPICTURES"))
@property
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user e.g. ``%USERPROFILE%\\Videos``"""
return os.path.normpath(get_win_folder("CSIDL_MYVIDEO"))
@property
def user_music_dir(self) -> str:
""":return: music directory tied to the user e.g. ``%USERPROFILE%\\Music``"""
return os.path.normpath(get_win_folder("CSIDL_MYMUSIC"))
@property
def user_runtime_dir(self) -> str:
"""
:return: runtime directory tied to the user, e.g.
``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname``
"""
path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp"))
path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp")) # noqa: PTH118
return self._append_parts(path)
def get_win_folder_from_env_vars(csidl_name: str) -> str:
"""Get folder from environment variables."""
if csidl_name == "CSIDL_PERSONAL": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")
result = get_win_folder_if_csidl_name_not_env_var(csidl_name)
if result is not None:
return result
env_var_name = {
"CSIDL_APPDATA": "APPDATA",
@@ -122,28 +144,54 @@ def get_win_folder_from_env_vars(csidl_name: str) -> str:
"CSIDL_LOCAL_APPDATA": "LOCALAPPDATA",
}.get(csidl_name)
if env_var_name is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
msg = f"Unknown CSIDL name: {csidl_name}"
raise ValueError(msg)
result = os.environ.get(env_var_name)
if result is None:
raise ValueError(f"Unset environment variable: {env_var_name}")
msg = f"Unset environment variable: {env_var_name}"
raise ValueError(msg)
return result
def get_win_folder_from_registry(csidl_name: str) -> str:
"""Get folder from the registry.
def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None:
"""Get folder for a CSIDL name that does not exist as an environment variable."""
if csidl_name == "CSIDL_PERSONAL":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents") # noqa: PTH118
This is a fallback technique at best. I'm not sure if using the
registry for this guarantees us the correct answer for all CSIDL_*
names.
if csidl_name == "CSIDL_DOWNLOADS":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Downloads") # noqa: PTH118
if csidl_name == "CSIDL_MYPICTURES":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures") # noqa: PTH118
if csidl_name == "CSIDL_MYVIDEO":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos") # noqa: PTH118
if csidl_name == "CSIDL_MYMUSIC":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Music") # noqa: PTH118
return None
def get_win_folder_from_registry(csidl_name: str) -> str:
"""
Get folder from the registry.
This is a fallback technique at best. I'm not sure if using the registry for these guarantees us the correct answer
for all CSIDL_* names.
"""
shell_folder_name = {
"CSIDL_APPDATA": "AppData",
"CSIDL_COMMON_APPDATA": "Common AppData",
"CSIDL_LOCAL_APPDATA": "Local AppData",
"CSIDL_PERSONAL": "Personal",
"CSIDL_DOWNLOADS": "{374DE290-123F-4565-9164-39C4925E467B}",
"CSIDL_MYPICTURES": "My Pictures",
"CSIDL_MYVIDEO": "My Video",
"CSIDL_MYMUSIC": "My Music",
}.get(csidl_name)
if shell_folder_name is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
msg = f"Unknown CSIDL name: {csidl_name}"
raise ValueError(msg)
if sys.platform != "win32": # only needed for mypy type checker to know that this code runs only on Windows
raise NotImplementedError
import winreg
@@ -155,25 +203,37 @@ def get_win_folder_from_registry(csidl_name: str) -> str:
def get_win_folder_via_ctypes(csidl_name: str) -> str:
"""Get folder with ctypes."""
# There is no 'CSIDL_DOWNLOADS'.
# Use 'CSIDL_PROFILE' (40) and append the default folder 'Downloads' instead.
# https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
csidl_const = {
"CSIDL_APPDATA": 26,
"CSIDL_COMMON_APPDATA": 35,
"CSIDL_LOCAL_APPDATA": 28,
"CSIDL_PERSONAL": 5,
"CSIDL_MYPICTURES": 39,
"CSIDL_MYVIDEO": 14,
"CSIDL_MYMUSIC": 13,
"CSIDL_DOWNLOADS": 40,
}.get(csidl_name)
if csidl_const is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
msg = f"Unknown CSIDL name: {csidl_name}"
raise ValueError(msg)
buf = ctypes.create_unicode_buffer(1024)
windll = getattr(ctypes, "windll") # noqa: B009 # using getattr to avoid false positive with mypy type checker
windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
# Downgrade to short path name if it has highbit chars.
if any(ord(c) > 255 for c in buf):
if any(ord(c) > 255 for c in buf): # noqa: PLR2004
buf2 = ctypes.create_unicode_buffer(1024)
if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
buf = buf2
if csidl_name == "CSIDL_DOWNLOADS":
return os.path.join(buf.value, "Downloads") # noqa: PTH118
return buf.value
+12 -12
View File
@@ -21,12 +21,12 @@
.. _Pygments master branch:
https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from io import StringIO, BytesIO
__version__ = '2.14.0'
__version__ = '2.15.1'
__docformat__ = 'restructuredtext'
__all__ = ['lex', 'format', 'highlight']
@@ -34,7 +34,9 @@ __all__ = ['lex', 'format', 'highlight']
def lex(code, lexer):
"""
Lex ``code`` with ``lexer`` and return an iterable of tokens.
Lex `code` with the `lexer` (must be a `Lexer` instance)
and return an iterable of tokens. Currently, this only calls
`lexer.get_tokens()`.
"""
try:
return lexer.get_tokens(code)
@@ -49,11 +51,12 @@ def lex(code, lexer):
def format(tokens, formatter, outfile=None): # pylint: disable=redefined-builtin
"""
Format a tokenlist ``tokens`` with the formatter ``formatter``.
Format ``tokens`` (an iterable of tokens) with the formatter ``formatter``
(a `Formatter` instance).
If ``outfile`` is given and a valid file object (an object
with a ``write`` method), the result will be written to it, otherwise
it is returned as a string.
If ``outfile`` is given and a valid file object (an object with a
``write`` method), the result will be written to it, otherwise it
is returned as a string.
"""
try:
if not outfile:
@@ -73,10 +76,7 @@ def format(tokens, formatter, outfile=None): # pylint: disable=redefined-builti
def highlight(code, lexer, formatter, outfile=None):
"""
Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
If ``outfile`` is given and a valid file object (an object
with a ``write`` method), the result will be written to it, otherwise
it is returned as a string.
This is the most high-level highlighting function. It combines `lex` and
`format` in one function.
"""
return format(lex(code, lexer), formatter, outfile)
@@ -4,7 +4,7 @@
Main entry point for ``python -m pygments``.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Command line interface.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -185,7 +185,7 @@ def main_inner(parser, argns):
return 0
if argns.V:
print('Pygments version %s, (c) 2006-2022 by Georg Brandl, Matthäus '
print('Pygments version %s, (c) 2006-2023 by Georg Brandl, Matthäus '
'Chajdas and contributors.' % __version__)
return 0
@@ -4,7 +4,7 @@
Format colored console output.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Module that implements the default filter.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -5,7 +5,7 @@
Module containing filter lookup functions and default
filters.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Base formatter class.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -26,7 +26,21 @@ class Formatter:
"""
Converts a token stream to text.
Options accepted:
Formatters should have attributes to help selecting them. These
are similar to the corresponding :class:`~pygments.lexer.Lexer`
attributes.
.. autoattribute:: name
:no-value:
.. autoattribute:: aliases
:no-value:
.. autoattribute:: filenames
:no-value:
You can pass options as keyword arguments to the constructor.
All formatters accept these basic options:
``style``
The style to use, can be a string or a Style subclass
@@ -47,15 +61,19 @@ class Formatter:
support (default: None).
``outencoding``
Overrides ``encoding`` if given.
"""
#: Name of the formatter
#: Full name for the formatter, in human-readable form.
name = None
#: Shortcuts for the formatter
#: A list of short, unique identifiers that can be used to lookup
#: the formatter from a list, e.g. using :func:`.get_formatter_by_name()`.
aliases = []
#: fn match rules
#: A list of fnmatch patterns that match filenames for which this
#: formatter can produce output. The patterns in this list should be unique
#: among all formatters.
filenames = []
#: If True, this formatter outputs Unicode strings when no encoding
@@ -63,6 +81,11 @@ class Formatter:
unicodeoutput = True
def __init__(self, **options):
"""
As with lexers, this constructor takes arbitrary optional arguments,
and if you override it, you should first process your own options, then
call the base class implementation.
"""
self.style = _lookup_style(options.get('style', 'default'))
self.full = get_bool_opt(options, 'full', False)
self.title = options.get('title', '')
@@ -75,18 +98,25 @@ class Formatter:
def get_style_defs(self, arg=''):
"""
Return the style definitions for the current style as a string.
This method must return statements or declarations suitable to define
the current style for subsequent highlighted text (e.g. CSS classes
in the `HTMLFormatter`).
``arg`` is an additional argument whose meaning depends on the
formatter used. Note that ``arg`` can also be a list or tuple
for some formatters like the html formatter.
The optional argument `arg` can be used to modify the generation and
is formatter dependent (it is standardized because it can be given on
the command line).
This method is called by the ``-S`` :doc:`command-line option <cmdline>`,
the `arg` is then given by the ``-a`` option.
"""
return ''
def format(self, tokensource, outfile):
"""
Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
tuples and write it into ``outfile``.
This method must format the tokens from the `tokensource` iterable and
write the formatted version to the file object `outfile`.
Formatter options can control how exactly the tokens are converted.
"""
if self.encoding:
# wrap the outfile in a StreamWriter
@@ -4,13 +4,14 @@
Pygments formatters.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import sys
import types
from fnmatch import fnmatch
import fnmatch
from os.path import basename
from pipenv.patched.pip._vendor.pygments.formatters._mapping import FORMATTERS
@@ -21,6 +22,16 @@ __all__ = ['get_formatter_by_name', 'get_formatter_for_filename',
'get_all_formatters', 'load_formatter_from_file'] + list(FORMATTERS)
_formatter_cache = {} # classes by name
_pattern_cache = {}
def _fn_matches(fn, glob):
"""Return whether the supplied file name fn matches pattern filename."""
if glob not in _pattern_cache:
pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob))
return pattern.match(fn)
return _pattern_cache[glob].match(fn)
def _load_formatters(module_name):
"""Load a formatter (and all others in the module too)."""
@@ -57,9 +68,12 @@ def find_formatter_class(alias):
def get_formatter_by_name(_alias, **options):
"""Lookup and instantiate a formatter by alias.
"""
Return an instance of a :class:`.Formatter` subclass that has `alias` in its
aliases list. The formatter is given the `options` at its instantiation.
Raises ClassNotFound if not found.
Will raise :exc:`pygments.util.ClassNotFound` if no formatter with that
alias is found.
"""
cls = find_formatter_class(_alias)
if cls is None:
@@ -67,19 +81,18 @@ def get_formatter_by_name(_alias, **options):
return cls(**options)
def load_formatter_from_file(filename, formattername="CustomFormatter",
**options):
"""Load a formatter from a file.
def load_formatter_from_file(filename, formattername="CustomFormatter", **options):
"""
Return a `Formatter` subclass instance loaded from the provided file, relative
to the current directory.
This method expects a file located relative to the current working
directory, which contains a class named CustomFormatter. By default,
it expects the Formatter to be named CustomFormatter; you can specify
your own class name as the second argument to this function.
The file is expected to contain a Formatter class named ``formattername``
(by default, CustomFormatter). Users should be very careful with the input, because
this method is equivalent to running ``eval()`` on the input file. The formatter is
given the `options` at its instantiation.
Users should be very careful with the input, because this method
is equivalent to running eval on the input file.
Raises ClassNotFound if there are any problems importing the Formatter.
:exc:`pygments.util.ClassNotFound` is raised if there are any errors loading
the formatter.
.. versionadded:: 2.2
"""
@@ -104,20 +117,23 @@ def load_formatter_from_file(filename, formattername="CustomFormatter",
def get_formatter_for_filename(fn, **options):
"""Lookup and instantiate a formatter by filename pattern.
"""
Return a :class:`.Formatter` subclass instance that has a filename pattern
matching `fn`. The formatter is given the `options` at its instantiation.
Raises ClassNotFound if not found.
Will raise :exc:`pygments.util.ClassNotFound` if no formatter for that filename
is found.
"""
fn = basename(fn)
for modname, name, _, filenames, _ in FORMATTERS.values():
for filename in filenames:
if fnmatch(fn, filename):
if _fn_matches(fn, filename):
if name not in _formatter_cache:
_load_formatters(modname)
return _formatter_cache[name](**options)
for cls in find_plugin_formatters():
for filename in cls.filenames:
if fnmatch(fn, filename):
if _fn_matches(fn, filename):
return cls(**options)
raise ClassNotFound("no formatter found for file name %r" % fn)
@@ -1,12 +1,12 @@
# Automatically generated by scripts/gen_mapfiles.py.
# DO NOT EDIT BY HAND; run `make mapfiles` instead.
# DO NOT EDIT BY HAND; run `tox -e mapfiles` instead.
FORMATTERS = {
'BBCodeFormatter': ('pygments.formatters.bbcode', 'BBCode', ('bbcode', 'bb'), (), 'Format tokens with BBcodes. These formatting codes are used by many bulletin boards, so you can highlight your sourcecode with pygments before posting it there.'),
'BmpImageFormatter': ('pygments.formatters.img', 'img_bmp', ('bmp', 'bitmap'), ('*.bmp',), 'Create a bitmap image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
'GifImageFormatter': ('pygments.formatters.img', 'img_gif', ('gif',), ('*.gif',), 'Create a GIF image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
'GroffFormatter': ('pygments.formatters.groff', 'groff', ('groff', 'troff', 'roff'), (), 'Format tokens with groff escapes to change their color and font style.'),
'HtmlFormatter': ('pygments.formatters.html', 'HTML', ('html',), ('*.html', '*.htm'), "Format tokens as HTML 4 ``<span>`` tags within a ``<pre>`` tag, wrapped in a ``<div>`` tag. The ``<div>``'s CSS class can be set by the `cssclass` option."),
'HtmlFormatter': ('pygments.formatters.html', 'HTML', ('html',), ('*.html', '*.htm'), "Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option). The ``<div>``'s CSS class can be set by the `cssclass` option."),
'IRCFormatter': ('pygments.formatters.irc', 'IRC', ('irc', 'IRC'), (), 'Format tokens with IRC color sequences'),
'ImageFormatter': ('pygments.formatters.img', 'img', ('img', 'IMG', 'png'), ('*.png',), 'Create a PNG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
'JpgImageFormatter': ('pygments.formatters.img', 'img_jpg', ('jpg', 'jpeg'), ('*.jpg',), 'Create a JPEG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
@@ -4,7 +4,7 @@
BBcode formatter.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Formatter for groff output.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -84,7 +84,7 @@ class GroffFormatter(Formatter):
if ndef['color'] is not None:
colors.add(ndef['color'])
for color in colors:
for color in sorted(colors):
outfile.write('.defcolor ' + color + ' rgb #' + color + '\n')
@@ -4,7 +4,7 @@
Formatter for HTML output.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -62,7 +62,7 @@ def _get_ttype_class(ttype):
CSSFILE_TEMPLATE = '''\
/*
generated by Pygments <https://pygments.org/>
Copyright 2006-2022 by the Pygments team.
Copyright 2006-2023 by the Pygments team.
Licensed under the BSD license, see LICENSE for details.
*/
%(styledefs)s
@@ -73,7 +73,7 @@ DOC_HEADER = '''\
"http://www.w3.org/TR/html4/strict.dtd">
<!--
generated by Pygments <https://pygments.org/>
Copyright 2006-2022 by the Pygments team.
Copyright 2006-2023 by the Pygments team.
Licensed under the BSD license, see LICENSE for details.
-->
<html>
@@ -112,9 +112,9 @@ DOC_FOOTER = '''\
class HtmlFormatter(Formatter):
r"""
Format tokens as HTML 4 ``<span>`` tags within a ``<pre>`` tag, wrapped
in a ``<div>`` tag. The ``<div>``'s CSS class can be set by the `cssclass`
option.
Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed
in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option).
The ``<div>``'s CSS class can be set by the `cssclass` option.
If the `linenos` option is set to ``"table"``, the ``<pre>`` is
additionally wrapped inside a ``<table>`` which has one row and two
@@ -140,8 +140,6 @@ class HtmlFormatter(Formatter):
(whitespace added to improve clarity).
Wrapping can be disabled using the `nowrap` option.
A list of lines can be specified using the `hl_lines` option to make these
lines highlighted (as of Pygments 0.11).
@@ -187,8 +185,8 @@ class HtmlFormatter(Formatter):
Additional options accepted:
`nowrap`
If set to ``True``, don't wrap the tokens at all, not even inside a ``<pre>``
tag. This disables most other options (default: ``False``).
If set to ``True``, don't add a ``<pre>`` and a ``<div>`` tag
around the tokens. This disables most other options (default: ``False``).
`full`
Tells the formatter to output a "full" document, i.e. a complete
@@ -635,7 +633,7 @@ class HtmlFormatter(Formatter):
# write CSS file only if noclobber_cssfile isn't given as an option.
try:
if not os.path.exists(cssfilename) or not self.noclobber_cssfile:
with open(cssfilename, "w") as cf:
with open(cssfilename, "w", encoding="utf-8") as cf:
cf.write(CSSFILE_TEMPLATE %
{'styledefs': self.get_style_defs('body')})
except OSError as err:
@@ -721,7 +719,7 @@ class HtmlFormatter(Formatter):
yield 0, dummyoutfile.getvalue()
yield 0, '</div>'
yield 0, '</td></tr></table>'
def _wrap_inlinelinenos(self, inner):
# need a list of lines since we need the width of a single number :(
@@ -946,9 +944,9 @@ class HtmlFormatter(Formatter):
output = source
if self.wrapcode:
output = self._wrap_code(output)
output = self._wrap_pre(output)
return output
def format_unencoded(self, tokensource, outfile):
@@ -4,7 +4,7 @@
Formatter for Pixmap output.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Formatter for IRC output
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Formatter for LaTeX fancyvrb output.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Other formatters: NullFormatter, RawTokenFormatter.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Formatter for Pango markup output.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
A formatter that generates RTF files.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Formatter for SVG output.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,7 +4,7 @@
Formatter for terminal output with ANSI sequences.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -10,7 +10,7 @@
Formatter version 1.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
+83 -23
View File
@@ -4,7 +4,7 @@
Base lexer classes.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -50,7 +50,31 @@ class Lexer(metaclass=LexerMeta):
"""
Lexer for a specific language.
Basic options recognized:
See also :doc:`lexerdevelopment`, a high-level guide to writing
lexers.
Lexer classes have attributes used for choosing the most appropriate
lexer based on various criteria.
.. autoattribute:: name
:no-value:
.. autoattribute:: aliases
:no-value:
.. autoattribute:: filenames
:no-value:
.. autoattribute:: alias_filenames
.. autoattribute:: mimetypes
:no-value:
.. autoattribute:: priority
Lexers included in Pygments should have an additional attribute:
.. autoattribute:: url
:no-value:
You can pass options to the constructor. The basic options recognized
by all lexers and processed by the base `Lexer` class are:
``stripnl``
Strip leading and trailing newlines from the input (default: True).
``stripall``
@@ -74,28 +98,55 @@ class Lexer(metaclass=LexerMeta):
Overrides the ``encoding`` if given.
"""
#: Name of the lexer
#: Full name of the lexer, in human-readable form
name = None
#: URL of the language specification/definition
url = None
#: Shortcuts for the lexer
#: A list of short, unique identifiers that can be used to look
#: up the lexer from a list, e.g., using `get_lexer_by_name()`.
aliases = []
#: File name globs
#: A list of `fnmatch` patterns that match filenames which contain
#: content for this lexer. The patterns in this list should be unique among
#: all lexers.
filenames = []
#: Secondary file name globs
#: A list of `fnmatch` patterns that match filenames which may or may not
#: contain content for this lexer. This list is used by the
#: :func:`.guess_lexer_for_filename()` function, to determine which lexers
#: are then included in guessing the correct one. That means that
#: e.g. every lexer for HTML and a template language should include
#: ``\*.html`` in this list.
alias_filenames = []
#: MIME types
#: A list of MIME types for content that can be lexed with this lexer.
mimetypes = []
#: Priority, should multiple lexers match and no content is provided
priority = 0
#: URL of the language specification/definition. Used in the Pygments
#: documentation.
url = None
def __init__(self, **options):
"""
This constructor takes arbitrary options as keyword arguments.
Every subclass must first process its own options and then call
the `Lexer` constructor, since it processes the basic
options like `stripnl`.
An example looks like this:
.. sourcecode:: python
def __init__(self, **options):
self.compress = options.get('compress', '')
Lexer.__init__(self, **options)
As these options must all be specifiable as strings (due to the
command line usage), there are various utility functions
available to help with that, see `Utilities`_.
"""
self.options = options
self.stripnl = get_bool_opt(options, 'stripnl', True)
self.stripall = get_bool_opt(options, 'stripall', False)
@@ -124,10 +175,13 @@ class Lexer(metaclass=LexerMeta):
def analyse_text(text):
"""
Has to return a float between ``0`` and ``1`` that indicates
if a lexer wants to highlight this text. Used by ``guess_lexer``.
If this method returns ``0`` it won't highlight it in any case, if
it returns ``1`` highlighting with this lexer is guaranteed.
A static method which is called for lexer guessing.
It should analyse the text and return a float in the range
from ``0.0`` to ``1.0``. If it returns ``0.0``, the lexer
will not be selected as the most probable one, if it returns
``1.0``, it will be selected immediately. This is used by
`guess_lexer`.
The `LexerMeta` metaclass automatically wraps this function so
that it works like a static method (no ``self`` or ``cls``
@@ -138,12 +192,17 @@ class Lexer(metaclass=LexerMeta):
def get_tokens(self, text, unfiltered=False):
"""
Return an iterable of (tokentype, value) pairs generated from
`text`. If `unfiltered` is set to `True`, the filtering mechanism
is bypassed even if filters are defined.
This method is the basic interface of a lexer. It is called by
the `highlight()` function. It must process the text and return an
iterable of ``(tokentype, value)`` pairs from `text`.
Also preprocess the text, i.e. expand tabs and strip it if
wanted and applies registered filters.
Normally, you don't need to override this method. The default
implementation processes the options recognized by all lexers
(`stripnl`, `stripall` and so on), and then yields all tokens
from `get_tokens_unprocessed()`, with the ``index`` dropped.
If `unfiltered` is set to `True`, the filtering mechanism is
bypassed even if filters are defined.
"""
if not isinstance(text, str):
if self.encoding == 'guess':
@@ -197,11 +256,12 @@ class Lexer(metaclass=LexerMeta):
def get_tokens_unprocessed(self, text):
"""
Return an iterable of (index, tokentype, value) pairs where "index"
is the starting position of the token within the input text.
This method should process the text and return an iterable of
``(index, tokentype, value)`` tuples where ``index`` is the starting
position of the token within the input text.
In subclasses, implement this method as a generator to
maximize effectiveness.
It must be overridden by subclasses. It is recommended to
implement it as a generator to maximize effectiveness.
"""
raise NotImplementedError
@@ -4,13 +4,14 @@
Pygments lexers.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import sys
import types
from fnmatch import fnmatch
import fnmatch
from os.path import basename
from pipenv.patched.pip._vendor.pygments.lexers._mapping import LEXERS
@@ -27,6 +28,16 @@ __all__ = ['get_lexer_by_name', 'get_lexer_for_filename', 'find_lexer_class',
'guess_lexer', 'load_lexer_from_file'] + list(LEXERS) + list(COMPAT)
_lexer_cache = {}
_pattern_cache = {}
def _fn_matches(fn, glob):
"""Return whether the supplied file name fn matches pattern filename."""
if glob not in _pattern_cache:
pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob))
return pattern.match(fn)
return _pattern_cache[glob].match(fn)
def _load_lexers(module_name):
"""Load a lexer (and all others in the module too)."""
@@ -51,9 +62,9 @@ def get_all_lexers(plugins=True):
def find_lexer_class(name):
"""Lookup a lexer class by name.
Return None if not found.
"""
Return the `Lexer` subclass that with the *name* attribute as given by
the *name* argument.
"""
if name in _lexer_cache:
return _lexer_cache[name]
@@ -69,10 +80,15 @@ def find_lexer_class(name):
def find_lexer_class_by_name(_alias):
"""Lookup a lexer class by alias.
"""
Return the `Lexer` subclass that has `alias` in its aliases list, without
instantiating it.
Like `get_lexer_by_name`, but does not instantiate the class.
Will raise :exc:`pygments.util.ClassNotFound` if no lexer with that alias is
found.
.. versionadded:: 2.2
"""
if not _alias:
@@ -91,9 +107,13 @@ def find_lexer_class_by_name(_alias):
def get_lexer_by_name(_alias, **options):
"""Get a lexer by an alias.
"""
Return an instance of a `Lexer` subclass that has `alias` in its
aliases list. The lexer is given the `options` at its
instantiation.
Raises ClassNotFound if not found.
Will raise :exc:`pygments.util.ClassNotFound` if no lexer with that alias is
found.
"""
if not _alias:
raise ClassNotFound('no lexer for alias %r found' % _alias)
@@ -158,13 +178,13 @@ def find_lexer_class_for_filename(_fn, code=None):
fn = basename(_fn)
for modname, name, _, filenames, _ in LEXERS.values():
for filename in filenames:
if fnmatch(fn, filename):
if _fn_matches(fn, filename):
if name not in _lexer_cache:
_load_lexers(modname)
matches.append((_lexer_cache[name], filename))
for cls in find_plugin_lexers():
for filename in cls.filenames:
if fnmatch(fn, filename):
if _fn_matches(fn, filename):
matches.append((cls, filename))
if isinstance(code, bytes):
@@ -192,10 +212,15 @@ def find_lexer_class_for_filename(_fn, code=None):
def get_lexer_for_filename(_fn, code=None, **options):
"""Get a lexer for a filename.
If multiple lexers match the filename pattern, use ``analyse_text()`` to
figure out which one is more appropriate.
Return a `Lexer` subclass instance that has a filename pattern
matching `fn`. The lexer is given the `options` at its
instantiation.
Raises ClassNotFound if not found.
Raise :exc:`pygments.util.ClassNotFound` if no lexer for that filename
is found.
If multiple lexers match the filename pattern, use their ``analyse_text()``
methods to figure out which one is more appropriate.
"""
res = find_lexer_class_for_filename(_fn, code)
if not res:
@@ -204,9 +229,12 @@ def get_lexer_for_filename(_fn, code=None, **options):
def get_lexer_for_mimetype(_mime, **options):
"""Get a lexer for a mimetype.
"""
Return a `Lexer` subclass instance that has `mime` in its mimetype
list. The lexer is given the `options` at its instantiation.
Raises ClassNotFound if not found.
Will raise :exc:`pygments.util.ClassNotFound` if not lexer for that mimetype
is found.
"""
for modname, name, _, _, mimetypes in LEXERS.values():
if _mime in mimetypes:
@@ -232,30 +260,22 @@ def _iter_lexerclasses(plugins=True):
def guess_lexer_for_filename(_fn, _text, **options):
"""
Lookup all lexers that handle those filenames primary (``filenames``)
or secondary (``alias_filenames``). Then run a text analysis for those
lexers and choose the best result.
As :func:`guess_lexer()`, but only lexers which have a pattern in `filenames`
or `alias_filenames` that matches `filename` are taken into consideration.
usage::
>>> from pygments.lexers import guess_lexer_for_filename
>>> guess_lexer_for_filename('hello.html', '<%= @foo %>')
<pygments.lexers.templates.RhtmlLexer object at 0xb7d2f32c>
>>> guess_lexer_for_filename('hello.html', '<h1>{{ title|e }}</h1>')
<pygments.lexers.templates.HtmlDjangoLexer object at 0xb7d2f2ac>
>>> guess_lexer_for_filename('style.css', 'a { color: <?= $link ?> }')
<pygments.lexers.templates.CssPhpLexer object at 0xb7ba518c>
:exc:`pygments.util.ClassNotFound` is raised if no lexer thinks it can
handle the content.
"""
fn = basename(_fn)
primary = {}
matching_lexers = set()
for lexer in _iter_lexerclasses():
for filename in lexer.filenames:
if fnmatch(fn, filename):
if _fn_matches(fn, filename):
matching_lexers.add(lexer)
primary[lexer] = True
for filename in lexer.alias_filenames:
if fnmatch(fn, filename):
if _fn_matches(fn, filename):
matching_lexers.add(lexer)
primary[lexer] = False
if not matching_lexers:
@@ -282,7 +302,15 @@ def guess_lexer_for_filename(_fn, _text, **options):
def guess_lexer(_text, **options):
"""Guess a lexer by strong distinctions in the text (eg, shebang)."""
"""
Return a `Lexer` subclass instance that's guessed from the text in
`text`. For that, the :meth:`.analyse_text()` method of every known lexer
class is called with the text as argument, and the lexer which returned the
highest value will be instantiated and returned.
:exc:`pygments.util.ClassNotFound` is raised if no lexer thinks it can
handle the content.
"""
if not isinstance(_text, str):
inencoding = options.get('inencoding', options.get('encoding'))
@@ -1,5 +1,5 @@
# Automatically generated by scripts/gen_mapfiles.py.
# DO NOT EDIT BY HAND; run `make mapfiles` instead.
# DO NOT EDIT BY HAND; run `tox -e mapfiles` instead.
LEXERS = {
'ABAPLexer': ('pipenv.patched.pip._vendor.pygments.lexers.business', 'ABAP', ('abap',), ('*.abap', '*.ABAP'), ('text/x-abap',)),
@@ -71,6 +71,7 @@ LEXERS = {
'CadlLexer': ('pipenv.patched.pip._vendor.pygments.lexers.archetype', 'cADL', ('cadl',), ('*.cadl',), ()),
'CapDLLexer': ('pipenv.patched.pip._vendor.pygments.lexers.esoteric', 'CapDL', ('capdl',), ('*.cdl',), ()),
'CapnProtoLexer': ('pipenv.patched.pip._vendor.pygments.lexers.capnproto', "Cap'n Proto", ('capnp',), ('*.capnp',), ()),
'CarbonLexer': ('pipenv.patched.pip._vendor.pygments.lexers.carbon', 'Carbon', ('carbon',), ('*.carbon',), ('text/x-carbon',)),
'CbmBasicV2Lexer': ('pipenv.patched.pip._vendor.pygments.lexers.basic', 'CBM BASIC V2', ('cbmbas',), ('*.bas',), ()),
'CddlLexer': ('pipenv.patched.pip._vendor.pygments.lexers.cddl', 'CDDL', ('cddl',), ('*.cddl',), ('text/x-cddl',)),
'CeylonLexer': ('pipenv.patched.pip._vendor.pygments.lexers.jvm', 'Ceylon', ('ceylon',), ('*.ceylon',), ('text/x-ceylon',)),
@@ -121,6 +122,7 @@ LEXERS = {
'DarcsPatchLexer': ('pipenv.patched.pip._vendor.pygments.lexers.diff', 'Darcs Patch', ('dpatch',), ('*.dpatch', '*.darcspatch'), ()),
'DartLexer': ('pipenv.patched.pip._vendor.pygments.lexers.javascript', 'Dart', ('dart',), ('*.dart',), ('text/x-dart',)),
'Dasm16Lexer': ('pipenv.patched.pip._vendor.pygments.lexers.asm', 'DASM16', ('dasm16',), ('*.dasm16', '*.dasm'), ('text/x-dasm16',)),
'DaxLexer': ('pipenv.patched.pip._vendor.pygments.lexers.dax', 'Dax', ('dax',), ('*.dax',), ()),
'DebianControlLexer': ('pipenv.patched.pip._vendor.pygments.lexers.installers', 'Debian Control file', ('debcontrol', 'control'), ('control',), ()),
'DelphiLexer': ('pipenv.patched.pip._vendor.pygments.lexers.pascal', 'Delphi', ('delphi', 'pas', 'pascal', 'objectpascal'), ('*.pas', '*.dpr'), ('text/x-pascal',)),
'DevicetreeLexer': ('pipenv.patched.pip._vendor.pygments.lexers.devicetree', 'Devicetree', ('devicetree', 'dts'), ('*.dts', '*.dtsi'), ('text/x-c',)),
@@ -368,6 +370,7 @@ LEXERS = {
'PortugolLexer': ('pipenv.patched.pip._vendor.pygments.lexers.pascal', 'Portugol', ('portugol',), ('*.alg', '*.portugol'), ()),
'PostScriptLexer': ('pipenv.patched.pip._vendor.pygments.lexers.graphics', 'PostScript', ('postscript', 'postscr'), ('*.ps', '*.eps'), ('application/postscript',)),
'PostgresConsoleLexer': ('pipenv.patched.pip._vendor.pygments.lexers.sql', 'PostgreSQL console (psql)', ('psql', 'postgresql-console', 'postgres-console'), (), ('text/x-postgresql-psql',)),
'PostgresExplainLexer': ('pipenv.patched.pip._vendor.pygments.lexers.sql', 'PostgreSQL EXPLAIN dialect', ('postgres-explain',), ('*.explain',), ('text/x-postgresql-explain',)),
'PostgresLexer': ('pipenv.patched.pip._vendor.pygments.lexers.sql', 'PostgreSQL SQL dialect', ('postgresql', 'postgres'), (), ('text/x-postgresql',)),
'PovrayLexer': ('pipenv.patched.pip._vendor.pygments.lexers.graphics', 'POVRay', ('pov',), ('*.pov', '*.inc'), ('text/x-povray',)),
'PowerShellLexer': ('pipenv.patched.pip._vendor.pygments.lexers.shell', 'PowerShell', ('powershell', 'pwsh', 'posh', 'ps1', 'psm1'), ('*.ps1', '*.psm1'), ('text/x-powershell',)),
@@ -488,7 +491,7 @@ LEXERS = {
'TeraTermLexer': ('pipenv.patched.pip._vendor.pygments.lexers.teraterm', 'Tera Term macro', ('teratermmacro', 'teraterm', 'ttl'), ('*.ttl',), ('text/x-teratermmacro',)),
'TermcapLexer': ('pipenv.patched.pip._vendor.pygments.lexers.configs', 'Termcap', ('termcap',), ('termcap', 'termcap.src'), ()),
'TerminfoLexer': ('pipenv.patched.pip._vendor.pygments.lexers.configs', 'Terminfo', ('terminfo',), ('terminfo', 'terminfo.src'), ()),
'TerraformLexer': ('pipenv.patched.pip._vendor.pygments.lexers.configs', 'Terraform', ('terraform', 'tf'), ('*.tf',), ('application/x-tf', 'application/x-terraform')),
'TerraformLexer': ('pipenv.patched.pip._vendor.pygments.lexers.configs', 'Terraform', ('terraform', 'tf', 'hcl'), ('*.tf', '*.hcl'), ('application/x-tf', 'application/x-terraform')),
'TexLexer': ('pipenv.patched.pip._vendor.pygments.lexers.markup', 'TeX', ('tex', 'latex'), ('*.tex', '*.aux', '*.toc'), ('text/x-tex', 'text/x-latex')),
'TextLexer': ('pipenv.patched.pip._vendor.pygments.lexers.special', 'Text only', ('text',), ('*.txt',), ('text/plain',)),
'ThingsDBLexer': ('pipenv.patched.pip._vendor.pygments.lexers.thingsdb', 'ThingsDB', ('ti', 'thingsdb'), ('*.ti',), ()),
@@ -528,7 +531,9 @@ LEXERS = {
'WDiffLexer': ('pipenv.patched.pip._vendor.pygments.lexers.diff', 'WDiff', ('wdiff',), ('*.wdiff',), ()),
'WatLexer': ('pipenv.patched.pip._vendor.pygments.lexers.webassembly', 'WebAssembly', ('wast', 'wat'), ('*.wat', '*.wast'), ()),
'WebIDLLexer': ('pipenv.patched.pip._vendor.pygments.lexers.webidl', 'Web IDL', ('webidl',), ('*.webidl',), ()),
'WgslLexer': ('pipenv.patched.pip._vendor.pygments.lexers.wgsl', 'WebGPU Shading Language', ('wgsl',), ('*.wgsl',), ('text/wgsl',)),
'WhileyLexer': ('pipenv.patched.pip._vendor.pygments.lexers.whiley', 'Whiley', ('whiley',), ('*.whiley',), ('text/x-whiley',)),
'WikitextLexer': ('pipenv.patched.pip._vendor.pygments.lexers.markup', 'Wikitext', ('wikitext', 'mediawiki'), (), ('text/x-wiki',)),
'WoWTocLexer': ('pipenv.patched.pip._vendor.pygments.lexers.wowtoc', 'World of Warcraft TOC', ('wowtoc',), ('*.toc',), ()),
'WrenLexer': ('pipenv.patched.pip._vendor.pygments.lexers.wren', 'Wren', ('wren',), ('*.wren',), ()),
'X10Lexer': ('pipenv.patched.pip._vendor.pygments.lexers.x10', 'X10', ('x10', 'xten'), ('*.x10',), ('text/x-x10',)),
@@ -540,6 +545,7 @@ LEXERS = {
'XmlPhpLexer': ('pipenv.patched.pip._vendor.pygments.lexers.templates', 'XML+PHP', ('xml+php',), (), ('application/xml+php',)),
'XmlSmartyLexer': ('pipenv.patched.pip._vendor.pygments.lexers.templates', 'XML+Smarty', ('xml+smarty',), (), ('application/xml+smarty',)),
'XorgLexer': ('pipenv.patched.pip._vendor.pygments.lexers.xorg', 'Xorg', ('xorg.conf',), ('xorg.conf',), ()),
'XppLexer': ('pipenv.patched.pip._vendor.pygments.lexers.dotnet', 'X++', ('xpp', 'x++'), ('*.xpp',), ()),
'XsltLexer': ('pipenv.patched.pip._vendor.pygments.lexers.html', 'XSLT', ('xslt',), ('*.xsl', '*.xslt', '*.xpl'), ('application/xsl+xml', 'application/xslt+xml')),
'XtendLexer': ('pipenv.patched.pip._vendor.pygments.lexers.jvm', 'Xtend', ('xtend',), ('*.xtend',), ('text/x-xtend',)),
'XtlangLexer': ('pipenv.patched.pip._vendor.pygments.lexers.lisp', 'xtlang', ('extempore',), ('*.xtm',), ()),
@@ -4,15 +4,15 @@
Lexers for Python and related languages.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import keyword
from pipenv.patched.pip._vendor.pygments.lexer import Lexer, RegexLexer, include, bygroups, using, \
default, words, combined, do_insertions, this, line_re
from pipenv.patched.pip._vendor.pygments.lexer import DelegatingLexer, Lexer, RegexLexer, include, \
bygroups, using, default, words, combined, do_insertions, this, line_re
from pipenv.patched.pip._vendor.pygments.util import get_bool_opt, shebang_matches
from pipenv.patched.pip._vendor.pygments.token import Text, Comment, Operator, Keyword, Name, String, \
Number, Punctuation, Generic, Other, Error, Whitespace
@@ -234,16 +234,16 @@ class PythonLexer(RegexLexer):
],
'builtins': [
(words((
'__import__', 'abs', 'all', 'any', 'bin', 'bool', 'bytearray',
'breakpoint', 'bytes', 'chr', 'classmethod', 'compile', 'complex',
'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'filter',
'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
'hash', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview',
'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print',
'property', 'range', 'repr', 'reversed', 'round', 'set', 'setattr',
'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple',
'type', 'vars', 'zip'), prefix=r'(?<!\.)', suffix=r'\b'),
'__import__', 'abs', 'aiter', 'all', 'any', 'bin', 'bool', 'bytearray',
'breakpoint', 'bytes', 'callable', 'chr', 'classmethod', 'compile',
'complex', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval',
'filter', 'float', 'format', 'frozenset', 'getattr', 'globals',
'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'isinstance',
'issubclass', 'iter', 'len', 'list', 'locals', 'map', 'max',
'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow',
'print', 'property', 'range', 'repr', 'reversed', 'round', 'set',
'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
'tuple', 'type', 'vars', 'zip'), prefix=r'(?<!\.)', suffix=r'\b'),
Name.Builtin),
(r'(?<!\.)(self|Ellipsis|NotImplemented|cls)\b', Name.Builtin.Pseudo),
(words((
@@ -341,7 +341,7 @@ class PythonLexer(RegexLexer):
(r'\.', Name.Namespace),
# if None occurs here, it's "raise x from None", since None can
# never be a module name
(r'None\b', Name.Builtin.Pseudo, '#pop'),
(r'None\b', Keyword.Constant, '#pop'),
(uni_name, Name.Namespace),
default('#pop'),
],
@@ -635,15 +635,50 @@ class Python2Lexer(RegexLexer):
def analyse_text(text):
return shebang_matches(text, r'pythonw?2(\.\d)?')
class _PythonConsoleLexerBase(RegexLexer):
name = 'Python console session'
aliases = ['pycon']
mimetypes = ['text/x-python-doctest']
class PythonConsoleLexer(Lexer):
"""Auxiliary lexer for `PythonConsoleLexer`.
Code tokens are output as ``Token.Other.Code``, traceback tokens as
``Token.Other.Traceback``.
"""
tokens = {
'root': [
(r'(>>> )(.*\n)', bygroups(Generic.Prompt, Other.Code), 'continuations'),
# This happens, e.g., when tracebacks are embedded in documentation;
# trailing whitespaces are often stripped in such contexts.
(r'(>>>)(\n)', bygroups(Generic.Prompt, Whitespace)),
(r'(\^C)?Traceback \(most recent call last\):\n', Other.Traceback, 'traceback'),
# SyntaxError starts with this
(r' File "[^"]+", line \d+', Other.Traceback, 'traceback'),
(r'.*\n', Generic.Output),
],
'continuations': [
(r'(\.\.\. )(.*\n)', bygroups(Generic.Prompt, Other.Code)),
# See above.
(r'(\.\.\.)(\n)', bygroups(Generic.Prompt, Whitespace)),
default('#pop'),
],
'traceback': [
# As soon as we see a traceback, consume everything until the next
# >>> prompt.
(r'(?=>>>( |$))', Text, '#pop'),
(r'(KeyboardInterrupt)(\n)', bygroups(Name.Class, Whitespace)),
(r'.*\n', Other.Traceback),
],
}
class PythonConsoleLexer(DelegatingLexer):
"""
For Python console output or doctests, such as:
.. sourcecode:: pycon
>>> a = 'foo'
>>> print a
>>> print(a)
foo
>>> 1 / 0
Traceback (most recent call last):
@@ -659,70 +694,28 @@ class PythonConsoleLexer(Lexer):
.. versionchanged:: 2.5
Now defaults to ``True``.
"""
name = 'Python console session'
aliases = ['pycon']
mimetypes = ['text/x-python-doctest']
def __init__(self, **options):
self.python3 = get_bool_opt(options, 'python3', True)
Lexer.__init__(self, **options)
def get_tokens_unprocessed(self, text):
if self.python3:
pylexer = PythonLexer(**self.options)
tblexer = PythonTracebackLexer(**self.options)
python3 = get_bool_opt(options, 'python3', True)
if python3:
pylexer = PythonLexer
tblexer = PythonTracebackLexer
else:
pylexer = Python2Lexer(**self.options)
tblexer = Python2TracebackLexer(**self.options)
curcode = ''
insertions = []
curtb = ''
tbindex = 0
tb = 0
for match in line_re.finditer(text):
line = match.group()
if line.startswith('>>> ') or line.startswith('... '):
tb = 0
insertions.append((len(curcode),
[(0, Generic.Prompt, line[:4])]))
curcode += line[4:]
elif line.rstrip() == '...' and not tb:
# only a new >>> prompt can end an exception block
# otherwise an ellipsis in place of the traceback frames
# will be mishandled
insertions.append((len(curcode),
[(0, Generic.Prompt, '...')]))
curcode += line[3:]
else:
if curcode:
yield from do_insertions(
insertions, pylexer.get_tokens_unprocessed(curcode))
curcode = ''
insertions = []
if (line.startswith('Traceback (most recent call last):') or
re.match(' File "[^"]+", line \\d+\\n$', line)):
tb = 1
curtb = line
tbindex = match.start()
elif line == 'KeyboardInterrupt\n':
yield match.start(), Name.Class, line
elif tb:
curtb += line
if not (line.startswith(' ') or line.strip() == '...'):
tb = 0
for i, t, v in tblexer.get_tokens_unprocessed(curtb):
yield tbindex+i, t, v
curtb = ''
else:
yield match.start(), Generic.Output, line
if curcode:
yield from do_insertions(insertions,
pylexer.get_tokens_unprocessed(curcode))
if curtb:
for i, t, v in tblexer.get_tokens_unprocessed(curtb):
yield tbindex+i, t, v
pylexer = Python2Lexer
tblexer = Python2TracebackLexer
# We have two auxiliary lexers. Use DelegatingLexer twice with
# different tokens. TODO: DelegatingLexer should support this
# directly, by accepting a tuplet of auxiliary lexers and a tuple of
# distinguishing tokens. Then we wouldn't need this intermediary
# class.
class _ReplaceInnerCode(DelegatingLexer):
def __init__(self, **options):
super().__init__(pylexer, _PythonConsoleLexerBase, Other.Code, **options)
super().__init__(tblexer, _ReplaceInnerCode, Other.Traceback, **options)
class PythonTracebackLexer(RegexLexer):
"""
@@ -743,7 +736,7 @@ class PythonTracebackLexer(RegexLexer):
tokens = {
'root': [
(r'\n', Whitespace),
(r'^Traceback \(most recent call last\):\n', Generic.Traceback, 'intb'),
(r'^(\^C)?Traceback \(most recent call last\):\n', Generic.Traceback, 'intb'),
(r'^During handling of the above exception, another '
r'exception occurred:\n\n', Generic.Traceback),
(r'^The above exception was the direct cause of the '
@@ -763,7 +756,8 @@ class PythonTracebackLexer(RegexLexer):
(r'^([^:]+)(: )(.+)(\n)',
bygroups(Generic.Error, Text, Name, Whitespace), '#pop'),
(r'^([a-zA-Z_][\w.]*)(:?\n)',
bygroups(Generic.Error, Whitespace), '#pop')
bygroups(Generic.Error, Whitespace), '#pop'),
default('#pop'),
],
'markers': [
# Either `PEP 657 <https://www.python.org/dev/peps/pep-0657/>`
@@ -4,7 +4,7 @@
A simple modeline parser (based on pymodeline).
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -34,7 +34,7 @@
yourfilter = yourfilter:YourFilter
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -5,7 +5,7 @@
An algorithm that generates optimized regexes for matching long lists of
literal strings.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -11,7 +11,7 @@
Have a look at the `DelphiLexer` to get an idea of how to use
this scanner.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
@@ -5,7 +5,7 @@
Sphinx extension to generate automatic documentation of lexers,
formatters and filters.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
+1 -1
View File
@@ -4,7 +4,7 @@
Basic style object.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -4,15 +4,15 @@
Contains built-in styles.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from pipenv.patched.pip._vendor.pygments.plugin import find_plugin_styles
from pipenv.patched.pip._vendor.pygments.util import ClassNotFound
#: Maps style names to 'submodule::classname'.
#: A dictionary of built-in styles, mapping style names to
#: ``'submodule::classname'`` strings.
STYLE_MAP = {
'default': 'default::DefaultStyle',
'emacs': 'emacs::EmacsStyle',
@@ -66,6 +66,13 @@ STYLE_MAP = {
def get_style_by_name(name):
"""
Return a style class by its short name. The names of the builtin styles
are listed in :data:`pygments.styles.STYLE_MAP`.
Will raise :exc:`pygments.util.ClassNotFound` if no style of that name is
found.
"""
if name in STYLE_MAP:
mod, cls = STYLE_MAP[name].split('::')
builtin = "yes"
@@ -90,8 +97,7 @@ def get_style_by_name(name):
def get_all_styles():
"""Return a generator for all styles by name,
both builtin and plugin."""
"""Return a generator for all styles by name, both builtin and plugin."""
yield from STYLE_MAP
for name, _ in find_plugin_styles():
yield name
+1 -1
View File
@@ -4,7 +4,7 @@
Basic token types and the standard tokens.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -7,7 +7,7 @@
Inspired by chartypes_create.py from the MoinMoin project.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -112,7 +112,7 @@ if __name__ == '__main__': # pragma: no cover
categories = {'xid_start': [], 'xid_continue': []}
with open(__file__) as fp:
with open(__file__, encoding='utf-8') as fp:
content = fp.read()
header = content[:content.find('Cc =')]
@@ -136,7 +136,7 @@ if __name__ == '__main__': # pragma: no cover
if ('a' + c).isidentifier():
categories['xid_continue'].append(c)
with open(__file__, 'w') as fp:
with open(__file__, 'w', encoding='utf-8') as fp:
fp.write(header)
for cat in sorted(categories):
+26 -4
View File
@@ -4,7 +4,7 @@
Utility functions.
:copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
:copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
@@ -32,10 +32,16 @@ class ClassNotFound(ValueError):
class OptionError(Exception):
pass
"""
This exception will be raised by all option processing functions if
the type or value of the argument is not correct.
"""
def get_choice_opt(options, optname, allowed, default=None, normcase=False):
"""
If the key `optname` from the dictionary is not in the sequence
`allowed`, raise an error, otherwise return it.
"""
string = options.get(optname, default)
if normcase:
string = string.lower()
@@ -46,6 +52,17 @@ def get_choice_opt(options, optname, allowed, default=None, normcase=False):
def get_bool_opt(options, optname, default=None):
"""
Intuitively, this is `options.get(optname, default)`, but restricted to
Boolean value. The Booleans can be represented as string, in order to accept
Boolean value from the command line arguments. If the key `optname` is
present in the dictionary `options` and is not associated with a Boolean,
raise an `OptionError`. If it is absent, `default` is returned instead.
The valid string values for ``True`` are ``1``, ``yes``, ``true`` and
``on``, the ones for ``False`` are ``0``, ``no``, ``false`` and ``off``
(matched case-insensitively).
"""
string = options.get(optname, default)
if isinstance(string, bool):
return string
@@ -66,6 +83,7 @@ def get_bool_opt(options, optname, default=None):
def get_int_opt(options, optname, default=None):
"""As :func:`get_bool_opt`, but interpret the value as an integer."""
string = options.get(optname, default)
try:
return int(string)
@@ -78,8 +96,12 @@ def get_int_opt(options, optname, default=None):
'must give an integer value' % (
string, optname))
def get_list_opt(options, optname, default=None):
"""
If the key `optname` from the dictionary `options` is a string,
split it at whitespace and return it. If it is already a list
or a tuple, it is returned as a list.
"""
val = options.get(optname, default)
if isinstance(val, str):
return val.split()
@@ -56,7 +56,7 @@ self-explanatory class names, and the use of :class:`'+'<And>`,
:class:`'|'<MatchFirst>`, :class:`'^'<Or>` and :class:`'&'<Each>` operators.
The :class:`ParseResults` object returned from
:class:`ParserElement.parseString` can be
:class:`ParserElement.parse_string` can be
accessed as a nested list, a dictionary, or an object with named
attributes.
@@ -85,11 +85,11 @@ classes inherit from. Use the docstrings for examples of how to:
and :class:`'&'<Each>` operators to combine simple expressions into
more complex ones
- associate names with your parsed results using
:class:`ParserElement.setResultsName`
:class:`ParserElement.set_results_name`
- access the parsed data, which is returned as a :class:`ParseResults`
object
- find some helpful expression short-cuts like :class:`delimitedList`
and :class:`oneOf`
- find some helpful expression short-cuts like :class:`DelimitedList`
and :class:`one_of`
- find more useful common expressions in the :class:`pyparsing_common`
namespace class
"""
@@ -106,30 +106,22 @@ class version_info(NamedTuple):
@property
def __version__(self):
return (
"{}.{}.{}".format(self.major, self.minor, self.micro)
f"{self.major}.{self.minor}.{self.micro}"
+ (
"{}{}{}".format(
"r" if self.releaselevel[0] == "c" else "",
self.releaselevel[0],
self.serial,
),
f"{'r' if self.releaselevel[0] == 'c' else ''}{self.releaselevel[0]}{self.serial}",
"",
)[self.releaselevel == "final"]
)
def __str__(self):
return "{} {} / {}".format(__name__, self.__version__, __version_time__)
return f"{__name__} {self.__version__} / {__version_time__}"
def __repr__(self):
return "{}.{}({})".format(
__name__,
type(self).__name__,
", ".join("{}={!r}".format(*nv) for nv in zip(self._fields, self)),
)
return f"{__name__}.{type(self).__name__}({', '.join('{}={!r}'.format(*nv) for nv in zip(self._fields, self))})"
__version_info__ = version_info(3, 0, 9, "final", 0)
__version_time__ = "05 May 2022 07:02 UTC"
__version_info__ = version_info(3, 1, 0, "final", 1)
__version_time__ = "18 Jun 2023 14:05 UTC"
__version__ = __version_info__.__version__
__versionTime__ = __version_time__
__author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"
@@ -139,9 +131,9 @@ from .exceptions import *
from .actions import *
from .core import __diag__, __compat__
from .results import *
from .core import *
from .core import * # type: ignore[misc, assignment]
from .core import _builtin_exprs as core_builtin_exprs
from .helpers import *
from .helpers import * # type: ignore[misc, assignment]
from .helpers import _builtin_exprs as helper_builtin_exprs
from .unicode import unicode_set, UnicodeRangeList, pyparsing_unicode as unicode
@@ -153,11 +145,11 @@ from .common import (
# define backward compat synonyms
if "pyparsing_unicode" not in globals():
pyparsing_unicode = unicode
pyparsing_unicode = unicode # type: ignore[misc]
if "pyparsing_common" not in globals():
pyparsing_common = common
pyparsing_common = common # type: ignore[misc]
if "pyparsing_test" not in globals():
pyparsing_test = testing
pyparsing_test = testing # type: ignore[misc]
core_builtin_exprs += common_builtin_exprs + helper_builtin_exprs
@@ -174,7 +166,9 @@ __all__ = [
"CaselessKeyword",
"CaselessLiteral",
"CharsNotIn",
"CloseMatch",
"Combine",
"DelimitedList",
"Dict",
"Each",
"Empty",
@@ -227,9 +221,11 @@ __all__ = [
"alphas8bit",
"any_close_tag",
"any_open_tag",
"autoname_elements",
"c_style_comment",
"col",
"common_html_entity",
"condition_as_parse_action",
"counted_array",
"cpp_style_comment",
"dbl_quoted_string",
@@ -241,6 +237,7 @@ __all__ = [
"html_comment",
"identchars",
"identbodychars",
"infix_notation",
"java_style_comment",
"line",
"line_end",
@@ -255,8 +252,12 @@ __all__ = [
"null_debug_action",
"nums",
"one_of",
"original_text_for",
"printables",
"punc8bit",
"pyparsing_common",
"pyparsing_test",
"pyparsing_unicode",
"python_style_comment",
"quoted_string",
"remove_quotes",
@@ -267,28 +268,20 @@ __all__ = [
"srange",
"string_end",
"string_start",
"token_map",
"trace_parse_action",
"ungroup",
"unicode_set",
"unicode_string",
"with_attribute",
"indentedBlock",
"original_text_for",
"ungroup",
"infix_notation",
"locatedExpr",
"with_class",
"CloseMatch",
"token_map",
"pyparsing_common",
"pyparsing_unicode",
"unicode_set",
"condition_as_parse_action",
"pyparsing_test",
# pre-PEP8 compatibility names
"__versionTime__",
"anyCloseTag",
"anyOpenTag",
"cStyleComment",
"commonHTMLEntity",
"conditionAsParseAction",
"countedArray",
"cppStyleComment",
"dblQuotedString",
@@ -296,9 +289,12 @@ __all__ = [
"delimitedList",
"dictOf",
"htmlComment",
"indentedBlock",
"infixNotation",
"javaStyleComment",
"lineEnd",
"lineStart",
"locatedExpr",
"makeHTMLTags",
"makeXMLTags",
"matchOnlyAtCol",
@@ -308,6 +304,7 @@ __all__ = [
"nullDebugAction",
"oneOf",
"opAssoc",
"originalTextFor",
"pythonStyleComment",
"quotedString",
"removeQuotes",
@@ -317,15 +314,9 @@ __all__ = [
"sglQuotedString",
"stringEnd",
"stringStart",
"tokenMap",
"traceParseAction",
"unicodeString",
"withAttribute",
"indentedBlock",
"originalTextFor",
"infixNotation",
"locatedExpr",
"withClass",
"tokenMap",
"conditionAsParseAction",
"autoname_elements",
]
+22 -12
View File
@@ -1,7 +1,7 @@
# actions.py
from .exceptions import ParseException
from .util import col
from .util import col, replaced_by_pep8
class OnlyOnce:
@@ -38,7 +38,7 @@ def match_only_at_col(n):
def verify_col(strg, locn, toks):
if col(locn, strg) != n:
raise ParseException(strg, locn, "matched token not at column {}".format(n))
raise ParseException(strg, locn, f"matched token not at column {n}")
return verify_col
@@ -148,15 +148,13 @@ def with_attribute(*args, **attr_dict):
raise ParseException(
s,
l,
"attribute {!r} has value {!r}, must be {!r}".format(
attrName, tokens[attrName], attrValue
),
f"attribute {attrName!r} has value {tokens[attrName]!r}, must be {attrValue!r}",
)
return pa
with_attribute.ANY_VALUE = object()
with_attribute.ANY_VALUE = object() # type: ignore [attr-defined]
def with_class(classname, namespace=""):
@@ -195,13 +193,25 @@ def with_class(classname, namespace=""):
1 4 0 1 0
1,3 2,3 1,1
"""
classattr = "{}:class".format(namespace) if namespace else "class"
classattr = f"{namespace}:class" if namespace else "class"
return with_attribute(**{classattr: classname})
# pre-PEP8 compatibility symbols
replaceWith = replace_with
removeQuotes = remove_quotes
withAttribute = with_attribute
withClass = with_class
matchOnlyAtCol = match_only_at_col
# fmt: off
@replaced_by_pep8(replace_with)
def replaceWith(): ...
@replaced_by_pep8(remove_quotes)
def removeQuotes(): ...
@replaced_by_pep8(with_attribute)
def withAttribute(): ...
@replaced_by_pep8(with_class)
def withClass(): ...
@replaced_by_pep8(match_only_at_col)
def matchOnlyAtCol(): ...
# fmt: on
+33 -25
View File
@@ -1,6 +1,6 @@
# common.py
from .core import *
from .helpers import delimited_list, any_open_tag, any_close_tag
from .helpers import DelimitedList, any_open_tag, any_close_tag
from datetime import datetime
@@ -22,17 +22,17 @@ class pyparsing_common:
Parse actions:
- :class:`convertToInteger`
- :class:`convertToFloat`
- :class:`convertToDate`
- :class:`convertToDatetime`
- :class:`stripHTMLTags`
- :class:`upcaseTokens`
- :class:`downcaseTokens`
- :class:`convert_to_integer`
- :class:`convert_to_float`
- :class:`convert_to_date`
- :class:`convert_to_datetime`
- :class:`strip_html_tags`
- :class:`upcase_tokens`
- :class:`downcase_tokens`
Example::
pyparsing_common.number.runTests('''
pyparsing_common.number.run_tests('''
# any int or real number, returned as the appropriate type
100
-100
@@ -42,7 +42,7 @@ class pyparsing_common:
1e-12
''')
pyparsing_common.fnumber.runTests('''
pyparsing_common.fnumber.run_tests('''
# any int or real number, returned as float
100
-100
@@ -52,19 +52,19 @@ class pyparsing_common:
1e-12
''')
pyparsing_common.hex_integer.runTests('''
pyparsing_common.hex_integer.run_tests('''
# hex numbers
100
FF
''')
pyparsing_common.fraction.runTests('''
pyparsing_common.fraction.run_tests('''
# fractions
1/2
-3/4
''')
pyparsing_common.mixed_integer.runTests('''
pyparsing_common.mixed_integer.run_tests('''
# mixed fractions
1
1/2
@@ -73,8 +73,8 @@ class pyparsing_common:
''')
import uuid
pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID))
pyparsing_common.uuid.runTests('''
pyparsing_common.uuid.set_parse_action(token_map(uuid.UUID))
pyparsing_common.uuid.run_tests('''
# uuid
12345678-1234-5678-1234-567812345678
''')
@@ -260,8 +260,8 @@ class pyparsing_common:
Example::
date_expr = pyparsing_common.iso8601_date.copy()
date_expr.setParseAction(pyparsing_common.convertToDate())
print(date_expr.parseString("1999-12-31"))
date_expr.set_parse_action(pyparsing_common.convert_to_date())
print(date_expr.parse_string("1999-12-31"))
prints::
@@ -287,8 +287,8 @@ class pyparsing_common:
Example::
dt_expr = pyparsing_common.iso8601_datetime.copy()
dt_expr.setParseAction(pyparsing_common.convertToDatetime())
print(dt_expr.parseString("1999-12-31T23:59:59.999"))
dt_expr.set_parse_action(pyparsing_common.convert_to_datetime())
print(dt_expr.parse_string("1999-12-31T23:59:59.999"))
prints::
@@ -326,9 +326,9 @@ class pyparsing_common:
# strip HTML links from normal text
text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>'
td, td_end = makeHTMLTags("TD")
table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end
print(table_text.parseString(text).body)
td, td_end = make_html_tags("TD")
table_text = td + SkipTo(td_end).set_parse_action(pyparsing_common.strip_html_tags)("body") + td_end
print(table_text.parse_string(text).body)
Prints::
@@ -348,7 +348,7 @@ class pyparsing_common:
.streamline()
.set_name("commaItem")
)
comma_separated_list = delimited_list(
comma_separated_list = DelimitedList(
Opt(quoted_string.copy() | _commasepitem, default="")
).set_name("comma separated list")
"""Predefined expression of 1 or more printable words or quoted strings, separated by commas."""
@@ -363,7 +363,7 @@ class pyparsing_common:
url = Regex(
# https://mathiasbynens.be/demo/url-regex
# https://gist.github.com/dperini/729294
r"^" +
r"(?P<url>" +
# protocol identifier (optional)
# short syntax // still required
r"(?:(?:(?P<scheme>https?|ftp):)?\/\/)" +
@@ -405,18 +405,26 @@ class pyparsing_common:
r"(\?(?P<query>[^#]*))?" +
# fragment (optional)
r"(#(?P<fragment>\S*))?" +
r"$"
r")"
).set_name("url")
"""URL (http/https/ftp scheme)"""
# fmt: on
# pre-PEP8 compatibility names
convertToInteger = convert_to_integer
"""Deprecated - use :class:`convert_to_integer`"""
convertToFloat = convert_to_float
"""Deprecated - use :class:`convert_to_float`"""
convertToDate = convert_to_date
"""Deprecated - use :class:`convert_to_date`"""
convertToDatetime = convert_to_datetime
"""Deprecated - use :class:`convert_to_datetime`"""
stripHTMLTags = strip_html_tags
"""Deprecated - use :class:`strip_html_tags`"""
upcaseTokens = upcase_tokens
"""Deprecated - use :class:`upcase_tokens`"""
downcaseTokens = downcase_tokens
"""Deprecated - use :class:`downcase_tokens`"""
_builtin_exprs = [
File diff suppressed because it is too large Load Diff
@@ -1,3 +1,4 @@
# mypy: ignore-errors
import railroad
from pipenv.patched.pip._vendor import pyparsing
import typing
@@ -17,11 +18,13 @@ import inspect
jinja2_template_source = """\
{% if not embed %}
<!DOCTYPE html>
<html>
<head>
{% endif %}
{% if not head %}
<style type="text/css">
<style>
.railroad-heading {
font-family: monospace;
}
@@ -29,8 +32,10 @@ jinja2_template_source = """\
{% else %}
{{ head | safe }}
{% endif %}
{% if not embed %}
</head>
<body>
{% endif %}
{{ body | safe }}
{% for diagram in diagrams %}
<div class="railroad-group">
@@ -41,8 +46,10 @@ jinja2_template_source = """\
</div>
</div>
{% endfor %}
{% if not embed %}
</body>
</html>
{% endif %}
"""
template = Template(jinja2_template_source)
@@ -127,7 +134,7 @@ class EditablePartial(Generic[T]):
return self.func(*args, **kwargs)
def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str:
def railroad_to_html(diagrams: List[NamedDiagram], embed=False, **kwargs) -> str:
"""
Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams
:params kwargs: kwargs to be passed in to the template
@@ -137,13 +144,17 @@ def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str:
if diagram.diagram is None:
continue
io = StringIO()
diagram.diagram.writeSvg(io.write)
try:
css = kwargs.get('css')
diagram.diagram.writeStandalone(io.write, css=css)
except AttributeError:
diagram.diagram.writeSvg(io.write)
title = diagram.name
if diagram.index == 0:
title += " (root)"
data.append({"title": title, "text": "", "svg": io.getvalue()})
return template.render(diagrams=data, **kwargs)
return template.render(diagrams=data, embed=embed, **kwargs)
def resolve_partial(partial: "EditablePartial[T]") -> T:
@@ -398,7 +409,6 @@ def _apply_diagram_item_enhancements(fn):
show_results_names: bool = False,
show_groups: bool = False,
) -> typing.Optional[EditablePartial]:
ret = fn(
element,
parent,
@@ -555,9 +565,11 @@ def _to_diagram_element(
else:
ret = EditablePartial.from_call(railroad.Group, label="", item="")
elif isinstance(element, pyparsing.TokenConverter):
ret = EditablePartial.from_call(
AnnotatedItem, label=type(element).__name__.lower(), item=""
)
label = type(element).__name__.lower()
if label == "tokenconverter":
ret = EditablePartial.from_call(railroad.Sequence, items=[])
else:
ret = EditablePartial.from_call(AnnotatedItem, label=label, item="")
elif isinstance(element, pyparsing.Opt):
ret = EditablePartial.from_call(railroad.Optional, item="")
elif isinstance(element, pyparsing.OneOrMore):
@@ -571,10 +583,12 @@ def _to_diagram_element(
elif isinstance(element, pyparsing.Empty) and not element.customName:
# Skip unnamed "Empty" elements
ret = None
elif len(exprs) > 1:
elif isinstance(element, pyparsing.ParseElementEnhance):
ret = EditablePartial.from_call(railroad.Sequence, items=[])
elif len(exprs) > 0 and not element_results_name:
ret = EditablePartial.from_call(railroad.Group, item="", label=name)
elif len(exprs) > 0:
ret = EditablePartial.from_call(railroad.Sequence, items=[])
else:
terminal = EditablePartial.from_call(railroad.Terminal, element.defaultName)
ret = terminal
@@ -4,7 +4,13 @@ import re
import sys
import typing
from .util import col, line, lineno, _collapse_string_to_ranges
from .util import (
col,
line,
lineno,
_collapse_string_to_ranges,
replaced_by_pep8,
)
from .unicode import pyparsing_unicode as ppu
@@ -19,6 +25,20 @@ _exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.")
class ParseBaseException(Exception):
"""base exception class for all parsing runtime exceptions"""
loc: int
msg: str
pstr: str
parser_element: typing.Any # "ParserElement"
args: typing.Tuple[str, int, typing.Optional[str]]
__slots__ = (
"loc",
"msg",
"pstr",
"parser_element",
"args",
)
# Performance tuning: we construct a *lot* of these, so keep this
# constructor as small and fast as possible
def __init__(
@@ -35,7 +55,7 @@ class ParseBaseException(Exception):
else:
self.msg = msg
self.pstr = pstr
self.parser_element = self.parserElement = elem
self.parser_element = elem
self.args = (pstr, loc, msg)
@staticmethod
@@ -64,7 +84,7 @@ class ParseBaseException(Exception):
if isinstance(exc, ParseBaseException):
ret.append(exc.line)
ret.append(" " * (exc.column - 1) + "^")
ret.append("{}: {}".format(type(exc).__name__, exc))
ret.append(f"{type(exc).__name__}: {exc}")
if depth > 0:
callers = inspect.getinnerframes(exc.__traceback__, context=depth)
@@ -74,7 +94,9 @@ class ParseBaseException(Exception):
f_self = frm.f_locals.get("self", None)
if isinstance(f_self, ParserElement):
if frm.f_code.co_name not in ("parseImpl", "_parseNoCache"):
if not frm.f_code.co_name.startswith(
("parseImpl", "_parseNoCache")
):
continue
if id(f_self) in seen:
continue
@@ -82,21 +104,19 @@ class ParseBaseException(Exception):
self_type = type(f_self)
ret.append(
"{}.{} - {}".format(
self_type.__module__, self_type.__name__, f_self
)
f"{self_type.__module__}.{self_type.__name__} - {f_self}"
)
elif f_self is not None:
self_type = type(f_self)
ret.append("{}.{}".format(self_type.__module__, self_type.__name__))
ret.append(f"{self_type.__module__}.{self_type.__name__}")
else:
code = frm.f_code
if code.co_name in ("wrapper", "<module>"):
continue
ret.append("{}".format(code.co_name))
ret.append(code.co_name)
depth -= 1
if not depth:
@@ -110,7 +130,7 @@ class ParseBaseException(Exception):
internal factory method to simplify creating one type of ParseException
from another - avoids having __init__ signature conflicts among subclasses
"""
return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
return cls(pe.pstr, pe.loc, pe.msg, pe.parser_element)
@property
def line(self) -> str:
@@ -140,6 +160,15 @@ class ParseBaseException(Exception):
"""
return col(self.loc, self.pstr)
# pre-PEP8 compatibility
@property
def parserElement(self):
return self.parser_element
@parserElement.setter
def parserElement(self, elem):
self.parser_element = elem
def __str__(self) -> str:
if self.pstr:
if self.loc >= len(self.pstr):
@@ -154,14 +183,14 @@ class ParseBaseException(Exception):
foundstr = (", found %r" % found).replace(r"\\", "\\")
else:
foundstr = ""
return "{}{} (at char {}), (line:{}, col:{})".format(
self.msg, foundstr, self.loc, self.lineno, self.column
)
return f"{self.msg}{foundstr} (at char {self.loc}), (line:{self.lineno}, col:{self.column})"
def __repr__(self):
return str(self)
def mark_input_line(self, marker_string: str = None, *, markerString=">!<") -> str:
def mark_input_line(
self, marker_string: typing.Optional[str] = None, *, markerString: str = ">!<"
) -> str:
"""
Extracts the exception line from the input string, and marks
the location of the exception with a special symbol.
@@ -214,7 +243,10 @@ class ParseBaseException(Exception):
"""
return self.explain_exception(self, depth)
markInputline = mark_input_line
# fmt: off
@replaced_by_pep8(mark_input_line)
def markInputline(self): ...
# fmt: on
class ParseException(ParseBaseException):
@@ -264,4 +296,4 @@ class RecursiveGrammarException(Exception):
self.parseElementTrace = parseElementList
def __str__(self) -> str:
return "RecursiveGrammarException: {}".format(self.parseElementTrace)
return f"RecursiveGrammarException: {self.parseElementTrace}"
+104 -92
View File
@@ -1,73 +1,22 @@
# helpers.py
import html.entities
import re
import sys
import typing
from . import __diag__
from .core import *
from .util import _bslash, _flatten, _escape_regex_range_chars
from .util import (
_bslash,
_flatten,
_escape_regex_range_chars,
replaced_by_pep8,
)
#
# global helpers
#
def delimited_list(
expr: Union[str, ParserElement],
delim: Union[str, ParserElement] = ",",
combine: bool = False,
min: typing.Optional[int] = None,
max: typing.Optional[int] = None,
*,
allow_trailing_delim: bool = False,
) -> ParserElement:
"""Helper to define a delimited list of expressions - the delimiter
defaults to ','. By default, the list elements and delimiters can
have intervening whitespace, and comments, but this can be
overridden by passing ``combine=True`` in the constructor. If
``combine`` is set to ``True``, the matching tokens are
returned as a single token string, with the delimiters included;
otherwise, the matching tokens are returned as a list of tokens,
with the delimiters suppressed.
If ``allow_trailing_delim`` is set to True, then the list may end with
a delimiter.
Example::
delimited_list(Word(alphas)).parse_string("aa,bb,cc") # -> ['aa', 'bb', 'cc']
delimited_list(Word(hexnums), delim=':', combine=True).parse_string("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
"""
if isinstance(expr, str_type):
expr = ParserElement._literalStringClass(expr)
dlName = "{expr} [{delim} {expr}]...{end}".format(
expr=str(expr.copy().streamline()),
delim=str(delim),
end=" [{}]".format(str(delim)) if allow_trailing_delim else "",
)
if not combine:
delim = Suppress(delim)
if min is not None:
if min < 1:
raise ValueError("min must be greater than 0")
min -= 1
if max is not None:
if min is not None and max <= min:
raise ValueError("max must be greater than, or equal to min")
max -= 1
delimited_list_expr = expr + (delim + expr)[min, max]
if allow_trailing_delim:
delimited_list_expr += Opt(delim)
if combine:
return Combine(delimited_list_expr).set_name(dlName)
else:
return delimited_list_expr.set_name(dlName)
def counted_array(
expr: ParserElement,
int_expr: typing.Optional[ParserElement] = None,
@@ -187,7 +136,7 @@ def match_previous_expr(expr: ParserElement) -> ParserElement:
theseTokens = _flatten(t.as_list())
if theseTokens != matchTokens:
raise ParseException(
s, l, "Expected {}, found{}".format(matchTokens, theseTokens)
s, l, f"Expected {matchTokens}, found{theseTokens}"
)
rep.set_parse_action(must_match_these_tokens, callDuringTry=True)
@@ -218,7 +167,7 @@ def one_of(
- ``caseless`` - treat all literals as caseless - (default= ``False``)
- ``use_regex`` - as an optimization, will
generate a :class:`Regex` object; otherwise, will generate
a :class:`MatchFirst` object (if ``caseless=True`` or ``asKeyword=True``, or if
a :class:`MatchFirst` object (if ``caseless=True`` or ``as_keyword=True``, or if
creating a :class:`Regex` raises an exception) - (default= ``True``)
- ``as_keyword`` - enforce :class:`Keyword`-style matching on the
generated expressions - (default= ``False``)
@@ -262,6 +211,7 @@ def one_of(
symbols: List[str] = []
if isinstance(strs, str_type):
strs = typing.cast(str, strs)
symbols = strs.split()
elif isinstance(strs, Iterable):
symbols = list(strs)
@@ -293,15 +243,13 @@ def one_of(
try:
if all(len(sym) == 1 for sym in symbols):
# symbols are just single characters, create range regex pattern
patt = "[{}]".format(
"".join(_escape_regex_range_chars(sym) for sym in symbols)
)
patt = f"[{''.join(_escape_regex_range_chars(sym) for sym in symbols)}]"
else:
patt = "|".join(re.escape(sym) for sym in symbols)
# wrap with \b word break markers if defining as keywords
if asKeyword:
patt = r"\b(?:{})\b".format(patt)
patt = rf"\b(?:{patt})\b"
ret = Regex(patt, flags=re_flags).set_name(" | ".join(symbols))
@@ -371,7 +319,7 @@ def original_text_for(
expression. Useful to restore the parsed fields of an HTML start
tag into the raw tag text itself, or to revert separate tokens with
intervening whitespace back to the original matching input text. By
default, returns astring containing the original parsed text.
default, returns a string containing the original parsed text.
If the optional ``as_string`` argument is passed as
``False``, then the return value is
@@ -390,7 +338,7 @@ def original_text_for(
src = "this is test <b> bold <i>text</i> </b> normal text "
for tag in ("b", "i"):
opener, closer = make_html_tags(tag)
patt = original_text_for(opener + SkipTo(closer) + closer)
patt = original_text_for(opener + ... + closer)
print(patt.search_string(src)[0])
prints::
@@ -426,7 +374,7 @@ def ungroup(expr: ParserElement) -> ParserElement:
def locatedExpr(expr: ParserElement) -> ParserElement:
"""
(DEPRECATED - future code should use the Located class)
(DEPRECATED - future code should use the :class:`Located` class)
Helper to decorate a returned token with its starting and ending
locations in the input string.
@@ -437,12 +385,12 @@ def locatedExpr(expr: ParserElement) -> ParserElement:
- ``value`` - the actual parsed results
Be careful if the input text contains ``<TAB>`` characters, you
may want to call :class:`ParserElement.parseWithTabs`
may want to call :class:`ParserElement.parse_with_tabs`
Example::
wd = Word(alphas)
for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
for match in locatedExpr(wd).search_string("ljsdf123lksdjjf123lkkjj1222"):
print(match)
prints::
@@ -471,6 +419,7 @@ def nested_expr(
closing delimiters (``"("`` and ``")"`` are the default).
Parameters:
- ``opener`` - opening character for a nested list
(default= ``"("``); can also be a pyparsing expression
- ``closer`` - closing character for a nested list
@@ -507,7 +456,7 @@ def nested_expr(
c_function = (decl_data_type("type")
+ ident("name")
+ LPAR + Opt(delimited_list(arg), [])("args") + RPAR
+ LPAR + Opt(DelimitedList(arg), [])("args") + RPAR
+ code_body("body"))
c_function.ignore(c_style_comment)
@@ -539,6 +488,8 @@ def nested_expr(
raise ValueError("opening and closing strings cannot be the same")
if content is None:
if isinstance(opener, str_type) and isinstance(closer, str_type):
opener = typing.cast(str, opener)
closer = typing.cast(str, closer)
if len(opener) == 1 and len(closer) == 1:
if ignoreExpr is not None:
content = Combine(
@@ -695,12 +646,15 @@ common_html_entity = Regex("&(?P<entity>" + "|".join(_htmlEntityMap) + ");").set
)
def replace_html_entity(t):
def replace_html_entity(s, l, t):
"""Helper parser action to replace common HTML entities with their special characters"""
return _htmlEntityMap.get(t.entity)
class OpAssoc(Enum):
"""Enumeration of operator associativity
- used in constructing InfixNotationOperatorSpec for :class:`infix_notation`"""
LEFT = 1
RIGHT = 2
@@ -742,6 +696,7 @@ def infix_notation(
improve your parser performance.
Parameters:
- ``base_expr`` - expression representing the most basic operand to
be used in the expression
- ``op_list`` - list of tuples, one for each operator precedence level
@@ -764,11 +719,11 @@ def infix_notation(
``set_parse_action(*fn)``
(:class:`ParserElement.set_parse_action`)
- ``lpar`` - expression for matching left-parentheses; if passed as a
str, then will be parsed as Suppress(lpar). If lpar is passed as
str, then will be parsed as ``Suppress(lpar)``. If lpar is passed as
an expression (such as ``Literal('(')``), then it will be kept in
the parsed results, and grouped with them. (default= ``Suppress('(')``)
- ``rpar`` - expression for matching right-parentheses; if passed as a
str, then will be parsed as Suppress(rpar). If rpar is passed as
str, then will be parsed as ``Suppress(rpar)``. If rpar is passed as
an expression (such as ``Literal(')')``), then it will be kept in
the parsed results, and grouped with them. (default= ``Suppress(')')``)
@@ -800,9 +755,13 @@ def infix_notation(
(5+3)*6
[[[5, '+', 3], '*', 6]]
(5+x)*y
[[[5, '+', 'x'], '*', 'y']]
-2--11
[[['-', 2], '-', ['-', 11]]]
"""
# captive version of FollowedBy that does not do parse actions or capture results names
class _FB(FollowedBy):
def parseImpl(self, instring, loc, doActions=True):
@@ -823,19 +782,25 @@ def infix_notation(
else:
lastExpr = base_expr | (lpar + ret + rpar)
arity: int
rightLeftAssoc: opAssoc
pa: typing.Optional[ParseAction]
opExpr1: ParserElement
opExpr2: ParserElement
for i, operDef in enumerate(op_list):
opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4]
opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4] # type: ignore[assignment]
if isinstance(opExpr, str_type):
opExpr = ParserElement._literalStringClass(opExpr)
opExpr = typing.cast(ParserElement, opExpr)
if arity == 3:
if not isinstance(opExpr, (tuple, list)) or len(opExpr) != 2:
raise ValueError(
"if numterms=3, opExpr must be a tuple or list of two expressions"
)
opExpr1, opExpr2 = opExpr
term_name = "{}{} term".format(opExpr1, opExpr2)
term_name = f"{opExpr1}{opExpr2} term"
else:
term_name = "{} term".format(opExpr)
term_name = f"{opExpr} term"
if not 1 <= arity <= 3:
raise ValueError("operator must be unary (1), binary (2), or ternary (3)")
@@ -843,7 +808,8 @@ def infix_notation(
if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT):
raise ValueError("operator must indicate right or left associativity")
thisExpr: Forward = Forward().set_name(term_name)
thisExpr: ParserElement = Forward().set_name(term_name)
thisExpr = typing.cast(Forward, thisExpr)
if rightLeftAssoc is OpAssoc.LEFT:
if arity == 1:
matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...])
@@ -890,7 +856,7 @@ def infix_notation(
def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]):
"""
(DEPRECATED - use IndentedBlock class instead)
(DEPRECATED - use :class:`IndentedBlock` class instead)
Helper method for defining space-delimited indentation blocks,
such as those used to define block statements in Python source code.
@@ -1063,22 +1029,28 @@ _builtin_exprs: List[ParserElement] = [
]
# compatibility function, superseded by DelimitedList class
def delimited_list(
expr: Union[str, ParserElement],
delim: Union[str, ParserElement] = ",",
combine: bool = False,
min: typing.Optional[int] = None,
max: typing.Optional[int] = None,
*,
allow_trailing_delim: bool = False,
) -> ParserElement:
"""(DEPRECATED - use :class:`DelimitedList` class)"""
return DelimitedList(
expr, delim, combine, min, max, allow_trailing_delim=allow_trailing_delim
)
# pre-PEP8 compatible names
delimitedList = delimited_list
countedArray = counted_array
matchPreviousLiteral = match_previous_literal
matchPreviousExpr = match_previous_expr
oneOf = one_of
dictOf = dict_of
originalTextFor = original_text_for
nestedExpr = nested_expr
makeHTMLTags = make_html_tags
makeXMLTags = make_xml_tags
anyOpenTag, anyCloseTag = any_open_tag, any_close_tag
commonHTMLEntity = common_html_entity
replaceHTMLEntity = replace_html_entity
# fmt: off
opAssoc = OpAssoc
infixNotation = infix_notation
anyOpenTag = any_open_tag
anyCloseTag = any_close_tag
commonHTMLEntity = common_html_entity
cStyleComment = c_style_comment
htmlComment = html_comment
restOfLine = rest_of_line
@@ -1086,3 +1058,43 @@ dblSlashComment = dbl_slash_comment
cppStyleComment = cpp_style_comment
javaStyleComment = java_style_comment
pythonStyleComment = python_style_comment
@replaced_by_pep8(DelimitedList)
def delimitedList(): ...
@replaced_by_pep8(DelimitedList)
def delimited_list(): ...
@replaced_by_pep8(counted_array)
def countedArray(): ...
@replaced_by_pep8(match_previous_literal)
def matchPreviousLiteral(): ...
@replaced_by_pep8(match_previous_expr)
def matchPreviousExpr(): ...
@replaced_by_pep8(one_of)
def oneOf(): ...
@replaced_by_pep8(dict_of)
def dictOf(): ...
@replaced_by_pep8(original_text_for)
def originalTextFor(): ...
@replaced_by_pep8(nested_expr)
def nestedExpr(): ...
@replaced_by_pep8(make_html_tags)
def makeHTMLTags(): ...
@replaced_by_pep8(make_xml_tags)
def makeXMLTags(): ...
@replaced_by_pep8(replace_html_entity)
def replaceHTMLEntity(): ...
@replaced_by_pep8(infix_notation)
def infixNotation(): ...
# fmt: on
+82 -46
View File
@@ -1,18 +1,25 @@
# results.py
from collections.abc import MutableMapping, Mapping, MutableSequence, Iterator
from collections.abc import (
MutableMapping,
Mapping,
MutableSequence,
Iterator,
Sequence,
Container,
)
import pprint
from weakref import ref as wkref
from typing import Tuple, Any
from typing import Tuple, Any, Dict, Set, List
str_type: Tuple[type, ...] = (str, bytes)
_generator_type = type((_ for _ in ()))
class _ParseResultsWithOffset:
tup: Tuple["ParseResults", int]
__slots__ = ["tup"]
def __init__(self, p1, p2):
self.tup = (p1, p2)
def __init__(self, p1: "ParseResults", p2: int):
self.tup: Tuple[ParseResults, int] = (p1, p2)
def __getitem__(self, i):
return self.tup[i]
@@ -47,7 +54,7 @@ class ParseResults:
result = date_str.parse_string("1999/12/31")
def test(s, fn=repr):
print("{} -> {}".format(s, fn(eval(s))))
print(f"{s} -> {fn(eval(s))}")
test("list(result)")
test("result[0]")
test("result['month']")
@@ -70,27 +77,33 @@ class ParseResults:
- year: '1999'
"""
_null_values: Tuple[Any, ...] = (None, [], "", ())
_null_values: Tuple[Any, ...] = (None, [], ())
__slots__ = [
_name: str
_parent: "ParseResults"
_all_names: Set[str]
_modal: bool
_toklist: List[Any]
_tokdict: Dict[str, Any]
__slots__ = (
"_name",
"_parent",
"_all_names",
"_modal",
"_toklist",
"_tokdict",
"__weakref__",
]
)
class List(list):
"""
Simple wrapper class to distinguish parsed list results that should be preserved
as actual Python lists, instead of being converted to :class:`ParseResults`:
as actual Python lists, instead of being converted to :class:`ParseResults`::
LBRACK, RBRACK = map(pp.Suppress, "[]")
element = pp.Forward()
item = ppc.integer
element_list = LBRACK + pp.delimited_list(element) + RBRACK
element_list = LBRACK + pp.DelimitedList(element) + RBRACK
# add parse actions to convert from ParseResults to actual Python collection types
def as_python_list(t):
@@ -107,7 +120,7 @@ class ParseResults:
(2,3,4)
''', post_parse=lambda s, r: (r[0], type(r[0])))
prints:
prints::
100
(100, <class 'int'>)
@@ -127,8 +140,7 @@ class ParseResults:
if not isinstance(contained, list):
raise TypeError(
"{} may only be constructed with a list,"
" not {}".format(cls.__name__, type(contained).__name__)
f"{cls.__name__} may only be constructed with a list, not {type(contained).__name__}"
)
return list.__new__(cls)
@@ -159,6 +171,7 @@ class ParseResults:
def __init__(
self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance
):
self._tokdict: Dict[str, _ParseResultsWithOffset]
self._modal = modal
if name is not None and name != "":
if isinstance(name, int):
@@ -210,7 +223,7 @@ class ParseResults:
]
sub = v
if isinstance(sub, ParseResults):
sub._parent = wkref(self)
sub._parent = self
def __delitem__(self, i):
if isinstance(i, (int, slice)):
@@ -263,7 +276,7 @@ class ParseResults:
"""
Since ``keys()`` returns an iterator, this method is helpful in bypassing
code that looks for the existence of any defined results names."""
return bool(self._tokdict)
return not not self._tokdict
def pop(self, *args, **kwargs):
"""
@@ -311,9 +324,7 @@ class ParseResults:
if k == "default":
args = (args[0], v)
else:
raise TypeError(
"pop() got an unexpected keyword argument {!r}".format(k)
)
raise TypeError(f"pop() got an unexpected keyword argument {k!r}")
if isinstance(args[0], int) or len(args) == 1 or args[0] in self:
index = args[0]
ret = self[index]
@@ -423,12 +434,15 @@ class ParseResults:
raise AttributeError(name)
return ""
def __add__(self, other) -> "ParseResults":
def __add__(self, other: "ParseResults") -> "ParseResults":
ret = self.copy()
ret += other
return ret
def __iadd__(self, other) -> "ParseResults":
def __iadd__(self, other: "ParseResults") -> "ParseResults":
if not other:
return self
if other._tokdict:
offset = len(self._toklist)
addoffset = lambda a: offset if a < 0 else a + offset
@@ -441,7 +455,7 @@ class ParseResults:
for k, v in otherdictitems:
self[k] = v
if isinstance(v[0], ParseResults):
v[0]._parent = wkref(self)
v[0]._parent = self
self._toklist += other._toklist
self._all_names |= other._all_names
@@ -456,7 +470,7 @@ class ParseResults:
return other + self
def __repr__(self) -> str:
return "{}({!r}, {})".format(type(self).__name__, self._toklist, self.as_dict())
return f"{type(self).__name__}({self._toklist!r}, {self.as_dict()})"
def __str__(self) -> str:
return (
@@ -532,7 +546,10 @@ class ParseResults:
def copy(self) -> "ParseResults":
"""
Returns a new copy of a :class:`ParseResults` object.
Returns a new shallow copy of a :class:`ParseResults` object. `ParseResults`
items contained within the source are shared with the copy. Use
:class:`ParseResults.deepcopy()` to create a copy with its own separate
content values.
"""
ret = ParseResults(self._toklist)
ret._tokdict = self._tokdict.copy()
@@ -541,6 +558,27 @@ class ParseResults:
ret._name = self._name
return ret
def deepcopy(self) -> "ParseResults":
"""
Returns a new deep copy of a :class:`ParseResults` object.
"""
ret = self.copy()
# replace values with copies if they are of known mutable types
for i, obj in enumerate(self._toklist):
if isinstance(obj, ParseResults):
self._toklist[i] = obj.deepcopy()
elif isinstance(obj, (str, bytes)):
pass
elif isinstance(obj, MutableMapping):
self._toklist[i] = dest = type(obj)()
for k, v in obj.items():
dest[k] = v.deepcopy() if isinstance(v, ParseResults) else v
elif isinstance(obj, Container):
self._toklist[i] = type(obj)(
v.deepcopy() if isinstance(v, ParseResults) else v for v in obj
)
return ret
def get_name(self):
r"""
Returns the results name for this token expression. Useful when several
@@ -569,20 +607,17 @@ class ParseResults:
if self._name:
return self._name
elif self._parent:
par = self._parent()
def find_in_parent(sub):
return next(
(
k
for k, vlist in par._tokdict.items()
for v, loc in vlist
if sub is v
),
None,
)
return find_in_parent(self) if par else None
par: "ParseResults" = self._parent
parent_tokdict_items = par._tokdict.items()
return next(
(
k
for k, vlist in parent_tokdict_items
for v, loc in vlist
if v is self
),
None,
)
elif (
len(self) == 1
and len(self._tokdict) == 1
@@ -623,7 +658,7 @@ class ParseResults:
for k, v in items:
if out:
out.append(NL)
out.append("{}{}- {}: ".format(indent, (" " * _depth), k))
out.append(f"{indent}{(' ' * _depth)}- {k}: ")
if isinstance(v, ParseResults):
if v:
out.append(
@@ -685,7 +720,7 @@ class ParseResults:
num = Word(nums)
func = Forward()
term = ident | num | Group('(' + func + ')')
func <<= ident + Group(Optional(delimited_list(term)))
func <<= ident + Group(Optional(DelimitedList(term)))
result = func.parse_string("fna a,b,(fnb c,d,200),100")
result.pprint(width=40)
@@ -705,7 +740,7 @@ class ParseResults:
self._toklist,
(
self._tokdict.copy(),
self._parent is not None and self._parent() or None,
None,
self._all_names,
self._name,
),
@@ -714,10 +749,7 @@ class ParseResults:
def __setstate__(self, state):
self._toklist, (self._tokdict, par, inAccumNames, self._name) = state
self._all_names = set(inAccumNames)
if par is not None:
self._parent = wkref(par)
else:
self._parent = None
self._parent = None
def __getnewargs__(self):
return self._toklist, self._name
@@ -738,6 +770,7 @@ class ParseResults:
iter(obj)
except Exception:
return False
# str's are iterable, but in pyparsing, we don't want to iterate over them
else:
return not isinstance(obj, str_type)
@@ -752,8 +785,11 @@ class ParseResults:
return ret
asList = as_list
"""Deprecated - use :class:`as_list`"""
asDict = as_dict
"""Deprecated - use :class:`as_dict`"""
getName = get_name
"""Deprecated - use :class:`get_name`"""
MutableMapping.register(ParseResults)
+12 -12
View File
@@ -222,7 +222,7 @@ class pyparsing_test:
)
else:
# warning here maybe?
print("no validation for {!r}".format(test_string))
print(f"no validation for {test_string!r}")
# do this last, in case some specific test results can be reported instead
self.assertTrue(
@@ -265,15 +265,18 @@ class pyparsing_test:
if expand_tabs:
s = s.expandtabs()
if mark_control is not None:
mark_control = typing.cast(str, mark_control)
if mark_control == "unicode":
tbl = str.maketrans(
{c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))}
| {127: 0x2421}
)
transtable_map = {
c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))
}
transtable_map[127] = 0x2421
tbl = str.maketrans(transtable_map)
eol_mark = ""
else:
ord_mark_control = ord(mark_control)
tbl = str.maketrans(
{c: mark_control for c in list(range(0, 32)) + [127]}
{c: ord_mark_control for c in list(range(0, 32)) + [127]}
)
s = s.translate(tbl)
if mark_spaces is not None and mark_spaces != " ":
@@ -303,7 +306,7 @@ class pyparsing_test:
header0 = (
lead
+ "".join(
"{}{}".format(" " * 99, (i + 1) % 100)
f"{' ' * 99}{(i + 1) % 100}"
for i in range(max(max_line_len // 100, 1))
)
+ "\n"
@@ -313,10 +316,7 @@ class pyparsing_test:
header1 = (
header0
+ lead
+ "".join(
" {}".format((i + 1) % 10)
for i in range(-(-max_line_len // 10))
)
+ "".join(f" {(i + 1) % 10}" for i in range(-(-max_line_len // 10)))
+ "\n"
)
header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n"
@@ -324,7 +324,7 @@ class pyparsing_test:
header1
+ header2
+ "\n".join(
"{:{}d}:{}{}".format(i, lineno_width, line, eol_mark)
f"{i:{lineno_width}d}:{line}{eol_mark}"
for i, line in enumerate(s_lines, start=start_line)
)
+ "\n"
+56 -47
View File
@@ -64,27 +64,27 @@ class unicode_set:
@_lazyclassproperty
def printables(cls):
"all non-whitespace characters in this range"
"""all non-whitespace characters in this range"""
return "".join(filterfalse(str.isspace, cls._chars_for_ranges))
@_lazyclassproperty
def alphas(cls):
"all alphabetic characters in this range"
"""all alphabetic characters in this range"""
return "".join(filter(str.isalpha, cls._chars_for_ranges))
@_lazyclassproperty
def nums(cls):
"all numeric digit characters in this range"
"""all numeric digit characters in this range"""
return "".join(filter(str.isdigit, cls._chars_for_ranges))
@_lazyclassproperty
def alphanums(cls):
"all alphanumeric characters in this range"
"""all alphanumeric characters in this range"""
return cls.alphas + cls.nums
@_lazyclassproperty
def identchars(cls):
"all characters in this range that are valid identifier characters, plus underscore '_'"
"""all characters in this range that are valid identifier characters, plus underscore '_'"""
return "".join(
sorted(
set(
@@ -100,13 +100,13 @@ class unicode_set:
def identbodychars(cls):
"""
all characters in this range that are valid identifier body characters,
plus the digits 0-9
plus the digits 0-9, and · (Unicode MIDDLE DOT)
"""
return "".join(
sorted(
set(
cls.identchars
+ "0123456789"
+ "0123456789·"
+ "".join(
[c for c in cls._chars_for_ranges if ("_" + c).isidentifier()]
)
@@ -114,6 +114,16 @@ class unicode_set:
)
)
@_lazyclassproperty
def identifier(cls):
"""
a pyparsing Word expression for an identifier using this range's definitions for
identchars and identbodychars
"""
from pipenv.patched.pip._vendor.pyparsing import Word
return Word(cls.identchars, cls.identbodychars)
class pyparsing_unicode(unicode_set):
"""
@@ -128,32 +138,32 @@ class pyparsing_unicode(unicode_set):
]
class BasicMultilingualPlane(unicode_set):
"Unicode set for the Basic Multilingual Plane"
"""Unicode set for the Basic Multilingual Plane"""
_ranges: UnicodeRangeList = [
(0x0020, 0xFFFF),
]
class Latin1(unicode_set):
"Unicode set for Latin-1 Unicode Character Range"
"""Unicode set for Latin-1 Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x0020, 0x007E),
(0x00A0, 0x00FF),
]
class LatinA(unicode_set):
"Unicode set for Latin-A Unicode Character Range"
"""Unicode set for Latin-A Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x0100, 0x017F),
]
class LatinB(unicode_set):
"Unicode set for Latin-B Unicode Character Range"
"""Unicode set for Latin-B Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x0180, 0x024F),
]
class Greek(unicode_set):
"Unicode set for Greek Unicode Character Ranges"
"""Unicode set for Greek Unicode Character Ranges"""
_ranges: UnicodeRangeList = [
(0x0342, 0x0345),
(0x0370, 0x0377),
@@ -193,7 +203,7 @@ class pyparsing_unicode(unicode_set):
]
class Cyrillic(unicode_set):
"Unicode set for Cyrillic Unicode Character Range"
"""Unicode set for Cyrillic Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x0400, 0x052F),
(0x1C80, 0x1C88),
@@ -206,7 +216,7 @@ class pyparsing_unicode(unicode_set):
]
class Chinese(unicode_set):
"Unicode set for Chinese Unicode Character Range"
"""Unicode set for Chinese Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x2E80, 0x2E99),
(0x2E9B, 0x2EF3),
@@ -229,8 +239,7 @@ class pyparsing_unicode(unicode_set):
]
class Japanese(unicode_set):
"Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges"
_ranges: UnicodeRangeList = []
"""Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges"""
class Kanji(unicode_set):
"Unicode set for Kanji Unicode Character Range"
@@ -240,7 +249,7 @@ class pyparsing_unicode(unicode_set):
]
class Hiragana(unicode_set):
"Unicode set for Hiragana Unicode Character Range"
"""Unicode set for Hiragana Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x3041, 0x3096),
(0x3099, 0x30A0),
@@ -252,7 +261,7 @@ class pyparsing_unicode(unicode_set):
]
class Katakana(unicode_set):
"Unicode set for Katakana Unicode Character Range"
"""Unicode set for Katakana Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x3099, 0x309C),
(0x30A0, 0x30FF),
@@ -265,8 +274,18 @@ class pyparsing_unicode(unicode_set):
(0x1F213,),
]
漢字 = Kanji
カタカナ = Katakana
ひらがな = Hiragana
_ranges = (
Kanji._ranges
+ Hiragana._ranges
+ Katakana._ranges
)
class Hangul(unicode_set):
"Unicode set for Hangul (Korean) Unicode Character Range"
"""Unicode set for Hangul (Korean) Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x1100, 0x11FF),
(0x302E, 0x302F),
@@ -288,17 +307,17 @@ class pyparsing_unicode(unicode_set):
Korean = Hangul
class CJK(Chinese, Japanese, Hangul):
"Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"
"""Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"""
class Thai(unicode_set):
"Unicode set for Thai Unicode Character Range"
"""Unicode set for Thai Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x0E01, 0x0E3A),
(0x0E3F, 0x0E5B)
]
class Arabic(unicode_set):
"Unicode set for Arabic Unicode Character Range"
"""Unicode set for Arabic Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x0600, 0x061B),
(0x061E, 0x06FF),
@@ -306,7 +325,7 @@ class pyparsing_unicode(unicode_set):
]
class Hebrew(unicode_set):
"Unicode set for Hebrew Unicode Character Range"
"""Unicode set for Hebrew Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x0591, 0x05C7),
(0x05D0, 0x05EA),
@@ -320,33 +339,23 @@ class pyparsing_unicode(unicode_set):
]
class Devanagari(unicode_set):
"Unicode set for Devanagari Unicode Character Range"
"""Unicode set for Devanagari Unicode Character Range"""
_ranges: UnicodeRangeList = [
(0x0900, 0x097F),
(0xA8E0, 0xA8FF)
]
BMP = BasicMultilingualPlane
# add language identifiers using language Unicode
العربية = Arabic
中文 = Chinese
кириллица = Cyrillic
Ελληνικά = Greek
עִברִית = Hebrew
日本語 = Japanese
한국어 = Korean
ไทย = Thai
वनगर = Devanagari
# fmt: on
pyparsing_unicode.Japanese._ranges = (
pyparsing_unicode.Japanese.Kanji._ranges
+ pyparsing_unicode.Japanese.Hiragana._ranges
+ pyparsing_unicode.Japanese.Katakana._ranges
)
pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane
# add language identifiers using language Unicode
pyparsing_unicode.العربية = pyparsing_unicode.Arabic
pyparsing_unicode.中文 = pyparsing_unicode.Chinese
pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic
pyparsing_unicode.Ελληνικά = pyparsing_unicode.Greek
pyparsing_unicode.עִברִית = pyparsing_unicode.Hebrew
pyparsing_unicode.日本語 = pyparsing_unicode.Japanese
pyparsing_unicode.Japanese.漢字 = pyparsing_unicode.Japanese.Kanji
pyparsing_unicode.Japanese.カタカナ = pyparsing_unicode.Japanese.Katakana
pyparsing_unicode.Japanese.ひらがな = pyparsing_unicode.Japanese.Hiragana
pyparsing_unicode.한국어 = pyparsing_unicode.Korean
pyparsing_unicode.ไทย = pyparsing_unicode.Thai
pyparsing_unicode.वनगर = pyparsing_unicode.Devanagari
+69 -20
View File
@@ -1,12 +1,14 @@
# util.py
import inspect
import warnings
import types
import collections
import itertools
from functools import lru_cache
from typing import List, Union, Iterable
from functools import lru_cache, wraps
from typing import Callable, List, Union, Iterable, TypeVar, cast
_bslash = chr(92)
C = TypeVar("C", bound=Callable)
class __config_flags:
@@ -20,18 +22,15 @@ class __config_flags:
def _set(cls, dname, value):
if dname in cls._fixed_names:
warnings.warn(
"{}.{} {} is {} and cannot be overridden".format(
cls.__name__,
dname,
cls._type_desc,
str(getattr(cls, dname)).upper(),
)
f"{cls.__name__}.{dname} {cls._type_desc} is {str(getattr(cls, dname)).upper()}"
f" and cannot be overridden",
stacklevel=3,
)
return
if dname in cls._all_names:
setattr(cls, dname, value)
else:
raise ValueError("no such {} {!r}".format(cls._type_desc, dname))
raise ValueError(f"no such {cls._type_desc} {dname!r}")
enable = classmethod(lambda cls, name: cls._set(name, True))
disable = classmethod(lambda cls, name: cls._set(name, False))
@@ -45,7 +44,7 @@ def col(loc: int, strg: str) -> int:
Note: the default parsing behavior is to expand tabs in the input string
before starting the parsing process. See
:class:`ParserElement.parseString` for more
:class:`ParserElement.parse_string` for more
information on parsing strings containing ``<TAB>`` s, and suggested
methods to maintain a consistent view of the parsed string, the parse
location, and line and column positions within the parsed string.
@@ -60,7 +59,7 @@ def lineno(loc: int, strg: str) -> int:
The first line is number 1.
Note - the default parsing behavior is to expand tabs in the input string
before starting the parsing process. See :class:`ParserElement.parseString`
before starting the parsing process. See :class:`ParserElement.parse_string`
for more information on parsing strings containing ``<TAB>`` s, and
suggested methods to maintain a consistent view of the parsed string, the
parse location, and line and column positions within the parsed string.
@@ -102,19 +101,24 @@ class _UnboundedCache:
class _FifoCache:
def __init__(self, size):
self.not_in_cache = not_in_cache = object()
cache = collections.OrderedDict()
cache = {}
keyring = [object()] * size
cache_get = cache.get
cache_pop = cache.pop
keyiter = itertools.cycle(range(size))
def get(_, key):
return cache_get(key, not_in_cache)
def set_(_, key, value):
cache[key] = value
while len(cache) > size:
cache.popitem(last=False)
i = next(keyiter)
cache_pop(keyring[i], None)
keyring[i] = key
def clear(_):
cache.clear()
keyring[:] = [object()] * size
self.size = size
self.get = types.MethodType(get, self)
@@ -189,9 +193,9 @@ def _collapse_string_to_ranges(
is_consecutive.value = next(is_consecutive.counter)
return is_consecutive.value
is_consecutive.prev = 0
is_consecutive.counter = itertools.count()
is_consecutive.value = -1
is_consecutive.prev = 0 # type: ignore [attr-defined]
is_consecutive.counter = itertools.count() # type: ignore [attr-defined]
is_consecutive.value = -1 # type: ignore [attr-defined]
def escape_re_range_char(c):
return "\\" + c if c in r"\^-][" else c
@@ -215,9 +219,7 @@ def _collapse_string_to_ranges(
else:
sep = "" if ord(last) == ord(first) + 1 else "-"
ret.append(
"{}{}{}".format(
escape_re_range_char(first), sep, escape_re_range_char(last)
)
f"{escape_re_range_char(first)}{sep}{escape_re_range_char(last)}"
)
else:
ret = [escape_re_range_char(c) for c in s]
@@ -233,3 +235,50 @@ def _flatten(ll: list) -> list:
else:
ret.append(i)
return ret
def _make_synonym_function(compat_name: str, fn: C) -> C:
# In a future version, uncomment the code in the internal _inner() functions
# to begin emitting DeprecationWarnings.
# Unwrap staticmethod/classmethod
fn = getattr(fn, "__func__", fn)
# (Presence of 'self' arg in signature is used by explain_exception() methods, so we take
# some extra steps to add it if present in decorated function.)
if "self" == list(inspect.signature(fn).parameters)[0]:
@wraps(fn)
def _inner(self, *args, **kwargs):
# warnings.warn(
# f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3
# )
return fn(self, *args, **kwargs)
else:
@wraps(fn)
def _inner(*args, **kwargs):
# warnings.warn(
# f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3
# )
return fn(*args, **kwargs)
_inner.__doc__ = f"""Deprecated - use :class:`{fn.__name__}`"""
_inner.__name__ = compat_name
_inner.__annotations__ = fn.__annotations__
if isinstance(fn, types.FunctionType):
_inner.__kwdefaults__ = fn.__kwdefaults__
elif isinstance(fn, type) and hasattr(fn, "__init__"):
_inner.__kwdefaults__ = fn.__init__.__kwdefaults__
else:
_inner.__kwdefaults__ = None
_inner.__qualname__ = fn.__qualname__
return cast(C, _inner)
def replaced_by_pep8(fn: C) -> Callable[[Callable], C]:
"""
Decorator for pre-PEP8 compatibility synonyms, to link them to the new function.
"""
return lambda other: _make_synonym_function(other.__name__, fn)
@@ -63,10 +63,10 @@ def check_compatibility(urllib3_version, chardet_version, charset_normalizer_ver
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
major, minor, patch = int(major), int(minor), int(patch)
# urllib3 >= 1.21.1, <= 1.26
assert major == 1
assert minor >= 21
assert minor <= 26
# urllib3 >= 1.21.1
assert major >= 1
if major == 1:
assert minor >= 21
# Check charset_normalizer for compatibility.
if chardet_version:
@@ -5,8 +5,8 @@
__title__ = "requests"
__description__ = "Python HTTP for Humans."
__url__ = "https://requests.readthedocs.io"
__version__ = "2.28.2"
__build__ = 0x022802
__version__ = "2.31.0"
__build__ = 0x023100
__author__ = "Kenneth Reitz"
__author_email__ = "me@kennethreitz.org"
__license__ = "Apache 2.0"
@@ -14,9 +14,11 @@ _VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*$")
_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$")
_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$")
_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR)
_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE)
HEADER_VALIDATORS = {
bytes: (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE),
str: (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR),
bytes: _HEADER_VALIDATORS_BYTE,
str: _HEADER_VALIDATORS_STR,
}
+13 -59
View File
@@ -22,7 +22,6 @@ from pipenv.patched.pip._vendor.urllib3.exceptions import ProxyError as _ProxyEr
from pipenv.patched.pip._vendor.urllib3.exceptions import ReadTimeoutError, ResponseError
from pipenv.patched.pip._vendor.urllib3.exceptions import SSLError as _SSLError
from pipenv.patched.pip._vendor.urllib3.poolmanager import PoolManager, proxy_from_url
from pipenv.patched.pip._vendor.urllib3.response import HTTPResponse
from pipenv.patched.pip._vendor.urllib3.util import Timeout as TimeoutSauce
from pipenv.patched.pip._vendor.urllib3.util import parse_url
from pipenv.patched.pip._vendor.urllib3.util.retry import Retry
@@ -194,7 +193,6 @@ class HTTPAdapter(BaseAdapter):
num_pools=connections,
maxsize=maxsize,
block=block,
strict=True,
**pool_kwargs,
)
@@ -485,63 +483,19 @@ class HTTPAdapter(BaseAdapter):
timeout = TimeoutSauce(connect=timeout, read=timeout)
try:
if not chunked:
resp = conn.urlopen(
method=request.method,
url=url,
body=request.body,
headers=request.headers,
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=self.max_retries,
timeout=timeout,
)
# Send the request.
else:
if hasattr(conn, "proxy_pool"):
conn = conn.proxy_pool
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
try:
skip_host = "Host" in request.headers
low_conn.putrequest(
request.method,
url,
skip_accept_encoding=True,
skip_host=skip_host,
)
for header, value in request.headers.items():
low_conn.putheader(header, value)
low_conn.endheaders()
for i in request.body:
low_conn.send(hex(len(i))[2:].encode("utf-8"))
low_conn.send(b"\r\n")
low_conn.send(i)
low_conn.send(b"\r\n")
low_conn.send(b"0\r\n\r\n")
# Receive the response from the server
r = low_conn.getresponse()
resp = HTTPResponse.from_httplib(
r,
pool=conn,
connection=low_conn,
preload_content=False,
decode_content=False,
)
except Exception:
# If we hit any problems here, clean up the connection.
# Then, raise so that we can handle the actual exception.
low_conn.close()
raise
resp = conn.urlopen(
method=request.method,
url=url,
body=request.body,
headers=request.headers,
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=self.max_retries,
timeout=timeout,
chunked=chunked,
)
except (ProtocolError, OSError) as err:
raise ConnectionError(err, request=request)
+3 -3
View File
@@ -106,7 +106,7 @@ def post(url, data=None, json=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
@@ -121,7 +121,7 @@ def put(url, data=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
@@ -136,7 +136,7 @@ def patch(url, data=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
@@ -324,7 +324,9 @@ class SessionRedirectMixin:
except KeyError:
username, password = None, None
if username and password:
# urllib3 handles proxy authorization for us in the standard adapter.
# Avoid appending this to TLS tunneled requests where it may be leaked.
if not scheme.startswith('https') and username and password:
headers["Proxy-Authorization"] = _basic_auth_str(username, password)
return new_proxies
+20 -12
View File
@@ -25,7 +25,12 @@ from . import certs
from .__version__ import __version__
# to_native_string is unused here, but imported here for backwards compatibility
from ._internal_utils import HEADER_VALIDATORS, to_native_string # noqa: F401
from ._internal_utils import ( # noqa: F401
_HEADER_VALIDATORS_BYTE,
_HEADER_VALIDATORS_STR,
HEADER_VALIDATORS,
to_native_string,
)
from .compat import (
Mapping,
basestring,
@@ -1031,20 +1036,23 @@ def check_header_validity(header):
:param header: tuple, in the format (name, value).
"""
name, value = header
for part in header:
if type(part) not in HEADER_VALIDATORS:
raise InvalidHeader(
f"Header part ({part!r}) from {{{name!r}: {value!r}}} must be "
f"of type str or bytes, not {type(part)}"
)
_validate_header_part(name, "name", HEADER_VALIDATORS[type(name)][0])
_validate_header_part(value, "value", HEADER_VALIDATORS[type(value)][1])
_validate_header_part(header, name, 0)
_validate_header_part(header, value, 1)
def _validate_header_part(header_part, header_kind, validator):
def _validate_header_part(header, header_part, header_validator_index):
if isinstance(header_part, str):
validator = _HEADER_VALIDATORS_STR[header_validator_index]
elif isinstance(header_part, bytes):
validator = _HEADER_VALIDATORS_BYTE[header_validator_index]
else:
raise InvalidHeader(
f"Header part ({header_part!r}) from {header} "
f"must be of type str or bytes, not {type(header_part)}"
)
if not validator.match(header_part):
header_kind = "name" if header_validator_index == 0 else "value"
raise InvalidHeader(
f"Invalid leading whitespace, reserved character(s), or return"
f"character(s) in header {header_kind}: {header_part!r}"
+1 -1
View File
@@ -952,6 +952,7 @@ class Console:
force_color = self._environ.get("FORCE_COLOR")
if force_color is not None:
self._force_terminal = True
return True
isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None)
try:
@@ -2000,7 +2001,6 @@ class Console:
self._record_buffer.extend(self._buffer[:])
if self._buffer_index == 0:
if self.is_jupyter: # pragma: no cover
from .jupyter import display
+1 -1
View File
@@ -28,7 +28,7 @@ from typing import (
from pipenv.patched.pip._vendor.rich.repr import RichReprResult
try:
import pipenv.vendor.attr as _attr_module
import attr as _attr_module
_has_attrs = hasattr(_attr_module, "ib")
except ImportError: # pragma: no cover
+1 -3
View File
@@ -590,7 +590,6 @@ class Syntax(JupyterMixin):
def __rich_measure__(
self, console: "Console", options: "ConsoleOptions"
) -> "Measurement":
_, right, _, left = Padding.unpack(self.padding)
padding = left + right
if self.code_width is not None:
@@ -688,7 +687,7 @@ class Syntax(JupyterMixin):
lines = (
Text("\n")
.join(lines)
.with_indent_guides(self.tab_size, style=style)
.with_indent_guides(self.tab_size, style=style + Style(italic=False))
.split("\n", allow_blank=True)
)
@@ -830,7 +829,6 @@ def _get_code_index_for_syntax_position(
if __name__ == "__main__": # pragma: no cover
import argparse
import sys
@@ -2,12 +2,12 @@ A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
@@ -19,7 +19,7 @@ https://www.python.org/psf/) was formed, a non-profit organization
created specifically to own Python-related Intellectual Property.
Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see http://www.opensource.org for
All Python releases are Open Source (see https://opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
@@ -59,6 +59,17 @@ direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
Python software and documentation are licensed under the
Python Software Foundation License Version 2.
Starting with Python 3.8.6, examples, recipes, and other code in
the documentation are dual licensed under the PSF License Version 2
and the Zero-Clause BSD license.
Some software incorporated into Python is under different licenses.
The licenses are listed with code falling under that license.
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
@@ -73,7 +84,7 @@ analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation;
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
@@ -252,3 +263,17 @@ FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
----------------------------------------------------------------------
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
+443 -246
View File
@@ -65,8 +65,10 @@ __all__ = [
'get_args',
'get_origin',
'get_original_bases',
'get_protocol_members',
'get_type_hints',
'IntVar',
'is_protocol',
'is_typeddict',
'Literal',
'NewType',
@@ -85,6 +87,45 @@ __all__ = [
'NoReturn',
'Required',
'NotRequired',
# Pure aliases, have always been in typing
'AbstractSet',
'AnyStr',
'BinaryIO',
'Callable',
'Collection',
'Container',
'Dict',
'ForwardRef',
'FrozenSet',
'Generator',
'Generic',
'Hashable',
'IO',
'ItemsView',
'Iterable',
'Iterator',
'KeysView',
'List',
'Mapping',
'MappingView',
'Match',
'MutableMapping',
'MutableSequence',
'MutableSet',
'Optional',
'Pattern',
'Reversible',
'Sequence',
'Set',
'Sized',
'TextIO',
'Tuple',
'Union',
'ValuesView',
'cast',
'no_type_check',
'no_type_check_decorator',
]
# for backward compatibility
@@ -201,17 +242,19 @@ else:
ClassVar = typing.ClassVar
class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
# On older versions of typing there is an internal class named "Final".
# 3.8+
if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7):
Final = typing.Final
# 3.7
else:
class _FinalForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
class _FinalForm(_ExtensionsSpecialForm, _root=True):
def __getitem__(self, parameters):
item = typing._type_check(parameters,
f'{self._name} accepts only a single type.')
@@ -303,14 +346,11 @@ else:
def __hash__(self):
return hash(frozenset(_value_and_type_iter(self.__args__)))
class _LiteralForm(typing._SpecialForm, _root=True):
class _LiteralForm(_ExtensionsSpecialForm, _root=True):
def __init__(self, doc: str):
self._name = 'Literal'
self._doc = self.__doc__ = doc
def __repr__(self):
return 'typing_extensions.' + self._name
def __getitem__(self, parameters):
if not isinstance(parameters, tuple):
parameters = (parameters,)
@@ -453,9 +493,10 @@ TYPE_CHECKING = typing.TYPE_CHECKING
_PROTO_ALLOWLIST = {
'collections.abc': [
'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable',
'Hashable', 'Sized', 'Container', 'Collection', 'Reversible',
'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer',
],
'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'],
'typing_extensions': ['Buffer'],
}
@@ -545,9 +586,8 @@ def _caller(depth=2):
# so we backport the 3.12 version of Protocol to Python <=3.11
if sys.version_info >= (3, 12):
Protocol = typing.Protocol
runtime_checkable = typing.runtime_checkable
else:
def _allow_reckless_class_checks(depth=4):
def _allow_reckless_class_checks(depth=3):
"""Allow instance and class checks for special stdlib modules.
The abc and functools modules indiscriminately call isinstance() and
issubclass() on the whole MRO of a user class, which may contain protocols.
@@ -558,11 +598,41 @@ else:
if type(self)._is_protocol:
raise TypeError('Protocols cannot be instantiated')
class _ProtocolMeta(abc.ABCMeta):
if sys.version_info >= (3, 8):
# Inheriting from typing._ProtocolMeta isn't actually desirable,
# but is necessary to allow typing.Protocol and typing_extensions.Protocol
# to mix without getting TypeErrors about "metaclass conflict"
_typing_Protocol = typing.Protocol
_ProtocolMetaBase = type(_typing_Protocol)
else:
_typing_Protocol = _marker
_ProtocolMetaBase = abc.ABCMeta
class _ProtocolMeta(_ProtocolMetaBase):
# This metaclass is somewhat unfortunate,
# but is necessary for several reasons...
#
# NOTE: DO NOT call super() in any methods in this class
# That would call the methods on typing._ProtocolMeta on Python 3.8-3.11
# and those are slow
def __new__(mcls, name, bases, namespace, **kwargs):
if name == "Protocol" and len(bases) < 2:
pass
elif {Protocol, _typing_Protocol} & set(bases):
for base in bases:
if not (
base in {object, typing.Generic, Protocol, _typing_Protocol}
or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
or is_protocol(base)
):
raise TypeError(
f"Protocols can only inherit from other protocols, "
f"got {base!r}"
)
return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
abc.ABCMeta.__init__(cls, *args, **kwargs)
if getattr(cls, "_is_protocol", False):
cls.__protocol_attrs__ = _get_protocol_attrs(cls)
# PEP 544 prohibits using issubclass()
@@ -572,31 +642,46 @@ else:
)
def __subclasscheck__(cls, other):
if cls is Protocol:
return type.__subclasscheck__(cls, other)
if (
getattr(cls, '_is_protocol', False)
and not cls.__callable_proto_members_only__
and not _allow_reckless_class_checks(depth=3)
and not _allow_reckless_class_checks()
):
raise TypeError(
"Protocols with non-method members don't support issubclass()"
)
return super().__subclasscheck__(other)
if not isinstance(other, type):
# Same error message as for issubclass(1, int).
raise TypeError('issubclass() arg 1 must be a class')
if (
not cls.__callable_proto_members_only__
and cls.__dict__.get("__subclasshook__") is _proto_hook
):
raise TypeError(
"Protocols with non-method members don't support issubclass()"
)
if not getattr(cls, '_is_runtime_protocol', False):
raise TypeError(
"Instance and class checks can only be used with "
"@runtime_checkable protocols"
)
return abc.ABCMeta.__subclasscheck__(cls, other)
def __instancecheck__(cls, instance):
# We need this method for situations where attributes are
# assigned in __init__.
if cls is Protocol:
return type.__instancecheck__(cls, instance)
if not getattr(cls, "_is_protocol", False):
# i.e., it's a concrete subclass of a protocol
return super().__instancecheck__(instance)
return abc.ABCMeta.__instancecheck__(cls, instance)
if (
not getattr(cls, '_is_runtime_protocol', False) and
not _allow_reckless_class_checks(depth=2)
not _allow_reckless_class_checks()
):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")
if super().__instancecheck__(instance):
if abc.ABCMeta.__instancecheck__(cls, instance):
return True
for attr in cls.__protocol_attrs__:
@@ -615,7 +700,7 @@ else:
# Hack so that typing.Generic.__class_getitem__
# treats typing_extensions.Protocol
# as equivalent to typing.Protocol on Python 3.8+
if super().__eq__(other) is True:
if abc.ABCMeta.__eq__(cls, other) is True:
return True
return (
cls is Protocol and other is getattr(typing, "Protocol", object())
@@ -632,18 +717,6 @@ else:
if not cls.__dict__.get('_is_protocol', False):
return NotImplemented
# First, perform various sanity checks.
if not getattr(cls, '_is_runtime_protocol', False):
if _allow_reckless_class_checks():
return NotImplemented
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")
if not isinstance(other, type):
# Same error message as for issubclass(1, int).
raise TypeError('issubclass() arg 1 must be a class')
# Second, perform the actual structural compatibility check.
for attr in cls.__protocol_attrs__:
for base in other.__mro__:
# Check if the members appears in the class dictionary...
@@ -657,25 +730,13 @@ else:
if (
isinstance(annotations, collections.abc.Mapping)
and attr in annotations
and issubclass(other, (typing.Generic, _ProtocolMeta))
# All subclasses of Generic have an _is_proto attribute on 3.8+
# But not on 3.7
and getattr(other, "_is_protocol", False)
and is_protocol(other)
):
break
else:
return NotImplemented
return True
def _check_proto_bases(cls):
for base in cls.__bases__:
if not (base in (object, typing.Generic) or
base.__module__ in _PROTO_ALLOWLIST and
base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
isinstance(base, _ProtocolMeta) and base._is_protocol):
raise TypeError('Protocols can only inherit from other'
f' protocols, got {repr(base)}')
if sys.version_info >= (3, 8):
class Protocol(typing.Generic, metaclass=_ProtocolMeta):
__doc__ = typing.Protocol.__doc__
@@ -694,13 +755,8 @@ else:
if '__subclasshook__' not in cls.__dict__:
cls.__subclasshook__ = _proto_hook
# We have nothing more to do for non-protocols...
if not cls._is_protocol:
return
# ... otherwise check consistency of bases, and prohibit instantiation.
_check_proto_bases(cls)
if cls.__init__ is Protocol.__init__:
# Prohibit instantiation for protocol classes
if cls._is_protocol and cls.__init__ is Protocol.__init__:
cls.__init__ = _no_init
else:
@@ -790,15 +846,14 @@ else:
if '__subclasshook__' not in cls.__dict__:
cls.__subclasshook__ = _proto_hook
# We have nothing more to do for non-protocols.
if not cls._is_protocol:
return
# Check consistency of bases.
_check_proto_bases(cls)
if cls.__init__ is Protocol.__init__:
# Prohibit instantiation for protocol classes
if cls._is_protocol and cls.__init__ is Protocol.__init__:
cls.__init__ = _no_init
if sys.version_info >= (3, 8):
runtime_checkable = typing.runtime_checkable
else:
def runtime_checkable(cls):
"""Mark a protocol class as a runtime protocol, so that it
can be used with isinstance() and issubclass(). Raise TypeError
@@ -898,7 +953,22 @@ else:
pass
if sys.version_info >= (3, 12):
def _ensure_subclassable(mro_entries):
def inner(func):
if sys.implementation.name == "pypy" and sys.version_info < (3, 9):
cls_dict = {
"__call__": staticmethod(func),
"__mro_entries__": staticmethod(mro_entries)
}
t = type(func.__name__, (), cls_dict)
return functools.update_wrapper(t(), func)
else:
func.__mro_entries__ = mro_entries
return func
return inner
if sys.version_info >= (3, 13):
# The standard library TypedDict in Python 3.8 does not store runtime information
# about which (if any) keys are optional. See https://bugs.python.org/issue38834
# The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
@@ -908,117 +978,61 @@ if sys.version_info >= (3, 12):
# Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
# Aaaand on 3.12 we add __orig_bases__ to TypedDict
# to enable better runtime introspection.
# On 3.13 we deprecate some odd ways of creating TypedDicts.
TypedDict = typing.TypedDict
_TypedDictMeta = typing._TypedDictMeta
is_typeddict = typing.is_typeddict
else:
def _check_fails(cls, other):
try:
if _caller() not in {'abc', 'functools', 'typing'}:
# Typed dicts are only for static structural subtyping.
raise TypeError('TypedDict does not support instance and class checks')
except (AttributeError, ValueError):
pass
return False
def _dict_new(*args, **kwargs):
if not args:
raise TypeError('TypedDict.__new__(): not enough arguments')
_, args = args[0], args[1:] # allow the "cls" keyword be passed
return dict(*args, **kwargs)
_dict_new.__text_signature__ = '($cls, _typename, _fields=None, /, **kwargs)'
def _typeddict_new(*args, total=True, **kwargs):
if not args:
raise TypeError('TypedDict.__new__(): not enough arguments')
_, args = args[0], args[1:] # allow the "cls" keyword be passed
if args:
typename, args = args[0], args[1:] # allow the "_typename" keyword be passed
elif '_typename' in kwargs:
typename = kwargs.pop('_typename')
warnings.warn("Passing '_typename' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError("TypedDict.__new__() missing 1 required positional "
"argument: '_typename'")
if args:
try:
fields, = args # allow the "_fields" keyword be passed
except ValueError:
raise TypeError('TypedDict.__new__() takes from 2 to 3 '
f'positional arguments but {len(args) + 2} '
'were given')
elif '_fields' in kwargs and len(kwargs) == 1:
fields = kwargs.pop('_fields')
warnings.warn("Passing '_fields' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
fields = None
if fields is None:
fields = kwargs
elif kwargs:
raise TypeError("TypedDict takes either a dict or keyword arguments,"
" but not both")
if kwargs:
warnings.warn(
"The kwargs-based syntax for TypedDict definitions is deprecated, "
"may be removed in a future version, and may not be "
"understood by third-party type checkers.",
DeprecationWarning,
stacklevel=2,
)
ns = {'__annotations__': dict(fields)}
module = _caller()
if module is not None:
# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = module
return _TypedDictMeta(typename, (), ns, total=total)
_typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,'
' /, *, total=True, **kwargs)')
# 3.10.0 and later
_TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters
if sys.version_info >= (3, 8):
_fake_name = "Protocol"
else:
_fake_name = "_Protocol"
class _TypedDictMeta(type):
def __init__(cls, name, bases, ns, total=True):
super().__init__(name, bases, ns)
def __new__(cls, name, bases, ns, total=True):
# Create new typed dict class object.
# This method is called directly when TypedDict is subclassed,
# or via _typeddict_new when TypedDict is instantiated. This way
# TypedDict supports all three syntaxes described in its docstring.
# Subclasses and instances of TypedDict return actual dictionaries
# via _dict_new.
ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new
# Don't insert typing.Generic into __bases__ here,
# or Generic.__init_subclass__ will raise TypeError
# in the super().__new__() call.
# Instead, monkey-patch __bases__ onto the class after it's been created.
tp_dict = super().__new__(cls, name, (dict,), ns)
"""Create new typed dict class object.
is_generic = any(issubclass(base, typing.Generic) for base in bases)
This method is called when TypedDict is subclassed,
or when TypedDict is instantiated. This way
TypedDict supports all three syntax forms described in its docstring.
Subclasses and instances of TypedDict return actual dictionaries.
"""
for base in bases:
if type(base) is not _TypedDictMeta and base is not typing.Generic:
raise TypeError('cannot inherit from both a TypedDict type '
'and a non-TypedDict base class')
if is_generic:
tp_dict.__bases__ = (typing.Generic, dict)
_maybe_adjust_parameters(tp_dict)
if any(issubclass(b, typing.Generic) for b in bases):
generic_base = (typing.Generic,)
else:
# generic TypedDicts get __orig_bases__ from Generic
tp_dict.__orig_bases__ = bases or (TypedDict,)
generic_base = ()
# typing.py generally doesn't let you inherit from plain Generic, unless
# the name of the class happens to be "Protocol" (or "_Protocol" on 3.7).
tp_dict = type.__new__(_TypedDictMeta, _fake_name, (*generic_base, dict), ns)
tp_dict.__name__ = name
if tp_dict.__qualname__ == _fake_name:
tp_dict.__qualname__ = name
if not hasattr(tp_dict, '__orig_bases__'):
tp_dict.__orig_bases__ = bases
annotations = {}
own_annotations = ns.get('__annotations__', {})
msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
kwds = {"module": tp_dict.__module__} if _TAKES_MODULE else {}
own_annotations = {
n: typing._type_check(tp, msg, **kwds)
for n, tp in own_annotations.items()
}
if _TAKES_MODULE:
own_annotations = {
n: typing._type_check(tp, msg, module=tp_dict.__module__)
for n, tp in own_annotations.items()
}
else:
own_annotations = {
n: typing._type_check(tp, msg)
for n, tp in own_annotations.items()
}
required_keys = set()
optional_keys = set()
@@ -1052,17 +1066,25 @@ else:
tp_dict.__total__ = total
return tp_dict
__instancecheck__ = __subclasscheck__ = _check_fails
__call__ = dict # static method
TypedDict = _TypedDictMeta('TypedDict', (dict,), {})
TypedDict.__module__ = __name__
TypedDict.__doc__ = \
"""A simple typed name space. At runtime it is equivalent to a plain dict.
def __subclasscheck__(cls, other):
# Typed dicts are only for static structural subtyping.
raise TypeError('TypedDict does not support instance and class checks')
TypedDict creates a dictionary type that expects all of its
instances to have a certain set of keys, with each key
__instancecheck__ = __subclasscheck__
_TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
@_ensure_subclassable(lambda bases: (_TypedDict,))
def TypedDict(__typename, __fields=_marker, *, total=True, **kwargs):
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
TypedDict creates a dictionary type such that a type checker will expect all
instances to have a certain set of keys, where each key is
associated with a value of a consistent type. This expectation
is not checked at runtime but is only enforced by type checkers.
is not checked at runtime.
Usage::
class Point2D(TypedDict):
@@ -1077,14 +1099,66 @@ else:
The type info can be accessed via the Point2D.__annotations__ dict, and
the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
TypedDict supports two additional equivalent forms::
TypedDict supports an additional equivalent form::
Point2D = TypedDict('Point2D', x=int, y=int, label=str)
Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
The class syntax is only supported in Python 3.6+, while two other
syntax forms work for Python 2.7 and 3.2+
By default, all keys must be present in a TypedDict. It is possible
to override this by specifying totality::
class Point2D(TypedDict, total=False):
x: int
y: int
This means that a Point2D TypedDict can have any of the keys omitted. A type
checker is only expected to support a literal False or True as the value of
the total argument. True is the default, and makes all items defined in the
class body be required.
The Required and NotRequired special forms can also be used to mark
individual keys as being required or not required::
class Point2D(TypedDict):
x: int # the "x" key must always be present (Required is the default)
y: NotRequired[int] # the "y" key can be omitted
See PEP 655 for more details on Required and NotRequired.
"""
if __fields is _marker or __fields is None:
if __fields is _marker:
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
else:
deprecated_thing = "Passing `None` as the 'fields' parameter"
example = f"`{__typename} = TypedDict({__typename!r}, {{}})`"
deprecation_msg = (
f"{deprecated_thing} is deprecated and will be disallowed in "
"Python 3.15. To create a TypedDict class with 0 fields "
"using the functional syntax, pass an empty dictionary, e.g. "
) + example + "."
warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
__fields = kwargs
elif kwargs:
raise TypeError("TypedDict takes either a dict or keyword arguments,"
" but not both")
if kwargs:
warnings.warn(
"The kwargs-based syntax for TypedDict definitions is deprecated "
"in Python 3.11, will be removed in Python 3.13, and may not be "
"understood by third-party type checkers.",
DeprecationWarning,
stacklevel=2,
)
ns = {'__annotations__': dict(__fields)}
module = _caller()
if module is not None:
# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = module
td = _TypedDictMeta(__typename, (), ns, total=total)
td.__orig_bases__ = (TypedDict,)
return td
if hasattr(typing, "_TypedDictMeta"):
_TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
@@ -1102,7 +1176,10 @@ else:
is_typeddict(Film) # => True
is_typeddict(Union[list, str]) # => False
"""
return isinstance(tp, tuple(_TYPEDDICT_TYPES))
# On 3.8, this would otherwise return True
if hasattr(typing, "TypedDict") and tp is typing.TypedDict:
return False
return isinstance(tp, _TYPEDDICT_TYPES)
if hasattr(typing, "assert_type"):
@@ -1372,11 +1449,7 @@ if hasattr(typing, 'TypeAlias'):
TypeAlias = typing.TypeAlias
# 3.9
elif sys.version_info[:2] >= (3, 9):
class _TypeAliasForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
@_TypeAliasForm
@_ExtensionsSpecialForm
def TypeAlias(self, parameters):
"""Special marker indicating that an assignment should
be recognized as a proper type alias definition by type
@@ -1391,21 +1464,19 @@ elif sys.version_info[:2] >= (3, 9):
raise TypeError(f"{self} is not subscriptable")
# 3.7-3.8
else:
class _TypeAliasForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
TypeAlias = _ExtensionsSpecialForm(
'TypeAlias',
doc="""Special marker indicating that an assignment should
be recognized as a proper type alias definition by type
checkers.
TypeAlias = _TypeAliasForm('TypeAlias',
doc="""Special marker indicating that an assignment should
be recognized as a proper type alias definition by type
checkers.
For example::
For example::
Predicate: TypeAlias = Callable[..., bool]
Predicate: TypeAlias = Callable[..., bool]
It's invalid when used anywhere except as in the example
above.""")
It's invalid when used anywhere except as in the example
above."""
)
def _set_default(type_param, default):
@@ -1720,7 +1791,7 @@ if hasattr(typing, 'Concatenate'):
_ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa: F811
# 3.9
elif sys.version_info[:2] >= (3, 9):
@_TypeAliasForm
@_ExtensionsSpecialForm
def Concatenate(self, parameters):
"""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
higher order function which adds, removes or transforms parameters of a
@@ -1735,10 +1806,7 @@ elif sys.version_info[:2] >= (3, 9):
return _concatenate_getitem(self, parameters)
# 3.7-8
else:
class _ConcatenateForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
class _ConcatenateForm(_ExtensionsSpecialForm, _root=True):
def __getitem__(self, parameters):
return _concatenate_getitem(self, parameters)
@@ -1760,11 +1828,7 @@ if hasattr(typing, 'TypeGuard'):
TypeGuard = typing.TypeGuard
# 3.9
elif sys.version_info[:2] >= (3, 9):
class _TypeGuardForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
@_TypeGuardForm
@_ExtensionsSpecialForm
def TypeGuard(self, parameters):
"""Special typing form used to annotate the return type of a user-defined
type guard function. ``TypeGuard`` only accepts a single type argument.
@@ -1812,11 +1876,7 @@ elif sys.version_info[:2] >= (3, 9):
return typing._GenericAlias(self, (item,))
# 3.7-3.8
else:
class _TypeGuardForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
class _TypeGuardForm(_ExtensionsSpecialForm, _root=True):
def __getitem__(self, parameters):
item = typing._type_check(parameters,
f'{self._name} accepts only a single type')
@@ -1921,7 +1981,7 @@ else:
Example::
from typing_extensions import LiteralString
from pipenv.patched.pip._vendor.typing_extensions import LiteralString
def query(sql: LiteralString) -> ...:
...
@@ -1966,7 +2026,7 @@ else:
This can be used to define a function that should never be
called, or a function that never returns::
from typing_extensions import Never
from pipenv.patched.pip._vendor.typing_extensions import Never
def never_call_me(arg: Never) -> None:
pass
@@ -1990,10 +2050,6 @@ if hasattr(typing, 'Required'):
Required = typing.Required
NotRequired = typing.NotRequired
elif sys.version_info[:2] >= (3, 9):
class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
@_ExtensionsSpecialForm
def Required(self, parameters):
"""A special typing construct to mark a key of a total=False TypedDict
@@ -2032,10 +2088,7 @@ elif sys.version_info[:2] >= (3, 9):
return typing._GenericAlias(self, (item,))
else:
class _RequiredForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
class _RequiredForm(_ExtensionsSpecialForm, _root=True):
def __getitem__(self, parameters):
item = typing._type_check(parameters,
f'{self._name} accepts only a single type.')
@@ -2123,14 +2176,11 @@ if sys.version_info >= (3, 12): # PEP 692 changed the repr of Unpack[]
return get_origin(obj) is Unpack
elif sys.version_info[:2] >= (3, 9):
class _UnpackSpecialForm(typing._SpecialForm, _root=True):
class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True):
def __init__(self, getitem):
super().__init__(getitem)
self.__doc__ = _UNPACK_DOC
def __repr__(self):
return 'typing_extensions.' + self._name
class _UnpackAlias(typing._GenericAlias, _root=True):
__class__ = typing.TypeVar
@@ -2146,10 +2196,7 @@ else:
class _UnpackAlias(typing._GenericAlias, _root=True):
__class__ = typing.TypeVar
class _UnpackForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name
class _UnpackForm(_ExtensionsSpecialForm, _root=True):
def __getitem__(self, parameters):
item = typing._type_check(parameters,
f'{self._name} accepts only a single type.')
@@ -2327,7 +2374,7 @@ else:
Example:
from typing_extensions import dataclass_transform
from pipenv.patched.pip._vendor.typing_extensions import dataclass_transform
_T = TypeVar("_T")
@@ -2541,7 +2588,8 @@ if not hasattr(typing, "TypeVarTuple"):
# In 3.11, the ability to define generic `NamedTuple`s was supported.
# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
# On 3.12, we added __orig_bases__ to call-based NamedTuples
if sys.version_info >= (3, 12):
# On 3.13, we deprecated kwargs-based NamedTuples
if sys.version_info >= (3, 13):
NamedTuple = typing.NamedTuple
else:
def _make_nmtuple(name, types, module, defaults=()):
@@ -2585,8 +2633,11 @@ else:
)
nm_tpl.__bases__ = bases
if typing.Generic in bases:
class_getitem = typing.Generic.__class_getitem__.__func__
nm_tpl.__class_getitem__ = classmethod(class_getitem)
if hasattr(typing, '_generic_class_getitem'): # 3.12+
nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem)
else:
class_getitem = typing.Generic.__class_getitem__.__func__
nm_tpl.__class_getitem__ = classmethod(class_getitem)
# update from user namespace without overriding special namedtuple attributes
for key in ns:
if key in _prohibited_namedtuple_fields:
@@ -2597,30 +2648,87 @@ else:
nm_tpl.__init_subclass__()
return nm_tpl
def NamedTuple(__typename, __fields=None, **kwargs):
if __fields is None:
__fields = kwargs.items()
elif kwargs:
raise TypeError("Either list of fields or keywords"
" can be provided to NamedTuple, not both")
nt = _make_nmtuple(__typename, __fields, module=_caller())
nt.__orig_bases__ = (NamedTuple,)
return nt
NamedTuple.__doc__ = typing.NamedTuple.__doc__
_NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
# On 3.8+, alter the signature so that it matches typing.NamedTuple.
# The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7,
# so just leave the signature as it is on 3.7.
if sys.version_info >= (3, 8):
NamedTuple.__text_signature__ = '(typename, fields=None, /, **kwargs)'
def _namedtuple_mro_entries(bases):
assert NamedTuple in bases
return (_NamedTuple,)
NamedTuple.__mro_entries__ = _namedtuple_mro_entries
@_ensure_subclassable(_namedtuple_mro_entries)
def NamedTuple(__typename, __fields=_marker, **kwargs):
"""Typed version of namedtuple.
Usage::
class Employee(NamedTuple):
name: str
id: int
This is equivalent to::
Employee = collections.namedtuple('Employee', ['name', 'id'])
The resulting class has an extra __annotations__ attribute, giving a
dict that maps field names to types. (The field names are also in
the _fields attribute, which is part of the namedtuple API.)
An alternative equivalent functional syntax is also accepted::
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
if __fields is _marker:
if kwargs:
deprecated_thing = "Creating NamedTuple classes using keyword arguments"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"Use the class-based or functional syntax instead."
)
else:
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
example = f"`{__typename} = NamedTuple({__typename!r}, [])`"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. "
) + example + "."
elif __fields is None:
if kwargs:
raise TypeError(
"Cannot pass `None` as the 'fields' parameter "
"and also specify fields using keyword arguments"
)
else:
deprecated_thing = "Passing `None` as the 'fields' parameter"
example = f"`{__typename} = NamedTuple({__typename!r}, [])`"
deprecation_msg = (
"{name} is deprecated and will be disallowed in Python {remove}. "
"To create a NamedTuple class with 0 fields "
"using the functional syntax, "
"pass an empty list, e.g. "
) + example + "."
elif kwargs:
raise TypeError("Either list of fields or keywords"
" can be provided to NamedTuple, not both")
if __fields is _marker or __fields is None:
warnings.warn(
deprecation_msg.format(name=deprecated_thing, remove="3.15"),
DeprecationWarning,
stacklevel=2,
)
__fields = kwargs.items()
nt = _make_nmtuple(__typename, __fields, module=_caller())
nt.__orig_bases__ = (NamedTuple,)
return nt
# On 3.8+, alter the signature so that it matches typing.NamedTuple.
# The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7,
# so just leave the signature as it is on 3.7.
if sys.version_info >= (3, 8):
_new_signature = '(typename, fields=None, /, **kwargs)'
if isinstance(NamedTuple, _types.FunctionType):
NamedTuple.__text_signature__ = _new_signature
else:
NamedTuple.__call__.__text_signature__ = _new_signature
if hasattr(collections.abc, "Buffer"):
@@ -2662,7 +2770,7 @@ else:
Examples::
from typing import TypeVar, Generic
from typing_extensions import NamedTuple, TypedDict
from pipenv.patched.pip._vendor.typing_extensions import NamedTuple, TypedDict
T = TypeVar("T")
class Foo(Generic[T]): ...
@@ -2873,3 +2981,92 @@ else:
if not _is_unionable(left):
return NotImplemented
return typing.Union[left, self]
if hasattr(typing, "is_protocol"):
is_protocol = typing.is_protocol
get_protocol_members = typing.get_protocol_members
else:
def is_protocol(__tp: type) -> bool:
"""Return True if the given type is a Protocol.
Example::
>>> from pipenv.patched.pip._vendor.typing_extensions import Protocol, is_protocol
>>> class P(Protocol):
... def a(self) -> str: ...
... b: int
>>> is_protocol(P)
True
>>> is_protocol(int)
False
"""
return (
isinstance(__tp, type)
and getattr(__tp, '_is_protocol', False)
and __tp is not Protocol
and __tp is not getattr(typing, "Protocol", object())
)
def get_protocol_members(__tp: type) -> typing.FrozenSet[str]:
"""Return the set of members defined in a Protocol.
Example::
>>> from pipenv.patched.pip._vendor.typing_extensions import Protocol, get_protocol_members
>>> class P(Protocol):
... def a(self) -> str: ...
... b: int
>>> get_protocol_members(P)
frozenset({'a', 'b'})
Raise a TypeError for arguments that are not Protocols.
"""
if not is_protocol(__tp):
raise TypeError(f'{__tp!r} is not a Protocol')
if hasattr(__tp, '__protocol_attrs__'):
return frozenset(__tp.__protocol_attrs__)
return frozenset(_get_protocol_attrs(__tp))
# Aliases for items that have always been in typing.
# Explicitly assign these (rather than using `from typing import *` at the top),
# so that we get a CI error if one of these is deleted from typing.py
# in a future version of Python
AbstractSet = typing.AbstractSet
AnyStr = typing.AnyStr
BinaryIO = typing.BinaryIO
Callable = typing.Callable
Collection = typing.Collection
Container = typing.Container
Dict = typing.Dict
ForwardRef = typing.ForwardRef
FrozenSet = typing.FrozenSet
Generator = typing.Generator
Generic = typing.Generic
Hashable = typing.Hashable
IO = typing.IO
ItemsView = typing.ItemsView
Iterable = typing.Iterable
Iterator = typing.Iterator
KeysView = typing.KeysView
List = typing.List
Mapping = typing.Mapping
MappingView = typing.MappingView
Match = typing.Match
MutableMapping = typing.MutableMapping
MutableSequence = typing.MutableSequence
MutableSet = typing.MutableSet
Optional = typing.Optional
Pattern = typing.Pattern
Reversible = typing.Reversible
Sequence = typing.Sequence
Set = typing.Set
Sized = typing.Sized
TextIO = typing.TextIO
Tuple = typing.Tuple
Union = typing.Union
ValuesView = typing.ValuesView
cast = typing.cast
no_type_check = typing.no_type_check
no_type_check_decorator = typing.no_type_check_decorator
@@ -1,2 +1,2 @@
# This file is protected via CODEOWNERS
__version__ = "1.26.15"
__version__ = "1.26.16"

Some files were not shown because too many files have changed in this diff Show More