mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'master' into feature/keep-outdated-peep
Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
@@ -10,9 +10,8 @@ jobs:
|
||||
maxParallel: 4
|
||||
matrix:
|
||||
${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
|
||||
# TODO remove once vs2017-win2016 has Python 3.7
|
||||
Python37:
|
||||
python.version: '>= 3.7.0-b2'
|
||||
python.version: '>= 3.7.2'
|
||||
python.architecture: x64
|
||||
${{ if ne(parameters.vmImage, 'vs2017-win2016' )}}:
|
||||
Python37:
|
||||
@@ -33,7 +32,7 @@ jobs:
|
||||
pip install certifi
|
||||
export GIT_SSL_CAINFO=$(python -m certifi)
|
||||
export LANG=C.UTF-8
|
||||
python -m pip install --upgrade invoke requests parver bs4 vistir towncrier
|
||||
python -m pip install --upgrade invoke requests parver bs4 vistir towncrier pip setuptools wheel --upgrade-strategy=eager
|
||||
python -m invoke vendoring.update
|
||||
|
||||
- template: ./run-manifest-check.yml
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a bug which caused editable package resolution to sometimes fail with an unhelpful setuptools-related error message.
|
||||
@@ -0,0 +1 @@
|
||||
Dependency resolution now writes hashes for local and remote files to the lockfile.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a bug which prevented ``pipenv graph`` from correctly showing all dependencies when running from within ``pipenv shell``.
|
||||
@@ -0,0 +1,3 @@
|
||||
Fixed a bug which caused failures in warning reporting when running pipenv inside a virtualenv under some circumstances.
|
||||
|
||||
- Fixed a bug with package discovery when running ``pipenv clean``.
|
||||
@@ -0,0 +1,5 @@
|
||||
Added full support for resolution of all dependency types including direct URLs, zip archives, tarballs, etc.
|
||||
|
||||
- Improved error handling and formatting.
|
||||
|
||||
- Introduced improved cross platform stream wrappers for better ``stdout`` and ``stderr`` consistency.
|
||||
@@ -0,0 +1,28 @@
|
||||
Updated vendored dependencies:
|
||||
|
||||
- **attrs**: ``18.2.0`` => ``19.1.0``
|
||||
- **certifi**: ``2018.10.15`` => ``2018.11.29``
|
||||
- **cached_property**: ``1.4.3`` => ``1.5.1``
|
||||
- **colorama**: ``0.3.9`` => ``0.4.1``
|
||||
- **idna**: ``2.7`` => ``2.8``
|
||||
- **markupsafe**: ``1.0`` => ``1.1.1``
|
||||
- **orderedmultidict**: ``(new)`` => ``1.0``
|
||||
- **packaging**: ``18.0`` => ``19.0``
|
||||
- **parse**: ``1.9.0`` => ``1.11.1``
|
||||
- **pathlib2**: ``2.3.2`` => ``2.3.3``
|
||||
- **pep517**: ``(new)`` => ``0.5.0``
|
||||
- **pipdeptree**: ``0.13.0`` => ``0.13.2``
|
||||
- **pyparsing**: ``2.2.2`` => ``2.3.1``
|
||||
- **python-dotenv**: ``0.9.1`` => ``0.10.1``
|
||||
- **pythonfinder**: ``1.1.10`` => ``1.2.0``
|
||||
- **pytoml**: ``(new)`` => ``0.1.20``
|
||||
- **requests**: ``2.20.1`` => ``2.21.0``
|
||||
- **requirementslib**: ``1.3.3`` => ``1.4.2``
|
||||
- **shellingham**: ``1.2.7`` => ``1.2.8``
|
||||
- **six**: ``1.11.0`` => ``1.12.0``
|
||||
- **tomlkit**: ``0.5.2`` => ``0.5.3``
|
||||
- **urllib3**: ``1.24`` => ``1.24.1``
|
||||
- **vistir**: ``0.3.0`` => ``0.3.1``
|
||||
- **yaspin**: ``0.14.0`` => ``0.14.1``
|
||||
|
||||
- Removed vendored dependency **cursor**.
|
||||
@@ -0,0 +1 @@
|
||||
Pipenv will now successfully recursively lock VCS sub-dependencies.
|
||||
@@ -0,0 +1 @@
|
||||
Pipenv will now discover and resolve the intrinsic dependencies of **all** VCS dependencies, whether they are editable or not, to prevent resolution conflicts.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a keyerror which could occur when locking VCS dependencies in some cases.
|
||||
@@ -1 +1 @@
|
||||
Fix a bug that ``ValidationError`` is thrown when some fields are missing in source section.
|
||||
Fixed a bug that ``ValidationError`` is thrown when some fields are missing in source section.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Fix the wrong order of old and new hashes in message.
|
||||
Fixed the wrong order of old and new hashes in message.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Update the index names in lock file when source name in Pipfile is changed.
|
||||
Updated the index names in lock file when source name in Pipfile is changed.
|
||||
|
||||
@@ -12,8 +12,6 @@ import click_completion
|
||||
import crayons
|
||||
import delegator
|
||||
|
||||
from click_didyoumean import DYMCommandCollection
|
||||
|
||||
from ..__version__ import __version__
|
||||
from .options import (
|
||||
CONTEXT_SETTINGS, PipenvGroup, code_option, common_options, deploy_option,
|
||||
@@ -400,8 +398,7 @@ def shell(
|
||||
@pass_state
|
||||
def run(state, command, args):
|
||||
"""Spawns a command installed into the virtualenv."""
|
||||
from ..core import do_run, warn_in_virtualenv
|
||||
warn_in_virtualenv()
|
||||
from ..core import do_run
|
||||
do_run(
|
||||
command=command, args=args, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror
|
||||
)
|
||||
@@ -634,8 +631,5 @@ def clean(ctx, state, dry_run=False, bare=False, user=False):
|
||||
system=state.system)
|
||||
|
||||
|
||||
# Only invoke the "did you mean" when an argument wasn't passed (it breaks those).
|
||||
if "-" not in "".join(sys.argv) and len(sys.argv) > 1:
|
||||
cli = DYMCommandCollection(sources=[cli])
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
||||
@@ -8,6 +8,7 @@ import click.types
|
||||
from click import (
|
||||
BadParameter, Group, Option, argument, echo, make_pass_decorator, option
|
||||
)
|
||||
from click_didyoumean import DYMMixin
|
||||
|
||||
from .. import environments
|
||||
from ..utils import is_valid_url
|
||||
@@ -19,7 +20,7 @@ CONTEXT_SETTINGS = {
|
||||
}
|
||||
|
||||
|
||||
class PipenvGroup(Group):
|
||||
class PipenvGroup(DYMMixin, Group):
|
||||
"""Custom Group class provides formatted main help"""
|
||||
|
||||
def get_help_option(self, ctx):
|
||||
|
||||
+3
-7
@@ -1281,7 +1281,7 @@ def pip_install(
|
||||
use_pep517=True
|
||||
):
|
||||
from pipenv.patched.notpip._internal import logger as piplogger
|
||||
from .utils import Mapping
|
||||
from .vendor.vistir.compat import Mapping
|
||||
from .vendor.urllib3.util import parse_url
|
||||
src = []
|
||||
write_to_tmpfile = False
|
||||
@@ -1432,20 +1432,16 @@ def pip_install(
|
||||
possible_hashes = install_reqs[:]
|
||||
editable_opt, req = req.split(" ", 1)
|
||||
install_reqs = [editable_opt, req] + install_reqs
|
||||
|
||||
# hashes must be passed via a file
|
||||
ignore_hashes = True
|
||||
# if possible_hashes and not any(
|
||||
# item.startswith("--hash") for item in possible_hashes.split()
|
||||
# ):
|
||||
# ignore_hashes = True
|
||||
elif r:
|
||||
install_reqs = ["-r", r]
|
||||
with open(r) as f:
|
||||
if "--hash" not in f.read():
|
||||
ignore_hashes = True
|
||||
else:
|
||||
# hashes need to be passed via a file
|
||||
ignore_hashes = True
|
||||
ignore_hashes = True if not requirement.hashes else ignore_hashes
|
||||
install_reqs = requirement.as_line(as_list=True, include_hashes=not ignore_hashes)
|
||||
if not requirement.markers:
|
||||
install_reqs = [escape_cmd(r) for r in install_reqs]
|
||||
|
||||
@@ -188,7 +188,8 @@ class Environment(object):
|
||||
|
||||
@cached_property
|
||||
def sys_path(self):
|
||||
"""The system path inside the environment
|
||||
"""
|
||||
The system path inside the environment
|
||||
|
||||
:return: The :data:`sys.path` from the environment
|
||||
:rtype: list
|
||||
|
||||
@@ -179,7 +179,8 @@ class SystemUsageError(PipenvOptionsError):
|
||||
crayons.red("Warning", bold=True)
|
||||
),
|
||||
]
|
||||
message = crayons.blue("See also: {0}".format(crayons.white("-deploy flag.")))
|
||||
if message is None:
|
||||
message = crayons.blue("See also: {0}".format(crayons.white("--deploy flag.")))
|
||||
super(SystemUsageError, self).__init__(option_name, message=message, ctx=ctx, extra=extra, **kwargs)
|
||||
|
||||
|
||||
@@ -248,8 +249,14 @@ class UninstallError(PipenvException):
|
||||
|
||||
class InstallError(PipenvException):
|
||||
def __init__(self, package, **kwargs):
|
||||
message = "{0} {1}".format(
|
||||
package_message = ""
|
||||
if package is not None:
|
||||
package_message = crayons.normal("Couldn't install package {0}\n".format(
|
||||
crayons.white(package, bold=True)
|
||||
))
|
||||
message = "{0} {1} {2}".format(
|
||||
crayons.red("ERROR:", bold=True),
|
||||
package_message,
|
||||
crayons.yellow("Package installation failed...")
|
||||
)
|
||||
extra = kwargs.pop("extra", [])
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
__author__ = """pyup.io"""
|
||||
__email__ = 'support@pyup.io'
|
||||
__version__ = '1.8.4'
|
||||
__version__ = '1.8.5'
|
||||
|
||||
@@ -6,17 +6,13 @@ from safety import __version__
|
||||
from safety import safety
|
||||
from safety.formatter import report
|
||||
import itertools
|
||||
from safety.util import read_requirements
|
||||
from safety.util import read_requirements, read_vulnerabilities
|
||||
from safety.errors import DatabaseFetchError, DatabaseFileNotFoundError, InvalidKeyError
|
||||
|
||||
|
||||
try:
|
||||
# pip 9
|
||||
from pipenv.patched.notpip import get_installed_distributions
|
||||
from json.decoder import JSONDecodeError
|
||||
except ImportError:
|
||||
# pip 10
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions
|
||||
|
||||
JSONDecodeError = ValueError
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version=__version__)
|
||||
@@ -46,10 +42,17 @@ def cli():
|
||||
help="Read input from one (or multiple) requirement files. Default: empty")
|
||||
@click.option("ignore", "--ignore", "-i", multiple=True, type=str, default=[],
|
||||
help="Ignore one (or multiple) vulnerabilities by ID. Default: empty")
|
||||
def check(key, db, json, full_report, bare, stdin, files, cache, ignore):
|
||||
|
||||
@click.option("--output", "-o", default="",
|
||||
help="Path to where output file will be placed. Default: empty")
|
||||
@click.option("proxyhost", "--proxy-host", "-ph", multiple=False, type=str, default=None,
|
||||
help="Proxy host IP or DNS --proxy-host")
|
||||
@click.option("proxyport", "--proxy-port", "-pp", multiple=False, type=int, default=80,
|
||||
help="Proxy port number --proxy-port")
|
||||
@click.option("proxyprotocol", "--proxy-protocol", "-pr", multiple=False, type=str, default='http',
|
||||
help="Proxy protocol (https or http) --proxy-protocol")
|
||||
def check(key, db, json, full_report, bare, stdin, files, cache, ignore, output, proxyprotocol, proxyhost, proxyport):
|
||||
if files and stdin:
|
||||
click.secho("Can't read from --stdin and --file at the same time, exiting", fg="red")
|
||||
click.secho("Can't read from --stdin and --file at the same time, exiting", fg="red", file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
if files:
|
||||
@@ -57,33 +60,72 @@ def check(key, db, json, full_report, bare, stdin, files, cache, ignore):
|
||||
elif stdin:
|
||||
packages = list(read_requirements(sys.stdin))
|
||||
else:
|
||||
packages = get_installed_distributions()
|
||||
|
||||
import pkg_resources
|
||||
packages = [
|
||||
d for d in pkg_resources.working_set
|
||||
if d.key not in {"python", "wsgiref", "argparse"}
|
||||
]
|
||||
proxy_dictionary = {}
|
||||
if proxyhost is not None:
|
||||
if proxyprotocol in ["http", "https"]:
|
||||
proxy_dictionary = {proxyprotocol: "{0}://{1}:{2}".format(proxyprotocol, proxyhost, str(proxyport))}
|
||||
else:
|
||||
click.secho("Proxy Protocol should be http or https only.", fg="red")
|
||||
sys.exit(-1)
|
||||
try:
|
||||
vulns = safety.check(packages=packages, key=key, db_mirror=db, cached=cache, ignore_ids=ignore)
|
||||
click.secho(report(
|
||||
vulns=vulns,
|
||||
full=full_report,
|
||||
json_report=json,
|
||||
bare_report=bare,
|
||||
checked_packages=len(packages),
|
||||
db=db,
|
||||
key=key
|
||||
)
|
||||
)
|
||||
vulns = safety.check(packages=packages, key=key, db_mirror=db, cached=cache, ignore_ids=ignore, proxy=proxy_dictionary)
|
||||
output_report = report(vulns=vulns,
|
||||
full=full_report,
|
||||
json_report=json,
|
||||
bare_report=bare,
|
||||
checked_packages=len(packages),
|
||||
db=db,
|
||||
key=key)
|
||||
|
||||
if output:
|
||||
with open(output, 'w+') as output_file:
|
||||
output_file.write(output_report)
|
||||
else:
|
||||
click.secho(output_report, nl=False if bare and not vulns else True)
|
||||
sys.exit(-1 if vulns else 0)
|
||||
except InvalidKeyError:
|
||||
click.secho("Your API Key '{key}' is invalid. See {link}".format(
|
||||
key=key, link='https://goo.gl/O7Y1rS'),
|
||||
fg="red")
|
||||
fg="red",
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
except DatabaseFileNotFoundError:
|
||||
click.secho("Unable to load vulnerability database from {db}".format(db=db), fg="red")
|
||||
click.secho("Unable to load vulnerability database from {db}".format(db=db), fg="red", file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
except DatabaseFetchError:
|
||||
click.secho("Unable to load vulnerability database", fg="red")
|
||||
click.secho("Unable to load vulnerability database", fg="red", file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--full-report/--short-report", default=False,
|
||||
help='Full reports include a security advisory (if available). Default: '
|
||||
'--short-report')
|
||||
@click.option("--bare/--not-bare", default=False,
|
||||
help='Output vulnerable packages only. Useful in combination with other tools.'
|
||||
'Default: --not-bare')
|
||||
@click.option("file", "--file", "-f", type=click.File(), required=True,
|
||||
help="Read input from an insecure report file. Default: empty")
|
||||
def review(full_report, bare, file):
|
||||
if full_report and bare:
|
||||
click.secho("Can't choose both --bare and --full-report/--short-report", fg="red")
|
||||
sys.exit(-1)
|
||||
|
||||
try:
|
||||
input_vulns = read_vulnerabilities(file)
|
||||
except JSONDecodeError:
|
||||
click.secho("Not a valid JSON file", fg="red")
|
||||
sys.exit(-1)
|
||||
|
||||
vulns = safety.review(input_vulns)
|
||||
output_report = report(vulns=vulns, full=full_report, bare_report=bare)
|
||||
click.secho(output_report, nl=False if bare and not vulns else True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
||||
@@ -3,6 +3,7 @@ import platform
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
import textwrap
|
||||
|
||||
# python 2.7 compat
|
||||
try:
|
||||
@@ -110,9 +111,10 @@ class SheetReport(object):
|
||||
|
||||
descr = get_advisory(vuln)
|
||||
|
||||
for chunk in [descr[i:i + 76] for i in range(0, len(descr), 76)]:
|
||||
|
||||
for line in chunk.splitlines():
|
||||
for pn, paragraph in enumerate(descr.replace('\r', '').split('\n\n')):
|
||||
if pn:
|
||||
table.append("│ {:76} │".format(''))
|
||||
for line in textwrap.wrap(paragraph, width=76):
|
||||
try:
|
||||
table.append("│ {:76} │".format(line.encode('utf-8')))
|
||||
except TypeError:
|
||||
|
||||
@@ -9,6 +9,7 @@ import json
|
||||
import time
|
||||
import errno
|
||||
|
||||
|
||||
class Vulnerability(namedtuple("Vulnerability",
|
||||
["name", "spec", "version", "advisory", "vuln_id"])):
|
||||
pass
|
||||
@@ -64,7 +65,7 @@ def write_to_cache(db_name, data):
|
||||
f.write(json.dumps(cache))
|
||||
|
||||
|
||||
def fetch_database_url(mirror, db_name, key, cached):
|
||||
def fetch_database_url(mirror, db_name, key, cached, proxy):
|
||||
|
||||
headers = {}
|
||||
if key:
|
||||
@@ -74,9 +75,8 @@ def fetch_database_url(mirror, db_name, key, cached):
|
||||
cached_data = get_from_cache(db_name=db_name)
|
||||
if cached_data:
|
||||
return cached_data
|
||||
|
||||
url = mirror + db_name
|
||||
r = requests.get(url=url, timeout=REQUEST_TIMEOUT, headers=headers)
|
||||
r = requests.get(url=url, timeout=REQUEST_TIMEOUT, headers=headers, proxies=proxy)
|
||||
if r.status_code == 200:
|
||||
data = r.json()
|
||||
if cached:
|
||||
@@ -94,7 +94,7 @@ def fetch_database_file(path, db_name):
|
||||
return json.loads(f.read())
|
||||
|
||||
|
||||
def fetch_database(full=False, key=False, db=False, cached=False):
|
||||
def fetch_database(full=False, key=False, db=False, cached=False, proxy={}):
|
||||
|
||||
if db:
|
||||
mirrors = [db]
|
||||
@@ -105,7 +105,7 @@ def fetch_database(full=False, key=False, db=False, cached=False):
|
||||
for mirror in mirrors:
|
||||
# mirror can either be a local path or a URL
|
||||
if mirror.startswith("http://") or mirror.startswith("https://"):
|
||||
data = fetch_database_url(mirror, db_name=db_name, key=key, cached=cached)
|
||||
data = fetch_database_url(mirror, db_name=db_name, key=key, cached=cached, proxy=proxy)
|
||||
else:
|
||||
data = fetch_database_file(mirror, db_name=db_name)
|
||||
if data:
|
||||
@@ -120,10 +120,9 @@ def get_vulnerabilities(pkg, spec, db):
|
||||
yield entry
|
||||
|
||||
|
||||
def check(packages, key, db_mirror, cached, ignore_ids):
|
||||
|
||||
def check(packages, key, db_mirror, cached, ignore_ids, proxy):
|
||||
key = key if key else os.environ.get("SAFETY_API_KEY", False)
|
||||
db = fetch_database(key=key, db=db_mirror, cached=cached)
|
||||
db = fetch_database(key=key, db=db_mirror, cached=cached, proxy=proxy)
|
||||
db_full = None
|
||||
vulnerable_packages = frozenset(db.keys())
|
||||
vulnerable = []
|
||||
@@ -152,3 +151,19 @@ def check(packages, key, db_mirror, cached, ignore_ids):
|
||||
)
|
||||
)
|
||||
return vulnerable
|
||||
|
||||
|
||||
def review(vulnerabilities):
|
||||
vulnerable = []
|
||||
for vuln in vulnerabilities:
|
||||
current_vuln = {
|
||||
"name": vuln[0],
|
||||
"spec": vuln[1],
|
||||
"version": vuln[2],
|
||||
"advisory": vuln[3],
|
||||
"vuln_id": vuln[4],
|
||||
}
|
||||
vulnerable.append(
|
||||
Vulnerability(**current_vuln)
|
||||
)
|
||||
return vulnerable
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
from dparse.parser import setuptools_parse_requirements_backport as _parse_requirements
|
||||
from collections import namedtuple
|
||||
import click
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
Package = namedtuple("Package", ["key", "version"])
|
||||
RequirementFile = namedtuple("RequirementFile", ["path"])
|
||||
|
||||
|
||||
def read_vulnerabilities(fh):
|
||||
return json.load(fh)
|
||||
|
||||
|
||||
def iter_lines(fh, lineno=0):
|
||||
for line in fh.readlines()[lineno:]:
|
||||
yield line
|
||||
@@ -85,7 +91,8 @@ def read_requirements(fh, resolve=False):
|
||||
"Warning: unpinned requirement '{req}' found in {fname}, "
|
||||
"unable to check.".format(req=req.name,
|
||||
fname=fname),
|
||||
fg="yellow"
|
||||
fg="yellow",
|
||||
file=sys.stderr
|
||||
)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
@@ -539,6 +539,7 @@ class Project(object):
|
||||
def build_requires(self):
|
||||
return self._build_system.get("requires", ["setuptools>=40.8.0", "wheel"])
|
||||
|
||||
|
||||
@property
|
||||
def build_backend(self):
|
||||
return self._build_system.get("build-backend", get_default_pyproject_backend())
|
||||
|
||||
+18
-5
@@ -330,6 +330,7 @@ class Resolver(object):
|
||||
):
|
||||
# type: (...) -> Tuple[Requirement, Dict[str, str], Dict[str, str]]
|
||||
from .vendor.requirementslib.models.requirements import Requirement
|
||||
from .exceptions import ResolutionFailure
|
||||
if index_lookup is None:
|
||||
index_lookup = {}
|
||||
if markers_lookup is None:
|
||||
@@ -343,7 +344,10 @@ class Resolver(object):
|
||||
url = indexes[0]
|
||||
line = " ".join(remainder)
|
||||
req = None # type: Requirement
|
||||
req = Requirement.from_line(line)
|
||||
try:
|
||||
req = Requirement.from_line(line)
|
||||
except ValueError:
|
||||
raise ResolutionFailure("Failed to resolve requirement from line: {0!s}".format(line))
|
||||
if url:
|
||||
try:
|
||||
index_lookup[req.normalized_name] = project.get_source(
|
||||
@@ -397,7 +401,14 @@ class Resolver(object):
|
||||
continue
|
||||
line = _requirement_to_str_lowercase_name(r)
|
||||
new_req, _, _ = cls.parse_line(line)
|
||||
new_constraints, new_lock = cls.get_deps_from_req(new_req)
|
||||
if r.marker and not r.marker.evaluate():
|
||||
new_constraints = {}
|
||||
_, new_entry = req.pipfile_entry
|
||||
new_lock = {
|
||||
pep423_name(new_req.normalized_name): new_entry
|
||||
}
|
||||
else:
|
||||
new_constraints, new_lock = cls.get_deps_from_req(new_req)
|
||||
locked_deps.update(new_lock)
|
||||
constraints |= new_constraints
|
||||
else:
|
||||
@@ -767,14 +778,15 @@ def actually_resolve_deps(
|
||||
|
||||
@contextlib.contextmanager
|
||||
def create_spinner(text, nospin=None, spinner_name=None):
|
||||
import vistir.spin
|
||||
from .vendor.vistir import spin
|
||||
from .vendor.vistir.misc import fs_str
|
||||
if not spinner_name:
|
||||
spinner_name = environments.PIPENV_SPINNER
|
||||
if nospin is None:
|
||||
nospin = environments.PIPENV_NOSPIN
|
||||
with vistir.spin.create_spinner(
|
||||
with spin.create_spinner(
|
||||
spinner_name=spinner_name,
|
||||
start_text=vistir.compat.fs_str(text),
|
||||
start_text=fs_str(text),
|
||||
nospin=nospin, write_to_stdout=False
|
||||
) as sp:
|
||||
yield sp
|
||||
@@ -947,6 +959,7 @@ def venv_resolve_deps(
|
||||
cmd.append("--dev")
|
||||
with temp_environ():
|
||||
os.environ.update({fs_str(k): fs_str(val) for k, val in os.environ.items()})
|
||||
os.environ["PIPENV_PACKAGES"] = str("\n".join(constraints))
|
||||
if pypi_mirror:
|
||||
os.environ["PIPENV_PYPI_MIRROR"] = str(pypi_mirror)
|
||||
os.environ["PIPENV_VERBOSITY"] = str(environments.PIPENV_VERBOSITY)
|
||||
|
||||
Vendored
+1
-1
@@ -18,7 +18,7 @@ from ._make import (
|
||||
)
|
||||
|
||||
|
||||
__version__ = "18.2.0"
|
||||
__version__ = "19.1.0"
|
||||
|
||||
__title__ = "attrs"
|
||||
__description__ = "Classes Without Boilerplate"
|
||||
|
||||
Vendored
+15
-12
@@ -23,9 +23,9 @@ from . import validators as validators
|
||||
_T = TypeVar("_T")
|
||||
_C = TypeVar("_C", bound=type)
|
||||
|
||||
_ValidatorType = Callable[[Any, Attribute, _T], Any]
|
||||
_ValidatorType = Callable[[Any, Attribute[_T], _T], Any]
|
||||
_ConverterType = Callable[[Any], _T]
|
||||
_FilterType = Callable[[Attribute, Any], bool]
|
||||
_FilterType = Callable[[Attribute[_T], _T], bool]
|
||||
# FIXME: in reality, if multiple validators are passed they must be in a list or tuple,
|
||||
# but those are invariant and so would prevent subtypes of _ValidatorType from working
|
||||
# when passed in a list or tuple.
|
||||
@@ -57,10 +57,10 @@ class Attribute(Generic[_T]):
|
||||
metadata: Dict[Any, Any]
|
||||
type: Optional[Type[_T]]
|
||||
kw_only: bool
|
||||
def __lt__(self, x: Attribute) -> bool: ...
|
||||
def __le__(self, x: Attribute) -> bool: ...
|
||||
def __gt__(self, x: Attribute) -> bool: ...
|
||||
def __ge__(self, x: Attribute) -> bool: ...
|
||||
def __lt__(self, x: Attribute[_T]) -> bool: ...
|
||||
def __le__(self, x: Attribute[_T]) -> bool: ...
|
||||
def __gt__(self, x: Attribute[_T]) -> bool: ...
|
||||
def __ge__(self, x: Attribute[_T]) -> bool: ...
|
||||
|
||||
# NOTE: We had several choices for the annotation to use for type arg:
|
||||
# 1) Type[_T]
|
||||
@@ -167,6 +167,7 @@ def attrs(
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
) -> _C: ...
|
||||
@overload
|
||||
def attrs(
|
||||
@@ -184,14 +185,15 @@ def attrs(
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
) -> Callable[[_C], _C]: ...
|
||||
|
||||
# TODO: add support for returning NamedTuple from the mypy plugin
|
||||
class _Fields(Tuple[Attribute, ...]):
|
||||
def __getattr__(self, name: str) -> Attribute: ...
|
||||
class _Fields(Tuple[Attribute[Any], ...]):
|
||||
def __getattr__(self, name: str) -> Attribute[Any]: ...
|
||||
|
||||
def fields(cls: type) -> _Fields: ...
|
||||
def fields_dict(cls: type) -> Dict[str, Attribute]: ...
|
||||
def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ...
|
||||
def validate(inst: Any) -> None: ...
|
||||
|
||||
# TODO: add support for returning a proper attrs class from the mypy plugin
|
||||
@@ -212,6 +214,7 @@ def make_class(
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
) -> type: ...
|
||||
|
||||
# _funcs --
|
||||
@@ -223,7 +226,7 @@ def make_class(
|
||||
def asdict(
|
||||
inst: Any,
|
||||
recurse: bool = ...,
|
||||
filter: Optional[_FilterType] = ...,
|
||||
filter: Optional[_FilterType[Any]] = ...,
|
||||
dict_factory: Type[Mapping[Any, Any]] = ...,
|
||||
retain_collection_types: bool = ...,
|
||||
) -> Dict[str, Any]: ...
|
||||
@@ -232,8 +235,8 @@ def asdict(
|
||||
def astuple(
|
||||
inst: Any,
|
||||
recurse: bool = ...,
|
||||
filter: Optional[_FilterType] = ...,
|
||||
tuple_factory: Type[Sequence] = ...,
|
||||
filter: Optional[_FilterType[Any]] = ...,
|
||||
tuple_factory: Type[Sequence[Any]] = ...,
|
||||
retain_collection_types: bool = ...,
|
||||
) -> Tuple[Any, ...]: ...
|
||||
def has(cls: type) -> bool: ...
|
||||
|
||||
Vendored
+21
-25
@@ -20,6 +20,7 @@ else:
|
||||
|
||||
if PY2:
|
||||
from UserDict import IterableUserDict
|
||||
from collections import Mapping, Sequence # noqa
|
||||
|
||||
# We 'bundle' isclass instead of using inspect as importing inspect is
|
||||
# fairly expensive (order of 10-15 ms for a modern machine in 2016)
|
||||
@@ -89,8 +90,27 @@ if PY2:
|
||||
res.data.update(d) # We blocked update, so we have to do it like this.
|
||||
return res
|
||||
|
||||
def just_warn(*args, **kw): # pragma: nocover
|
||||
"""
|
||||
We only warn on Python 3 because we are not aware of any concrete
|
||||
consequences of not setting the cell on Python 2.
|
||||
"""
|
||||
|
||||
else:
|
||||
|
||||
else: # Python 3 and later.
|
||||
from collections.abc import Mapping, Sequence # noqa
|
||||
|
||||
def just_warn(*args, **kw):
|
||||
"""
|
||||
We only warn on Python 3 because we are not aware of any concrete
|
||||
consequences of not setting the cell on Python 2.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Missing ctypes. Some features like bare super() or accessing "
|
||||
"__class__ will not work with slotted classes.",
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def isclass(klass):
|
||||
return isinstance(klass, type)
|
||||
@@ -113,30 +133,6 @@ def import_ctypes():
|
||||
return ctypes
|
||||
|
||||
|
||||
if not PY2:
|
||||
|
||||
def just_warn(*args, **kw):
|
||||
"""
|
||||
We only warn on Python 3 because we are not aware of any concrete
|
||||
consequences of not setting the cell on Python 2.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Missing ctypes. Some features like bare super() or accessing "
|
||||
"__class__ will not work with slots classes.",
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
def just_warn(*args, **kw): # pragma: nocover
|
||||
"""
|
||||
We only warn on Python 3 because we are not aware of any concrete
|
||||
consequences of not setting the cell on Python 2.
|
||||
"""
|
||||
|
||||
|
||||
def make_set_closure_cell():
|
||||
"""
|
||||
Moved into a function for testability.
|
||||
|
||||
Vendored
+79
-27
@@ -409,12 +409,11 @@ def _transform_attrs(cls, these, auto_attribs, kw_only):
|
||||
a.kw_only is False
|
||||
):
|
||||
had_default = True
|
||||
if was_kw_only is True and a.kw_only is False:
|
||||
if was_kw_only is True and a.kw_only is False and a.init is True:
|
||||
raise ValueError(
|
||||
"Non keyword-only attributes are not allowed after a "
|
||||
"keyword-only attribute. Attribute in question: {a!r}".format(
|
||||
a=a
|
||||
)
|
||||
"keyword-only attribute (unless they are init=False). "
|
||||
"Attribute in question: {a!r}".format(a=a)
|
||||
)
|
||||
if was_kw_only is False and a.init is True and a.kw_only is True:
|
||||
was_kw_only = True
|
||||
@@ -454,6 +453,7 @@ class _ClassBuilder(object):
|
||||
"_has_post_init",
|
||||
"_delete_attribs",
|
||||
"_base_attr_map",
|
||||
"_is_exc",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
@@ -466,6 +466,7 @@ class _ClassBuilder(object):
|
||||
auto_attribs,
|
||||
kw_only,
|
||||
cache_hash,
|
||||
is_exc,
|
||||
):
|
||||
attrs, base_attrs, base_map = _transform_attrs(
|
||||
cls, these, auto_attribs, kw_only
|
||||
@@ -483,6 +484,7 @@ class _ClassBuilder(object):
|
||||
self._cache_hash = cache_hash
|
||||
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
|
||||
self._delete_attribs = not bool(these)
|
||||
self._is_exc = is_exc
|
||||
|
||||
self._cls_dict["__attrs_attrs__"] = self._attrs
|
||||
|
||||
@@ -530,6 +532,26 @@ class _ClassBuilder(object):
|
||||
for name, value in self._cls_dict.items():
|
||||
setattr(cls, name, value)
|
||||
|
||||
# Attach __setstate__. This is necessary to clear the hash code
|
||||
# cache on deserialization. See issue
|
||||
# https://github.com/python-attrs/attrs/issues/482 .
|
||||
# Note that this code only handles setstate for dict classes.
|
||||
# For slotted classes, see similar code in _create_slots_class .
|
||||
if self._cache_hash:
|
||||
existing_set_state_method = getattr(cls, "__setstate__", None)
|
||||
if existing_set_state_method:
|
||||
raise NotImplementedError(
|
||||
"Currently you cannot use hash caching if "
|
||||
"you specify your own __setstate__ method."
|
||||
"See https://github.com/python-attrs/attrs/issues/494 ."
|
||||
)
|
||||
|
||||
def cache_hash_set_state(chss_self, _):
|
||||
# clear hash code cache
|
||||
setattr(chss_self, _hash_cache_field, None)
|
||||
|
||||
setattr(cls, "__setstate__", cache_hash_set_state)
|
||||
|
||||
return cls
|
||||
|
||||
def _create_slots_class(self):
|
||||
@@ -582,6 +604,8 @@ class _ClassBuilder(object):
|
||||
"""
|
||||
return tuple(getattr(self, name) for name in state_attr_names)
|
||||
|
||||
hash_caching_enabled = self._cache_hash
|
||||
|
||||
def slots_setstate(self, state):
|
||||
"""
|
||||
Automatically created by attrs.
|
||||
@@ -589,6 +613,13 @@ class _ClassBuilder(object):
|
||||
__bound_setattr = _obj_setattr.__get__(self, Attribute)
|
||||
for name, value in zip(state_attr_names, state):
|
||||
__bound_setattr(name, value)
|
||||
# Clearing the hash code cache on deserialization is needed
|
||||
# because hash codes can change from run to run. See issue
|
||||
# https://github.com/python-attrs/attrs/issues/482 .
|
||||
# Note that this code only handles setstate for slotted classes.
|
||||
# For dict classes, see similar code in _patch_original_class .
|
||||
if hash_caching_enabled:
|
||||
__bound_setattr(_hash_cache_field, None)
|
||||
|
||||
# slots and frozen require __getstate__/__setstate__ to work
|
||||
cd["__getstate__"] = slots_getstate
|
||||
@@ -660,6 +691,7 @@ class _ClassBuilder(object):
|
||||
self._slots,
|
||||
self._cache_hash,
|
||||
self._base_attr_map,
|
||||
self._is_exc,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -710,6 +742,7 @@ def attrs(
|
||||
auto_attribs=False,
|
||||
kw_only=False,
|
||||
cache_hash=False,
|
||||
auto_exc=False,
|
||||
):
|
||||
r"""
|
||||
A class decorator that adds `dunder
|
||||
@@ -815,10 +848,23 @@ def attrs(
|
||||
:param bool cache_hash: Ensure that the object's hash code is computed
|
||||
only once and stored on the object. If this is set to ``True``,
|
||||
hashing must be either explicitly or implicitly enabled for this
|
||||
class. If the hash code is cached, then no attributes of this
|
||||
class which participate in hash code computation may be mutated
|
||||
after object creation.
|
||||
class. If the hash code is cached, avoid any reassignments of
|
||||
fields involved in hash code computation or mutations of the objects
|
||||
those fields point to after object creation. If such changes occur,
|
||||
the behavior of the object's hash code is undefined.
|
||||
:param bool auto_exc: If the class subclasses :class:`BaseException`
|
||||
(which implicitly includes any subclass of any exception), the
|
||||
following happens to behave like a well-behaved Python exceptions
|
||||
class:
|
||||
|
||||
- the values for *cmp* and *hash* are ignored and the instances compare
|
||||
and hash by the instance's ids (N.B. ``attrs`` will *not* remove
|
||||
existing implementations of ``__hash__`` or the equality methods. It
|
||||
just won't add own ones.),
|
||||
- all attributes that are either passed into ``__init__`` or have a
|
||||
default value are additionally available as a tuple in the ``args``
|
||||
attribute,
|
||||
- the value of *str* is ignored leaving ``__str__`` to base classes.
|
||||
|
||||
.. versionadded:: 16.0.0 *slots*
|
||||
.. versionadded:: 16.1.0 *frozen*
|
||||
@@ -838,12 +884,16 @@ def attrs(
|
||||
to each other.
|
||||
.. versionadded:: 18.2.0 *kw_only*
|
||||
.. versionadded:: 18.2.0 *cache_hash*
|
||||
.. versionadded:: 19.1.0 *auto_exc*
|
||||
"""
|
||||
|
||||
def wrap(cls):
|
||||
|
||||
if getattr(cls, "__class__", None) is None:
|
||||
raise TypeError("attrs only works with new-style classes.")
|
||||
|
||||
is_exc = auto_exc is True and issubclass(cls, BaseException)
|
||||
|
||||
builder = _ClassBuilder(
|
||||
cls,
|
||||
these,
|
||||
@@ -853,13 +903,14 @@ def attrs(
|
||||
auto_attribs,
|
||||
kw_only,
|
||||
cache_hash,
|
||||
is_exc,
|
||||
)
|
||||
|
||||
if repr is True:
|
||||
builder.add_repr(repr_ns)
|
||||
if str is True:
|
||||
builder.add_str()
|
||||
if cmp is True:
|
||||
if cmp is True and not is_exc:
|
||||
builder.add_cmp()
|
||||
|
||||
if hash is not True and hash is not False and hash is not None:
|
||||
@@ -874,7 +925,11 @@ def attrs(
|
||||
" hashing must be either explicitly or implicitly "
|
||||
"enabled."
|
||||
)
|
||||
elif hash is True or (hash is None and cmp is True and frozen is True):
|
||||
elif (
|
||||
hash is True
|
||||
or (hash is None and cmp is True and frozen is True)
|
||||
and is_exc is False
|
||||
):
|
||||
builder.add_hash()
|
||||
else:
|
||||
if cache_hash:
|
||||
@@ -1213,7 +1268,9 @@ def _add_repr(cls, ns=None, attrs=None):
|
||||
return cls
|
||||
|
||||
|
||||
def _make_init(attrs, post_init, frozen, slots, cache_hash, base_attr_map):
|
||||
def _make_init(
|
||||
attrs, post_init, frozen, slots, cache_hash, base_attr_map, is_exc
|
||||
):
|
||||
attrs = [a for a in attrs if a.init or a.default is not NOTHING]
|
||||
|
||||
# We cache the generated init methods for the same kinds of attributes.
|
||||
@@ -1222,16 +1279,18 @@ def _make_init(attrs, post_init, frozen, slots, cache_hash, base_attr_map):
|
||||
unique_filename = "<attrs generated init {0}>".format(sha1.hexdigest())
|
||||
|
||||
script, globs, annotations = _attrs_to_init_script(
|
||||
attrs, frozen, slots, post_init, cache_hash, base_attr_map
|
||||
attrs, frozen, slots, post_init, cache_hash, base_attr_map, is_exc
|
||||
)
|
||||
locs = {}
|
||||
bytecode = compile(script, unique_filename, "exec")
|
||||
attr_dict = dict((a.name, a) for a in attrs)
|
||||
globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
|
||||
|
||||
if frozen is True:
|
||||
# Save the lookup overhead in __init__ if we need to circumvent
|
||||
# immutability.
|
||||
globs["_cached_setattr"] = _obj_setattr
|
||||
|
||||
eval(bytecode, globs, locs)
|
||||
|
||||
# In order of debuggers like PDB being able to step through the code,
|
||||
@@ -1245,24 +1304,10 @@ def _make_init(attrs, post_init, frozen, slots, cache_hash, base_attr_map):
|
||||
|
||||
__init__ = locs["__init__"]
|
||||
__init__.__annotations__ = annotations
|
||||
|
||||
return __init__
|
||||
|
||||
|
||||
def _add_init(cls, frozen):
|
||||
"""
|
||||
Add a __init__ method to *cls*. If *frozen* is True, make it immutable.
|
||||
"""
|
||||
cls.__init__ = _make_init(
|
||||
cls.__attrs_attrs__,
|
||||
getattr(cls, "__attrs_post_init__", False),
|
||||
frozen,
|
||||
_is_slot_cls(cls),
|
||||
cache_hash=False,
|
||||
base_attr_map={},
|
||||
)
|
||||
return cls
|
||||
|
||||
|
||||
def fields(cls):
|
||||
"""
|
||||
Return the tuple of ``attrs`` attributes for a class.
|
||||
@@ -1348,7 +1393,7 @@ def _is_slot_attr(a_name, base_attr_map):
|
||||
|
||||
|
||||
def _attrs_to_init_script(
|
||||
attrs, frozen, slots, post_init, cache_hash, base_attr_map
|
||||
attrs, frozen, slots, post_init, cache_hash, base_attr_map, is_exc
|
||||
):
|
||||
"""
|
||||
Return a script of an initializer for *attrs* and a dict of globals.
|
||||
@@ -1597,6 +1642,13 @@ def _attrs_to_init_script(
|
||||
init_hash_cache = "self.%s = %s"
|
||||
lines.append(init_hash_cache % (_hash_cache_field, "None"))
|
||||
|
||||
# For exceptions we rely on BaseException.__init__ for proper
|
||||
# initialization.
|
||||
if is_exc:
|
||||
vals = ",".join("self." + a.name for a in attrs if a.init)
|
||||
|
||||
lines.append("BaseException.__init__(self, %s)" % (vals,))
|
||||
|
||||
args = ", ".join(args)
|
||||
if kw_only_args:
|
||||
if PY2:
|
||||
|
||||
Vendored
+3
-3
@@ -1,5 +1,5 @@
|
||||
from typing import Union
|
||||
from typing import Union, Any
|
||||
from . import Attribute, _FilterType
|
||||
|
||||
def include(*what: Union[type, Attribute]) -> _FilterType: ...
|
||||
def exclude(*what: Union[type, Attribute]) -> _FilterType: ...
|
||||
def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ...
|
||||
def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ...
|
||||
|
||||
Vendored
+113
-1
@@ -136,7 +136,7 @@ class _InValidator(object):
|
||||
def __call__(self, inst, attr, value):
|
||||
try:
|
||||
in_options = value in self.options
|
||||
except TypeError as e: # e.g. `1 in "abc"`
|
||||
except TypeError: # e.g. `1 in "abc"`
|
||||
in_options = False
|
||||
|
||||
if not in_options:
|
||||
@@ -168,3 +168,115 @@ def in_(options):
|
||||
.. versionadded:: 17.1.0
|
||||
"""
|
||||
return _InValidator(options)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=False, hash=True)
|
||||
class _IsCallableValidator(object):
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not callable(value):
|
||||
raise TypeError("'{name}' must be callable".format(name=attr.name))
|
||||
|
||||
def __repr__(self):
|
||||
return "<is_callable validator>"
|
||||
|
||||
|
||||
def is_callable():
|
||||
"""
|
||||
A validator that raises a :class:`TypeError` if the initializer is called
|
||||
with a value for this particular attribute that is not callable.
|
||||
|
||||
.. versionadded:: 19.1.0
|
||||
|
||||
:raises TypeError: With a human readable error message containing the
|
||||
attribute (of type :class:`attr.Attribute`) name.
|
||||
"""
|
||||
return _IsCallableValidator()
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, hash=True)
|
||||
class _DeepIterable(object):
|
||||
member_validator = attrib(validator=is_callable())
|
||||
iterable_validator = attrib(
|
||||
default=None, validator=optional(is_callable())
|
||||
)
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if self.iterable_validator is not None:
|
||||
self.iterable_validator(inst, attr, value)
|
||||
|
||||
for member in value:
|
||||
self.member_validator(inst, attr, member)
|
||||
|
||||
def __repr__(self):
|
||||
iterable_identifier = (
|
||||
""
|
||||
if self.iterable_validator is None
|
||||
else " {iterable!r}".format(iterable=self.iterable_validator)
|
||||
)
|
||||
return (
|
||||
"<deep_iterable validator for{iterable_identifier}"
|
||||
" iterables of {member!r}>"
|
||||
).format(
|
||||
iterable_identifier=iterable_identifier,
|
||||
member=self.member_validator,
|
||||
)
|
||||
|
||||
|
||||
def deep_iterable(member_validator, iterable_validator=None):
|
||||
"""
|
||||
A validator that performs deep validation of an iterable.
|
||||
|
||||
:param member_validator: Validator to apply to iterable members
|
||||
:param iterable_validator: Validator to apply to iterable itself
|
||||
(optional)
|
||||
|
||||
.. versionadded:: 19.1.0
|
||||
|
||||
:raises TypeError: if any sub-validators fail
|
||||
"""
|
||||
return _DeepIterable(member_validator, iterable_validator)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, hash=True)
|
||||
class _DeepMapping(object):
|
||||
key_validator = attrib(validator=is_callable())
|
||||
value_validator = attrib(validator=is_callable())
|
||||
mapping_validator = attrib(default=None, validator=optional(is_callable()))
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if self.mapping_validator is not None:
|
||||
self.mapping_validator(inst, attr, value)
|
||||
|
||||
for key in value:
|
||||
self.key_validator(inst, attr, key)
|
||||
self.value_validator(inst, attr, value[key])
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<deep_mapping validator for objects mapping {key!r} to {value!r}>"
|
||||
).format(key=self.key_validator, value=self.value_validator)
|
||||
|
||||
|
||||
def deep_mapping(key_validator, value_validator, mapping_validator=None):
|
||||
"""
|
||||
A validator that performs deep validation of a dictionary.
|
||||
|
||||
:param key_validator: Validator to apply to dictionary keys
|
||||
:param value_validator: Validator to apply to dictionary values
|
||||
:param mapping_validator: Validator to apply to top-level mapping
|
||||
attribute (optional)
|
||||
|
||||
.. versionadded:: 19.1.0
|
||||
|
||||
:raises TypeError: if any sub-validators fail
|
||||
"""
|
||||
return _DeepMapping(key_validator, value_validator, mapping_validator)
|
||||
|
||||
Vendored
+10
@@ -12,3 +12,13 @@ def optional(
|
||||
) -> _ValidatorType[Optional[_T]]: ...
|
||||
def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
|
||||
def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
|
||||
def deep_iterable(
|
||||
member_validator: _ValidatorType[_T],
|
||||
iterable_validator: Optional[_ValidatorType[_T]],
|
||||
) -> _ValidatorType[_T]: ...
|
||||
def deep_mapping(
|
||||
key_validator: _ValidatorType[_T],
|
||||
value_validator: _ValidatorType[_T],
|
||||
mapping_validator: Optional[_ValidatorType[_T]],
|
||||
) -> _ValidatorType[_T]: ...
|
||||
def is_callable() -> _ValidatorType[_T]: ...
|
||||
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
This work is licensed under the Creative Commons
|
||||
Attribution-ShareAlike 2.5 International License. To view a copy of
|
||||
this license, visit http://creativecommons.org/licenses/by-sa/2.5/ or
|
||||
send a letter to Creative Commons, PO Box 1866, Mountain View,
|
||||
CA 94042, USA.
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
from .cursor import hide, show, HiddenCursor
|
||||
|
||||
__all__ = ["hide", "show", "HiddenCursor"]
|
||||
|
||||
Vendored
-57
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
## Author: James Spencer: http://stackoverflow.com/users/1375885/james-spencer
|
||||
## Packager: Gijs TImmers: https://github.com/GijsTimmers
|
||||
|
||||
## Based on James Spencer's answer on StackOverflow:
|
||||
## http://stackoverflow.com/questions/5174810/how-to-turn-off-blinking-cursor-in-command-window
|
||||
|
||||
## Licence: CC-BY-SA-2.5
|
||||
## http://creativecommons.org/licenses/by-sa/2.5/
|
||||
|
||||
## This work is licensed under the Creative Commons
|
||||
## Attribution-ShareAlike 2.5 International License. To view a copy of
|
||||
## this license, visit http://creativecommons.org/licenses/by-sa/2.5/ or
|
||||
## send a letter to Creative Commons, PO Box 1866, Mountain View,
|
||||
## CA 94042, USA.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
if os.name == 'nt':
|
||||
import ctypes
|
||||
|
||||
class _CursorInfo(ctypes.Structure):
|
||||
_fields_ = [("size", ctypes.c_int),
|
||||
("visible", ctypes.c_byte)]
|
||||
|
||||
def hide(stream=sys.stdout):
|
||||
if os.name == 'nt':
|
||||
ci = _CursorInfo()
|
||||
handle = ctypes.windll.kernel32.GetStdHandle(-11)
|
||||
ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
|
||||
ci.visible = False
|
||||
ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
|
||||
elif os.name == 'posix':
|
||||
stream.write("\033[?25l")
|
||||
stream.flush()
|
||||
|
||||
def show(stream=sys.stdout):
|
||||
if os.name == 'nt':
|
||||
ci = _CursorInfo()
|
||||
handle = ctypes.windll.kernel32.GetStdHandle(-11)
|
||||
ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
|
||||
ci.visible = True
|
||||
ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
|
||||
elif os.name == 'posix':
|
||||
stream.write("\033[?25h")
|
||||
stream.flush()
|
||||
|
||||
class HiddenCursor(object):
|
||||
def __init__(self, stream=sys.stdout):
|
||||
self._stream = stream
|
||||
def __enter__(self):
|
||||
hide(stream=self._stream)
|
||||
def __exit__(self, type, value, traceback):
|
||||
show(stream=self._stream)
|
||||
Vendored
-33
@@ -1,33 +0,0 @@
|
||||
Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS
|
||||
for more details.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the software as well
|
||||
as documentation, with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
||||
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
Copyright 2010 Pallets
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Vendored
+140
-118
@@ -1,75 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
markupsafe
|
||||
~~~~~~~~~~
|
||||
markupsafe
|
||||
~~~~~~~~~~
|
||||
|
||||
Implements a Markup string.
|
||||
Implements an escape function and a Markup string to replace HTML
|
||||
special characters with safe representations.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import re
|
||||
import string
|
||||
from collections import Mapping
|
||||
from markupsafe._compat import text_type, string_types, int_types, \
|
||||
unichr, iteritems, PY2
|
||||
|
||||
__version__ = "1.0"
|
||||
from ._compat import int_types
|
||||
from ._compat import iteritems
|
||||
from ._compat import Mapping
|
||||
from ._compat import PY2
|
||||
from ._compat import string_types
|
||||
from ._compat import text_type
|
||||
from ._compat import unichr
|
||||
|
||||
__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
|
||||
__version__ = "1.1.1"
|
||||
|
||||
__all__ = ["Markup", "soft_unicode", "escape", "escape_silent"]
|
||||
|
||||
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
||||
_entity_re = re.compile(r'&([^& ;]+);')
|
||||
_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
|
||||
_entity_re = re.compile(r"&([^& ;]+);")
|
||||
|
||||
|
||||
class Markup(text_type):
|
||||
r"""Marks a string as being safe for inclusion in HTML/XML output without
|
||||
needing to be escaped. This implements the `__html__` interface a couple
|
||||
of frameworks and web applications use. :class:`Markup` is a direct
|
||||
subclass of `unicode` and provides all the methods of `unicode` just that
|
||||
it escapes arguments passed and always returns `Markup`.
|
||||
"""A string that is ready to be safely inserted into an HTML or XML
|
||||
document, either because it was escaped or because it was marked
|
||||
safe.
|
||||
|
||||
The `escape` function returns markup objects so that double escaping can't
|
||||
happen.
|
||||
Passing an object to the constructor converts it to text and wraps
|
||||
it to mark it safe without escaping. To escape the text, use the
|
||||
:meth:`escape` class method instead.
|
||||
|
||||
The constructor of the :class:`Markup` class can be used for three
|
||||
different things: When passed an unicode object it's assumed to be safe,
|
||||
when passed an object with an HTML representation (has an `__html__`
|
||||
method) that representation is used, otherwise the object passed is
|
||||
converted into a unicode string and then assumed to be safe:
|
||||
>>> Markup('Hello, <em>World</em>!')
|
||||
Markup('Hello, <em>World</em>!')
|
||||
>>> Markup(42)
|
||||
Markup('42')
|
||||
>>> Markup.escape('Hello, <em>World</em>!')
|
||||
Markup('Hello <em>World</em>!')
|
||||
|
||||
>>> Markup("Hello <em>World</em>!")
|
||||
Markup(u'Hello <em>World</em>!')
|
||||
>>> class Foo(object):
|
||||
... def __html__(self):
|
||||
... return '<a href="#">foo</a>'
|
||||
This implements the ``__html__()`` interface that some frameworks
|
||||
use. Passing an object that implements ``__html__()`` will wrap the
|
||||
output of that method, marking it safe.
|
||||
|
||||
>>> class Foo:
|
||||
... def __html__(self):
|
||||
... return '<a href="/foo">foo</a>'
|
||||
...
|
||||
>>> Markup(Foo())
|
||||
Markup(u'<a href="#">foo</a>')
|
||||
Markup('<a href="/foo">foo</a>')
|
||||
|
||||
If you want object passed being always treated as unsafe you can use the
|
||||
:meth:`escape` classmethod to create a :class:`Markup` object:
|
||||
This is a subclass of the text type (``str`` in Python 3,
|
||||
``unicode`` in Python 2). It has the same methods as that type, but
|
||||
all methods escape their arguments and return a ``Markup`` instance.
|
||||
|
||||
>>> Markup.escape("Hello <em>World</em>!")
|
||||
Markup(u'Hello <em>World</em>!')
|
||||
|
||||
Operations on a markup string are markup aware which means that all
|
||||
arguments are passed through the :func:`escape` function:
|
||||
|
||||
>>> em = Markup("<em>%s</em>")
|
||||
>>> em % "foo & bar"
|
||||
Markup(u'<em>foo & bar</em>')
|
||||
>>> strong = Markup("<strong>%(text)s</strong>")
|
||||
>>> strong % {'text': '<blink>hacker here</blink>'}
|
||||
Markup(u'<strong><blink>hacker here</blink></strong>')
|
||||
>>> Markup("<em>Hello</em> ") + "<foo>"
|
||||
Markup(u'<em>Hello</em> <foo>')
|
||||
>>> Markup('<em>%s</em>') % 'foo & bar'
|
||||
Markup('<em>foo & bar</em>')
|
||||
>>> Markup('<em>Hello</em> ') + '<foo>'
|
||||
Markup('<em>Hello</em> <foo>')
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, base=u'', encoding=None, errors='strict'):
|
||||
if hasattr(base, '__html__'):
|
||||
def __new__(cls, base=u"", encoding=None, errors="strict"):
|
||||
if hasattr(base, "__html__"):
|
||||
base = base.__html__()
|
||||
if encoding is None:
|
||||
return text_type.__new__(cls, base)
|
||||
@@ -79,12 +78,12 @@ class Markup(text_type):
|
||||
return self
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, string_types) or hasattr(other, '__html__'):
|
||||
if isinstance(other, string_types) or hasattr(other, "__html__"):
|
||||
return self.__class__(super(Markup, self).__add__(self.escape(other)))
|
||||
return NotImplemented
|
||||
|
||||
def __radd__(self, other):
|
||||
if hasattr(other, '__html__') or isinstance(other, string_types):
|
||||
if hasattr(other, "__html__") or isinstance(other, string_types):
|
||||
return self.escape(other).__add__(self)
|
||||
return NotImplemented
|
||||
|
||||
@@ -92,6 +91,7 @@ class Markup(text_type):
|
||||
if isinstance(num, int_types):
|
||||
return self.__class__(text_type.__mul__(self, num))
|
||||
return NotImplemented
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __mod__(self, arg):
|
||||
@@ -102,115 +102,124 @@ class Markup(text_type):
|
||||
return self.__class__(text_type.__mod__(self, arg))
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (
|
||||
self.__class__.__name__,
|
||||
text_type.__repr__(self)
|
||||
)
|
||||
return "%s(%s)" % (self.__class__.__name__, text_type.__repr__(self))
|
||||
|
||||
def join(self, seq):
|
||||
return self.__class__(text_type.join(self, map(self.escape, seq)))
|
||||
|
||||
join.__doc__ = text_type.join.__doc__
|
||||
|
||||
def split(self, *args, **kwargs):
|
||||
return list(map(self.__class__, text_type.split(self, *args, **kwargs)))
|
||||
|
||||
split.__doc__ = text_type.split.__doc__
|
||||
|
||||
def rsplit(self, *args, **kwargs):
|
||||
return list(map(self.__class__, text_type.rsplit(self, *args, **kwargs)))
|
||||
|
||||
rsplit.__doc__ = text_type.rsplit.__doc__
|
||||
|
||||
def splitlines(self, *args, **kwargs):
|
||||
return list(map(self.__class__, text_type.splitlines(
|
||||
self, *args, **kwargs)))
|
||||
return list(map(self.__class__, text_type.splitlines(self, *args, **kwargs)))
|
||||
|
||||
splitlines.__doc__ = text_type.splitlines.__doc__
|
||||
|
||||
def unescape(self):
|
||||
r"""Unescape markup again into an text_type string. This also resolves
|
||||
known HTML4 and XHTML entities:
|
||||
"""Convert escaped markup back into a text string. This replaces
|
||||
HTML entities with the characters they represent.
|
||||
|
||||
>>> Markup("Main » <em>About</em>").unescape()
|
||||
u'Main \xbb <em>About</em>'
|
||||
>>> Markup('Main » <em>About</em>').unescape()
|
||||
'Main » <em>About</em>'
|
||||
"""
|
||||
from markupsafe._constants import HTML_ENTITIES
|
||||
from ._constants import HTML_ENTITIES
|
||||
|
||||
def handle_match(m):
|
||||
name = m.group(1)
|
||||
if name in HTML_ENTITIES:
|
||||
return unichr(HTML_ENTITIES[name])
|
||||
try:
|
||||
if name[:2] in ('#x', '#X'):
|
||||
if name[:2] in ("#x", "#X"):
|
||||
return unichr(int(name[2:], 16))
|
||||
elif name.startswith('#'):
|
||||
elif name.startswith("#"):
|
||||
return unichr(int(name[1:]))
|
||||
except ValueError:
|
||||
pass
|
||||
# Don't modify unexpected input.
|
||||
return m.group()
|
||||
|
||||
return _entity_re.sub(handle_match, text_type(self))
|
||||
|
||||
def striptags(self):
|
||||
r"""Unescape markup into an text_type string and strip all tags. This
|
||||
also resolves known HTML4 and XHTML entities. Whitespace is
|
||||
normalized to one:
|
||||
""":meth:`unescape` the markup, remove tags, and normalize
|
||||
whitespace to single spaces.
|
||||
|
||||
>>> Markup("Main » <em>About</em>").striptags()
|
||||
u'Main \xbb About'
|
||||
>>> Markup('Main »\t<em>About</em>').striptags()
|
||||
'Main » About'
|
||||
"""
|
||||
stripped = u' '.join(_striptags_re.sub('', self).split())
|
||||
stripped = u" ".join(_striptags_re.sub("", self).split())
|
||||
return Markup(stripped).unescape()
|
||||
|
||||
@classmethod
|
||||
def escape(cls, s):
|
||||
"""Escape the string. Works like :func:`escape` with the difference
|
||||
that for subclasses of :class:`Markup` this function would return the
|
||||
correct subclass.
|
||||
"""Escape a string. Calls :func:`escape` and ensures that for
|
||||
subclasses the correct type is returned.
|
||||
"""
|
||||
rv = escape(s)
|
||||
if rv.__class__ is not cls:
|
||||
return cls(rv)
|
||||
return rv
|
||||
|
||||
def make_simple_escaping_wrapper(name):
|
||||
def make_simple_escaping_wrapper(name): # noqa: B902
|
||||
orig = getattr(text_type, name)
|
||||
|
||||
def func(self, *args, **kwargs):
|
||||
args = _escape_argspec(list(args), enumerate(args), self.escape)
|
||||
_escape_argspec(kwargs, iteritems(kwargs), self.escape)
|
||||
return self.__class__(orig(self, *args, **kwargs))
|
||||
|
||||
func.__name__ = orig.__name__
|
||||
func.__doc__ = orig.__doc__
|
||||
return func
|
||||
|
||||
for method in '__getitem__', 'capitalize', \
|
||||
'title', 'lower', 'upper', 'replace', 'ljust', \
|
||||
'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
|
||||
'translate', 'expandtabs', 'swapcase', 'zfill':
|
||||
for method in (
|
||||
"__getitem__",
|
||||
"capitalize",
|
||||
"title",
|
||||
"lower",
|
||||
"upper",
|
||||
"replace",
|
||||
"ljust",
|
||||
"rjust",
|
||||
"lstrip",
|
||||
"rstrip",
|
||||
"center",
|
||||
"strip",
|
||||
"translate",
|
||||
"expandtabs",
|
||||
"swapcase",
|
||||
"zfill",
|
||||
):
|
||||
locals()[method] = make_simple_escaping_wrapper(method)
|
||||
|
||||
# new in python 2.5
|
||||
if hasattr(text_type, 'partition'):
|
||||
def partition(self, sep):
|
||||
return tuple(map(self.__class__,
|
||||
text_type.partition(self, self.escape(sep))))
|
||||
def rpartition(self, sep):
|
||||
return tuple(map(self.__class__,
|
||||
text_type.rpartition(self, self.escape(sep))))
|
||||
def partition(self, sep):
|
||||
return tuple(map(self.__class__, text_type.partition(self, self.escape(sep))))
|
||||
|
||||
# new in python 2.6
|
||||
if hasattr(text_type, 'format'):
|
||||
def format(*args, **kwargs):
|
||||
self, args = args[0], args[1:]
|
||||
formatter = EscapeFormatter(self.escape)
|
||||
kwargs = _MagicFormatMapping(args, kwargs)
|
||||
return self.__class__(formatter.vformat(self, args, kwargs))
|
||||
def rpartition(self, sep):
|
||||
return tuple(map(self.__class__, text_type.rpartition(self, self.escape(sep))))
|
||||
|
||||
def __html_format__(self, format_spec):
|
||||
if format_spec:
|
||||
raise ValueError('Unsupported format specification '
|
||||
'for Markup.')
|
||||
return self
|
||||
def format(self, *args, **kwargs):
|
||||
formatter = EscapeFormatter(self.escape)
|
||||
kwargs = _MagicFormatMapping(args, kwargs)
|
||||
return self.__class__(formatter.vformat(self, args, kwargs))
|
||||
|
||||
def __html_format__(self, format_spec):
|
||||
if format_spec:
|
||||
raise ValueError("Unsupported format specification " "for Markup.")
|
||||
return self
|
||||
|
||||
# not in python 3
|
||||
if hasattr(text_type, '__getslice__'):
|
||||
__getslice__ = make_simple_escaping_wrapper('__getslice__')
|
||||
if hasattr(text_type, "__getslice__"):
|
||||
__getslice__ = make_simple_escaping_wrapper("__getslice__")
|
||||
|
||||
del method, make_simple_escaping_wrapper
|
||||
|
||||
@@ -229,7 +238,7 @@ class _MagicFormatMapping(Mapping):
|
||||
self._last_index = 0
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == '':
|
||||
if key == "":
|
||||
idx = self._last_index
|
||||
self._last_index += 1
|
||||
try:
|
||||
@@ -246,35 +255,37 @@ class _MagicFormatMapping(Mapping):
|
||||
return len(self._kwargs)
|
||||
|
||||
|
||||
if hasattr(text_type, 'format'):
|
||||
class EscapeFormatter(string.Formatter):
|
||||
if hasattr(text_type, "format"):
|
||||
|
||||
class EscapeFormatter(string.Formatter):
|
||||
def __init__(self, escape):
|
||||
self.escape = escape
|
||||
|
||||
def format_field(self, value, format_spec):
|
||||
if hasattr(value, '__html_format__'):
|
||||
if hasattr(value, "__html_format__"):
|
||||
rv = value.__html_format__(format_spec)
|
||||
elif hasattr(value, '__html__'):
|
||||
elif hasattr(value, "__html__"):
|
||||
if format_spec:
|
||||
raise ValueError('No format specification allowed '
|
||||
'when formatting an object with '
|
||||
'its __html__ method.')
|
||||
raise ValueError(
|
||||
"Format specifier {0} given, but {1} does not"
|
||||
" define __html_format__. A class that defines"
|
||||
" __html__ must define __html_format__ to work"
|
||||
" with format specifiers.".format(format_spec, type(value))
|
||||
)
|
||||
rv = value.__html__()
|
||||
else:
|
||||
# We need to make sure the format spec is unicode here as
|
||||
# otherwise the wrong callback methods are invoked. For
|
||||
# instance a byte string there would invoke __str__ and
|
||||
# not __unicode__.
|
||||
rv = string.Formatter.format_field(
|
||||
self, value, text_type(format_spec))
|
||||
rv = string.Formatter.format_field(self, value, text_type(format_spec))
|
||||
return text_type(self.escape(rv))
|
||||
|
||||
|
||||
def _escape_argspec(obj, iterable, escape):
|
||||
"""Helper for various string-wrapped functions."""
|
||||
for key, value in iterable:
|
||||
if hasattr(value, '__html__') or isinstance(value, string_types):
|
||||
if hasattr(value, "__html__") or isinstance(value, string_types):
|
||||
obj[key] = escape(value)
|
||||
return obj
|
||||
|
||||
@@ -286,20 +297,31 @@ class _MarkupEscapeHelper(object):
|
||||
self.obj = obj
|
||||
self.escape = escape
|
||||
|
||||
__getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x], s.escape)
|
||||
__unicode__ = __str__ = lambda s: text_type(s.escape(s.obj))
|
||||
__repr__ = lambda s: str(s.escape(repr(s.obj)))
|
||||
__int__ = lambda s: int(s.obj)
|
||||
__float__ = lambda s: float(s.obj)
|
||||
def __getitem__(self, item):
|
||||
return _MarkupEscapeHelper(self.obj[item], self.escape)
|
||||
|
||||
def __str__(self):
|
||||
return text_type(self.escape(self.obj))
|
||||
|
||||
__unicode__ = __str__
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.escape(repr(self.obj)))
|
||||
|
||||
def __int__(self):
|
||||
return int(self.obj)
|
||||
|
||||
def __float__(self):
|
||||
return float(self.obj)
|
||||
|
||||
|
||||
# we have to import it down here as the speedups and native
|
||||
# modules imports the markup type which is define above.
|
||||
try:
|
||||
from markupsafe._speedups import escape, escape_silent, soft_unicode
|
||||
from ._speedups import escape, escape_silent, soft_unicode
|
||||
except ImportError:
|
||||
from markupsafe._native import escape, escape_silent, soft_unicode
|
||||
from ._native import escape, escape_silent, soft_unicode
|
||||
|
||||
if not PY2:
|
||||
soft_str = soft_unicode
|
||||
__all__.append('soft_str')
|
||||
__all__.append("soft_str")
|
||||
|
||||
Vendored
+15
-8
@@ -1,12 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
markupsafe._compat
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
markupsafe._compat
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Compatibility module for different Python versions.
|
||||
|
||||
:copyright: (c) 2013 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import sys
|
||||
|
||||
@@ -17,10 +15,19 @@ if not PY2:
|
||||
string_types = (str,)
|
||||
unichr = chr
|
||||
int_types = (int,)
|
||||
iteritems = lambda x: iter(x.items())
|
||||
|
||||
def iteritems(x):
|
||||
return iter(x.items())
|
||||
|
||||
from collections.abc import Mapping
|
||||
|
||||
else:
|
||||
text_type = unicode
|
||||
string_types = (str, unicode)
|
||||
unichr = unichr
|
||||
int_types = (int, long)
|
||||
iteritems = lambda x: x.iteritems()
|
||||
|
||||
def iteritems(x):
|
||||
return x.iteritems()
|
||||
|
||||
from collections import Mapping
|
||||
|
||||
+257
-260
@@ -1,267 +1,264 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
markupsafe._constants
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
markupsafe._constants
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Highlevel implementation of the Markup string.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
|
||||
|
||||
HTML_ENTITIES = {
|
||||
'AElig': 198,
|
||||
'Aacute': 193,
|
||||
'Acirc': 194,
|
||||
'Agrave': 192,
|
||||
'Alpha': 913,
|
||||
'Aring': 197,
|
||||
'Atilde': 195,
|
||||
'Auml': 196,
|
||||
'Beta': 914,
|
||||
'Ccedil': 199,
|
||||
'Chi': 935,
|
||||
'Dagger': 8225,
|
||||
'Delta': 916,
|
||||
'ETH': 208,
|
||||
'Eacute': 201,
|
||||
'Ecirc': 202,
|
||||
'Egrave': 200,
|
||||
'Epsilon': 917,
|
||||
'Eta': 919,
|
||||
'Euml': 203,
|
||||
'Gamma': 915,
|
||||
'Iacute': 205,
|
||||
'Icirc': 206,
|
||||
'Igrave': 204,
|
||||
'Iota': 921,
|
||||
'Iuml': 207,
|
||||
'Kappa': 922,
|
||||
'Lambda': 923,
|
||||
'Mu': 924,
|
||||
'Ntilde': 209,
|
||||
'Nu': 925,
|
||||
'OElig': 338,
|
||||
'Oacute': 211,
|
||||
'Ocirc': 212,
|
||||
'Ograve': 210,
|
||||
'Omega': 937,
|
||||
'Omicron': 927,
|
||||
'Oslash': 216,
|
||||
'Otilde': 213,
|
||||
'Ouml': 214,
|
||||
'Phi': 934,
|
||||
'Pi': 928,
|
||||
'Prime': 8243,
|
||||
'Psi': 936,
|
||||
'Rho': 929,
|
||||
'Scaron': 352,
|
||||
'Sigma': 931,
|
||||
'THORN': 222,
|
||||
'Tau': 932,
|
||||
'Theta': 920,
|
||||
'Uacute': 218,
|
||||
'Ucirc': 219,
|
||||
'Ugrave': 217,
|
||||
'Upsilon': 933,
|
||||
'Uuml': 220,
|
||||
'Xi': 926,
|
||||
'Yacute': 221,
|
||||
'Yuml': 376,
|
||||
'Zeta': 918,
|
||||
'aacute': 225,
|
||||
'acirc': 226,
|
||||
'acute': 180,
|
||||
'aelig': 230,
|
||||
'agrave': 224,
|
||||
'alefsym': 8501,
|
||||
'alpha': 945,
|
||||
'amp': 38,
|
||||
'and': 8743,
|
||||
'ang': 8736,
|
||||
'apos': 39,
|
||||
'aring': 229,
|
||||
'asymp': 8776,
|
||||
'atilde': 227,
|
||||
'auml': 228,
|
||||
'bdquo': 8222,
|
||||
'beta': 946,
|
||||
'brvbar': 166,
|
||||
'bull': 8226,
|
||||
'cap': 8745,
|
||||
'ccedil': 231,
|
||||
'cedil': 184,
|
||||
'cent': 162,
|
||||
'chi': 967,
|
||||
'circ': 710,
|
||||
'clubs': 9827,
|
||||
'cong': 8773,
|
||||
'copy': 169,
|
||||
'crarr': 8629,
|
||||
'cup': 8746,
|
||||
'curren': 164,
|
||||
'dArr': 8659,
|
||||
'dagger': 8224,
|
||||
'darr': 8595,
|
||||
'deg': 176,
|
||||
'delta': 948,
|
||||
'diams': 9830,
|
||||
'divide': 247,
|
||||
'eacute': 233,
|
||||
'ecirc': 234,
|
||||
'egrave': 232,
|
||||
'empty': 8709,
|
||||
'emsp': 8195,
|
||||
'ensp': 8194,
|
||||
'epsilon': 949,
|
||||
'equiv': 8801,
|
||||
'eta': 951,
|
||||
'eth': 240,
|
||||
'euml': 235,
|
||||
'euro': 8364,
|
||||
'exist': 8707,
|
||||
'fnof': 402,
|
||||
'forall': 8704,
|
||||
'frac12': 189,
|
||||
'frac14': 188,
|
||||
'frac34': 190,
|
||||
'frasl': 8260,
|
||||
'gamma': 947,
|
||||
'ge': 8805,
|
||||
'gt': 62,
|
||||
'hArr': 8660,
|
||||
'harr': 8596,
|
||||
'hearts': 9829,
|
||||
'hellip': 8230,
|
||||
'iacute': 237,
|
||||
'icirc': 238,
|
||||
'iexcl': 161,
|
||||
'igrave': 236,
|
||||
'image': 8465,
|
||||
'infin': 8734,
|
||||
'int': 8747,
|
||||
'iota': 953,
|
||||
'iquest': 191,
|
||||
'isin': 8712,
|
||||
'iuml': 239,
|
||||
'kappa': 954,
|
||||
'lArr': 8656,
|
||||
'lambda': 955,
|
||||
'lang': 9001,
|
||||
'laquo': 171,
|
||||
'larr': 8592,
|
||||
'lceil': 8968,
|
||||
'ldquo': 8220,
|
||||
'le': 8804,
|
||||
'lfloor': 8970,
|
||||
'lowast': 8727,
|
||||
'loz': 9674,
|
||||
'lrm': 8206,
|
||||
'lsaquo': 8249,
|
||||
'lsquo': 8216,
|
||||
'lt': 60,
|
||||
'macr': 175,
|
||||
'mdash': 8212,
|
||||
'micro': 181,
|
||||
'middot': 183,
|
||||
'minus': 8722,
|
||||
'mu': 956,
|
||||
'nabla': 8711,
|
||||
'nbsp': 160,
|
||||
'ndash': 8211,
|
||||
'ne': 8800,
|
||||
'ni': 8715,
|
||||
'not': 172,
|
||||
'notin': 8713,
|
||||
'nsub': 8836,
|
||||
'ntilde': 241,
|
||||
'nu': 957,
|
||||
'oacute': 243,
|
||||
'ocirc': 244,
|
||||
'oelig': 339,
|
||||
'ograve': 242,
|
||||
'oline': 8254,
|
||||
'omega': 969,
|
||||
'omicron': 959,
|
||||
'oplus': 8853,
|
||||
'or': 8744,
|
||||
'ordf': 170,
|
||||
'ordm': 186,
|
||||
'oslash': 248,
|
||||
'otilde': 245,
|
||||
'otimes': 8855,
|
||||
'ouml': 246,
|
||||
'para': 182,
|
||||
'part': 8706,
|
||||
'permil': 8240,
|
||||
'perp': 8869,
|
||||
'phi': 966,
|
||||
'pi': 960,
|
||||
'piv': 982,
|
||||
'plusmn': 177,
|
||||
'pound': 163,
|
||||
'prime': 8242,
|
||||
'prod': 8719,
|
||||
'prop': 8733,
|
||||
'psi': 968,
|
||||
'quot': 34,
|
||||
'rArr': 8658,
|
||||
'radic': 8730,
|
||||
'rang': 9002,
|
||||
'raquo': 187,
|
||||
'rarr': 8594,
|
||||
'rceil': 8969,
|
||||
'rdquo': 8221,
|
||||
'real': 8476,
|
||||
'reg': 174,
|
||||
'rfloor': 8971,
|
||||
'rho': 961,
|
||||
'rlm': 8207,
|
||||
'rsaquo': 8250,
|
||||
'rsquo': 8217,
|
||||
'sbquo': 8218,
|
||||
'scaron': 353,
|
||||
'sdot': 8901,
|
||||
'sect': 167,
|
||||
'shy': 173,
|
||||
'sigma': 963,
|
||||
'sigmaf': 962,
|
||||
'sim': 8764,
|
||||
'spades': 9824,
|
||||
'sub': 8834,
|
||||
'sube': 8838,
|
||||
'sum': 8721,
|
||||
'sup': 8835,
|
||||
'sup1': 185,
|
||||
'sup2': 178,
|
||||
'sup3': 179,
|
||||
'supe': 8839,
|
||||
'szlig': 223,
|
||||
'tau': 964,
|
||||
'there4': 8756,
|
||||
'theta': 952,
|
||||
'thetasym': 977,
|
||||
'thinsp': 8201,
|
||||
'thorn': 254,
|
||||
'tilde': 732,
|
||||
'times': 215,
|
||||
'trade': 8482,
|
||||
'uArr': 8657,
|
||||
'uacute': 250,
|
||||
'uarr': 8593,
|
||||
'ucirc': 251,
|
||||
'ugrave': 249,
|
||||
'uml': 168,
|
||||
'upsih': 978,
|
||||
'upsilon': 965,
|
||||
'uuml': 252,
|
||||
'weierp': 8472,
|
||||
'xi': 958,
|
||||
'yacute': 253,
|
||||
'yen': 165,
|
||||
'yuml': 255,
|
||||
'zeta': 950,
|
||||
'zwj': 8205,
|
||||
'zwnj': 8204
|
||||
"AElig": 198,
|
||||
"Aacute": 193,
|
||||
"Acirc": 194,
|
||||
"Agrave": 192,
|
||||
"Alpha": 913,
|
||||
"Aring": 197,
|
||||
"Atilde": 195,
|
||||
"Auml": 196,
|
||||
"Beta": 914,
|
||||
"Ccedil": 199,
|
||||
"Chi": 935,
|
||||
"Dagger": 8225,
|
||||
"Delta": 916,
|
||||
"ETH": 208,
|
||||
"Eacute": 201,
|
||||
"Ecirc": 202,
|
||||
"Egrave": 200,
|
||||
"Epsilon": 917,
|
||||
"Eta": 919,
|
||||
"Euml": 203,
|
||||
"Gamma": 915,
|
||||
"Iacute": 205,
|
||||
"Icirc": 206,
|
||||
"Igrave": 204,
|
||||
"Iota": 921,
|
||||
"Iuml": 207,
|
||||
"Kappa": 922,
|
||||
"Lambda": 923,
|
||||
"Mu": 924,
|
||||
"Ntilde": 209,
|
||||
"Nu": 925,
|
||||
"OElig": 338,
|
||||
"Oacute": 211,
|
||||
"Ocirc": 212,
|
||||
"Ograve": 210,
|
||||
"Omega": 937,
|
||||
"Omicron": 927,
|
||||
"Oslash": 216,
|
||||
"Otilde": 213,
|
||||
"Ouml": 214,
|
||||
"Phi": 934,
|
||||
"Pi": 928,
|
||||
"Prime": 8243,
|
||||
"Psi": 936,
|
||||
"Rho": 929,
|
||||
"Scaron": 352,
|
||||
"Sigma": 931,
|
||||
"THORN": 222,
|
||||
"Tau": 932,
|
||||
"Theta": 920,
|
||||
"Uacute": 218,
|
||||
"Ucirc": 219,
|
||||
"Ugrave": 217,
|
||||
"Upsilon": 933,
|
||||
"Uuml": 220,
|
||||
"Xi": 926,
|
||||
"Yacute": 221,
|
||||
"Yuml": 376,
|
||||
"Zeta": 918,
|
||||
"aacute": 225,
|
||||
"acirc": 226,
|
||||
"acute": 180,
|
||||
"aelig": 230,
|
||||
"agrave": 224,
|
||||
"alefsym": 8501,
|
||||
"alpha": 945,
|
||||
"amp": 38,
|
||||
"and": 8743,
|
||||
"ang": 8736,
|
||||
"apos": 39,
|
||||
"aring": 229,
|
||||
"asymp": 8776,
|
||||
"atilde": 227,
|
||||
"auml": 228,
|
||||
"bdquo": 8222,
|
||||
"beta": 946,
|
||||
"brvbar": 166,
|
||||
"bull": 8226,
|
||||
"cap": 8745,
|
||||
"ccedil": 231,
|
||||
"cedil": 184,
|
||||
"cent": 162,
|
||||
"chi": 967,
|
||||
"circ": 710,
|
||||
"clubs": 9827,
|
||||
"cong": 8773,
|
||||
"copy": 169,
|
||||
"crarr": 8629,
|
||||
"cup": 8746,
|
||||
"curren": 164,
|
||||
"dArr": 8659,
|
||||
"dagger": 8224,
|
||||
"darr": 8595,
|
||||
"deg": 176,
|
||||
"delta": 948,
|
||||
"diams": 9830,
|
||||
"divide": 247,
|
||||
"eacute": 233,
|
||||
"ecirc": 234,
|
||||
"egrave": 232,
|
||||
"empty": 8709,
|
||||
"emsp": 8195,
|
||||
"ensp": 8194,
|
||||
"epsilon": 949,
|
||||
"equiv": 8801,
|
||||
"eta": 951,
|
||||
"eth": 240,
|
||||
"euml": 235,
|
||||
"euro": 8364,
|
||||
"exist": 8707,
|
||||
"fnof": 402,
|
||||
"forall": 8704,
|
||||
"frac12": 189,
|
||||
"frac14": 188,
|
||||
"frac34": 190,
|
||||
"frasl": 8260,
|
||||
"gamma": 947,
|
||||
"ge": 8805,
|
||||
"gt": 62,
|
||||
"hArr": 8660,
|
||||
"harr": 8596,
|
||||
"hearts": 9829,
|
||||
"hellip": 8230,
|
||||
"iacute": 237,
|
||||
"icirc": 238,
|
||||
"iexcl": 161,
|
||||
"igrave": 236,
|
||||
"image": 8465,
|
||||
"infin": 8734,
|
||||
"int": 8747,
|
||||
"iota": 953,
|
||||
"iquest": 191,
|
||||
"isin": 8712,
|
||||
"iuml": 239,
|
||||
"kappa": 954,
|
||||
"lArr": 8656,
|
||||
"lambda": 955,
|
||||
"lang": 9001,
|
||||
"laquo": 171,
|
||||
"larr": 8592,
|
||||
"lceil": 8968,
|
||||
"ldquo": 8220,
|
||||
"le": 8804,
|
||||
"lfloor": 8970,
|
||||
"lowast": 8727,
|
||||
"loz": 9674,
|
||||
"lrm": 8206,
|
||||
"lsaquo": 8249,
|
||||
"lsquo": 8216,
|
||||
"lt": 60,
|
||||
"macr": 175,
|
||||
"mdash": 8212,
|
||||
"micro": 181,
|
||||
"middot": 183,
|
||||
"minus": 8722,
|
||||
"mu": 956,
|
||||
"nabla": 8711,
|
||||
"nbsp": 160,
|
||||
"ndash": 8211,
|
||||
"ne": 8800,
|
||||
"ni": 8715,
|
||||
"not": 172,
|
||||
"notin": 8713,
|
||||
"nsub": 8836,
|
||||
"ntilde": 241,
|
||||
"nu": 957,
|
||||
"oacute": 243,
|
||||
"ocirc": 244,
|
||||
"oelig": 339,
|
||||
"ograve": 242,
|
||||
"oline": 8254,
|
||||
"omega": 969,
|
||||
"omicron": 959,
|
||||
"oplus": 8853,
|
||||
"or": 8744,
|
||||
"ordf": 170,
|
||||
"ordm": 186,
|
||||
"oslash": 248,
|
||||
"otilde": 245,
|
||||
"otimes": 8855,
|
||||
"ouml": 246,
|
||||
"para": 182,
|
||||
"part": 8706,
|
||||
"permil": 8240,
|
||||
"perp": 8869,
|
||||
"phi": 966,
|
||||
"pi": 960,
|
||||
"piv": 982,
|
||||
"plusmn": 177,
|
||||
"pound": 163,
|
||||
"prime": 8242,
|
||||
"prod": 8719,
|
||||
"prop": 8733,
|
||||
"psi": 968,
|
||||
"quot": 34,
|
||||
"rArr": 8658,
|
||||
"radic": 8730,
|
||||
"rang": 9002,
|
||||
"raquo": 187,
|
||||
"rarr": 8594,
|
||||
"rceil": 8969,
|
||||
"rdquo": 8221,
|
||||
"real": 8476,
|
||||
"reg": 174,
|
||||
"rfloor": 8971,
|
||||
"rho": 961,
|
||||
"rlm": 8207,
|
||||
"rsaquo": 8250,
|
||||
"rsquo": 8217,
|
||||
"sbquo": 8218,
|
||||
"scaron": 353,
|
||||
"sdot": 8901,
|
||||
"sect": 167,
|
||||
"shy": 173,
|
||||
"sigma": 963,
|
||||
"sigmaf": 962,
|
||||
"sim": 8764,
|
||||
"spades": 9824,
|
||||
"sub": 8834,
|
||||
"sube": 8838,
|
||||
"sum": 8721,
|
||||
"sup": 8835,
|
||||
"sup1": 185,
|
||||
"sup2": 178,
|
||||
"sup3": 179,
|
||||
"supe": 8839,
|
||||
"szlig": 223,
|
||||
"tau": 964,
|
||||
"there4": 8756,
|
||||
"theta": 952,
|
||||
"thetasym": 977,
|
||||
"thinsp": 8201,
|
||||
"thorn": 254,
|
||||
"tilde": 732,
|
||||
"times": 215,
|
||||
"trade": 8482,
|
||||
"uArr": 8657,
|
||||
"uacute": 250,
|
||||
"uarr": 8593,
|
||||
"ucirc": 251,
|
||||
"ugrave": 249,
|
||||
"uml": 168,
|
||||
"upsih": 978,
|
||||
"upsilon": 965,
|
||||
"uuml": 252,
|
||||
"weierp": 8472,
|
||||
"xi": 958,
|
||||
"yacute": 253,
|
||||
"yen": 165,
|
||||
"yuml": 255,
|
||||
"zeta": 950,
|
||||
"zwj": 8205,
|
||||
"zwnj": 8204,
|
||||
}
|
||||
|
||||
Vendored
+45
-22
@@ -1,36 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
markupsafe._native
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
markupsafe._native
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Native Python implementation the C module is not compiled.
|
||||
Native Python implementation used when the C module is not compiled.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
from markupsafe import Markup
|
||||
from markupsafe._compat import text_type
|
||||
from . import Markup
|
||||
from ._compat import text_type
|
||||
|
||||
|
||||
def escape(s):
|
||||
"""Convert the characters &, <, >, ' and " in string s to HTML-safe
|
||||
sequences. Use this if you need to display text that might contain
|
||||
such characters in HTML. Marks return value as markup string.
|
||||
"""Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in
|
||||
the string with HTML-safe sequences. Use this if you need to display
|
||||
text that might contain such characters in HTML.
|
||||
|
||||
If the object has an ``__html__`` method, it is called and the
|
||||
return value is assumed to already be safe for HTML.
|
||||
|
||||
:param s: An object to be converted to a string and escaped.
|
||||
:return: A :class:`Markup` string with the escaped text.
|
||||
"""
|
||||
if hasattr(s, '__html__'):
|
||||
return s.__html__()
|
||||
return Markup(text_type(s)
|
||||
.replace('&', '&')
|
||||
.replace('>', '>')
|
||||
.replace('<', '<')
|
||||
.replace("'", ''')
|
||||
.replace('"', '"')
|
||||
if hasattr(s, "__html__"):
|
||||
return Markup(s.__html__())
|
||||
return Markup(
|
||||
text_type(s)
|
||||
.replace("&", "&")
|
||||
.replace(">", ">")
|
||||
.replace("<", "<")
|
||||
.replace("'", "'")
|
||||
.replace('"', """)
|
||||
)
|
||||
|
||||
|
||||
def escape_silent(s):
|
||||
"""Like :func:`escape` but converts `None` into an empty
|
||||
markup string.
|
||||
"""Like :func:`escape` but treats ``None`` as the empty string.
|
||||
Useful with optional values, as otherwise you get the string
|
||||
``'None'`` when the value is ``None``.
|
||||
|
||||
>>> escape(None)
|
||||
Markup('None')
|
||||
>>> escape_silent(None)
|
||||
Markup('')
|
||||
"""
|
||||
if s is None:
|
||||
return Markup()
|
||||
@@ -38,8 +51,18 @@ def escape_silent(s):
|
||||
|
||||
|
||||
def soft_unicode(s):
|
||||
"""Make a string unicode if it isn't already. That way a markup
|
||||
string is not converted back to unicode.
|
||||
"""Convert an object to a string if it isn't already. This preserves
|
||||
a :class:`Markup` string rather than converting it back to a basic
|
||||
string, so it will still be marked as safe and won't be escaped
|
||||
again.
|
||||
|
||||
>>> value = escape('<User 1>')
|
||||
>>> value
|
||||
Markup('<User 1>')
|
||||
>>> escape(str(value))
|
||||
Markup('&lt;User 1&gt;')
|
||||
>>> escape(soft_unicode(value))
|
||||
Markup('<User 1>')
|
||||
"""
|
||||
if not isinstance(s, text_type):
|
||||
s = text_type(s)
|
||||
|
||||
Vendored
+199
-15
@@ -2,33 +2,30 @@
|
||||
* markupsafe._speedups
|
||||
* ~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This module implements functions for automatic escaping in C for better
|
||||
* performance.
|
||||
* C implementation of escaping for better performance. Used instead of
|
||||
* the native Python implementation when compiled.
|
||||
*
|
||||
* :copyright: (c) 2010 by Armin Ronacher.
|
||||
* :license: BSD.
|
||||
* :copyright: 2010 Pallets
|
||||
* :license: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
#define ESCAPED_CHARS_TABLE_SIZE 63
|
||||
#define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL)));
|
||||
|
||||
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
|
||||
typedef int Py_ssize_t;
|
||||
#define PY_SSIZE_T_MAX INT_MAX
|
||||
#define PY_SSIZE_T_MIN INT_MIN
|
||||
#endif
|
||||
|
||||
|
||||
static PyObject* markup;
|
||||
static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE];
|
||||
static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE];
|
||||
#endif
|
||||
|
||||
static PyObject* markup;
|
||||
|
||||
static int
|
||||
init_constants(void)
|
||||
{
|
||||
PyObject *module;
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
/* mapping of characters to replace */
|
||||
escaped_chars_repl['"'] = UNICHR(""");
|
||||
escaped_chars_repl['\''] = UNICHR("'");
|
||||
@@ -41,6 +38,7 @@ init_constants(void)
|
||||
escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \
|
||||
escaped_chars_delta_len['&'] = 4;
|
||||
escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3;
|
||||
#endif
|
||||
|
||||
/* import markup type so that we can mark the return value */
|
||||
module = PyImport_ImportModule("markupsafe");
|
||||
@@ -52,6 +50,7 @@ init_constants(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
static PyObject*
|
||||
escape_unicode(PyUnicodeObject *in)
|
||||
{
|
||||
@@ -112,13 +111,192 @@ escape_unicode(PyUnicodeObject *in)
|
||||
|
||||
return (PyObject*)out;
|
||||
}
|
||||
#else /* PY_MAJOR_VERSION < 3 */
|
||||
|
||||
#define GET_DELTA(inp, inp_end, delta) \
|
||||
while (inp < inp_end) { \
|
||||
switch (*inp++) { \
|
||||
case '"': \
|
||||
case '\'': \
|
||||
case '&': \
|
||||
delta += 4; \
|
||||
break; \
|
||||
case '<': \
|
||||
case '>': \
|
||||
delta += 3; \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DO_ESCAPE(inp, inp_end, outp) \
|
||||
{ \
|
||||
Py_ssize_t ncopy = 0; \
|
||||
while (inp < inp_end) { \
|
||||
switch (*inp) { \
|
||||
case '"': \
|
||||
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||
outp += ncopy; ncopy = 0; \
|
||||
*outp++ = '&'; \
|
||||
*outp++ = '#'; \
|
||||
*outp++ = '3'; \
|
||||
*outp++ = '4'; \
|
||||
*outp++ = ';'; \
|
||||
break; \
|
||||
case '\'': \
|
||||
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||
outp += ncopy; ncopy = 0; \
|
||||
*outp++ = '&'; \
|
||||
*outp++ = '#'; \
|
||||
*outp++ = '3'; \
|
||||
*outp++ = '9'; \
|
||||
*outp++ = ';'; \
|
||||
break; \
|
||||
case '&': \
|
||||
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||
outp += ncopy; ncopy = 0; \
|
||||
*outp++ = '&'; \
|
||||
*outp++ = 'a'; \
|
||||
*outp++ = 'm'; \
|
||||
*outp++ = 'p'; \
|
||||
*outp++ = ';'; \
|
||||
break; \
|
||||
case '<': \
|
||||
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||
outp += ncopy; ncopy = 0; \
|
||||
*outp++ = '&'; \
|
||||
*outp++ = 'l'; \
|
||||
*outp++ = 't'; \
|
||||
*outp++ = ';'; \
|
||||
break; \
|
||||
case '>': \
|
||||
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||
outp += ncopy; ncopy = 0; \
|
||||
*outp++ = '&'; \
|
||||
*outp++ = 'g'; \
|
||||
*outp++ = 't'; \
|
||||
*outp++ = ';'; \
|
||||
break; \
|
||||
default: \
|
||||
ncopy++; \
|
||||
} \
|
||||
inp++; \
|
||||
} \
|
||||
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
escape_unicode_kind1(PyUnicodeObject *in)
|
||||
{
|
||||
Py_UCS1 *inp = PyUnicode_1BYTE_DATA(in);
|
||||
Py_UCS1 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||
Py_UCS1 *outp;
|
||||
PyObject *out;
|
||||
Py_ssize_t delta = 0;
|
||||
|
||||
GET_DELTA(inp, inp_end, delta);
|
||||
if (!delta) {
|
||||
Py_INCREF(in);
|
||||
return (PyObject*)in;
|
||||
}
|
||||
|
||||
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta,
|
||||
PyUnicode_IS_ASCII(in) ? 127 : 255);
|
||||
if (!out)
|
||||
return NULL;
|
||||
|
||||
inp = PyUnicode_1BYTE_DATA(in);
|
||||
outp = PyUnicode_1BYTE_DATA(out);
|
||||
DO_ESCAPE(inp, inp_end, outp);
|
||||
return out;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
escape_unicode_kind2(PyUnicodeObject *in)
|
||||
{
|
||||
Py_UCS2 *inp = PyUnicode_2BYTE_DATA(in);
|
||||
Py_UCS2 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||
Py_UCS2 *outp;
|
||||
PyObject *out;
|
||||
Py_ssize_t delta = 0;
|
||||
|
||||
GET_DELTA(inp, inp_end, delta);
|
||||
if (!delta) {
|
||||
Py_INCREF(in);
|
||||
return (PyObject*)in;
|
||||
}
|
||||
|
||||
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 65535);
|
||||
if (!out)
|
||||
return NULL;
|
||||
|
||||
inp = PyUnicode_2BYTE_DATA(in);
|
||||
outp = PyUnicode_2BYTE_DATA(out);
|
||||
DO_ESCAPE(inp, inp_end, outp);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
escape_unicode_kind4(PyUnicodeObject *in)
|
||||
{
|
||||
Py_UCS4 *inp = PyUnicode_4BYTE_DATA(in);
|
||||
Py_UCS4 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||
Py_UCS4 *outp;
|
||||
PyObject *out;
|
||||
Py_ssize_t delta = 0;
|
||||
|
||||
GET_DELTA(inp, inp_end, delta);
|
||||
if (!delta) {
|
||||
Py_INCREF(in);
|
||||
return (PyObject*)in;
|
||||
}
|
||||
|
||||
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 1114111);
|
||||
if (!out)
|
||||
return NULL;
|
||||
|
||||
inp = PyUnicode_4BYTE_DATA(in);
|
||||
outp = PyUnicode_4BYTE_DATA(out);
|
||||
DO_ESCAPE(inp, inp_end, outp);
|
||||
return out;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
escape_unicode(PyUnicodeObject *in)
|
||||
{
|
||||
if (PyUnicode_READY(in))
|
||||
return NULL;
|
||||
|
||||
switch (PyUnicode_KIND(in)) {
|
||||
case PyUnicode_1BYTE_KIND:
|
||||
return escape_unicode_kind1(in);
|
||||
case PyUnicode_2BYTE_KIND:
|
||||
return escape_unicode_kind2(in);
|
||||
case PyUnicode_4BYTE_KIND:
|
||||
return escape_unicode_kind4(in);
|
||||
}
|
||||
assert(0); /* shouldn't happen */
|
||||
return NULL;
|
||||
}
|
||||
#endif /* PY_MAJOR_VERSION < 3 */
|
||||
|
||||
static PyObject*
|
||||
escape(PyObject *self, PyObject *text)
|
||||
{
|
||||
static PyObject *id_html;
|
||||
PyObject *s = NULL, *rv = NULL, *html;
|
||||
|
||||
if (id_html == NULL) {
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
id_html = PyString_InternFromString("__html__");
|
||||
#else
|
||||
id_html = PyUnicode_InternFromString("__html__");
|
||||
#endif
|
||||
if (id_html == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* we don't have to escape integers, bools or floats */
|
||||
if (PyLong_CheckExact(text) ||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
@@ -129,10 +307,16 @@ escape(PyObject *self, PyObject *text)
|
||||
return PyObject_CallFunctionObjArgs(markup, text, NULL);
|
||||
|
||||
/* if the object has an __html__ method that performs the escaping */
|
||||
html = PyObject_GetAttrString(text, "__html__");
|
||||
html = PyObject_GetAttr(text ,id_html);
|
||||
if (html) {
|
||||
rv = PyObject_CallObject(html, NULL);
|
||||
s = PyObject_CallObject(html, NULL);
|
||||
Py_DECREF(html);
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* Convert to Markup object */
|
||||
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
|
||||
Py_DECREF(s);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
Build Amazing Things.
|
||||
|
||||
***
|
||||
|
||||
### Unlicense
|
||||
|
||||
This is free and unencumbered software released into the public\
|
||||
domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute\
|
||||
this software, either in source code form or as a compiled binary, for any\
|
||||
purpose, commercial or non-commercial, and by any means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors of\
|
||||
this software dedicate any and all copyright interest in the software to the\
|
||||
public domain. We make this dedication for the benefit of the public at\
|
||||
large and to the detriment of our heirs and successors. We intend this\
|
||||
dedication to be an overt act of relinquishment in perpetuity of all\
|
||||
present and future rights to this software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF\
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\
|
||||
SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT\
|
||||
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
|
||||
SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# omdict - Ordered Multivalue Dictionary.
|
||||
#
|
||||
# Ansgar Grunseid
|
||||
# grunseid.com
|
||||
# grunseid@gmail.com
|
||||
#
|
||||
# License: Build Amazing Things (Unlicense)
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .orderedmultidict import * # noqa
|
||||
|
||||
__title__ = 'orderedmultidict'
|
||||
__version__ = '1.0'
|
||||
__author__ = 'Ansgar Grunseid'
|
||||
__contact__ = 'grunseid@gmail.com'
|
||||
__license__ = 'Unlicense'
|
||||
__url__ = 'https://github.com/gruns/orderedmultidict'
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# omdict - Ordered Multivalue Dictionary.
|
||||
#
|
||||
# Ansgar Grunseid
|
||||
# grunseid.com
|
||||
# grunseid@gmail.com
|
||||
#
|
||||
# License: Build Amazing Things (Unlicense)
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from six.moves import zip_longest
|
||||
|
||||
_absent = object() # Marker that means no parameter was provided.
|
||||
|
||||
|
||||
class itemnode(object):
|
||||
|
||||
"""
|
||||
Dictionary key:value items wrapped in a node to be members of itemlist, the
|
||||
doubly linked list defined below.
|
||||
"""
|
||||
|
||||
def __init__(self, prev=None, next=None, key=_absent, value=_absent):
|
||||
self.prev = prev
|
||||
self.next = next
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
|
||||
class itemlist(object):
|
||||
|
||||
"""
|
||||
Doubly linked list of itemnodes.
|
||||
|
||||
This class is used as the key:value item storage of orderedmultidict.
|
||||
Methods below were only added as needed for use with orderedmultidict, so
|
||||
some otherwise common list methods may be missing.
|
||||
"""
|
||||
|
||||
def __init__(self, items=[]):
|
||||
self.root = itemnode()
|
||||
self.root.next = self.root.prev = self.root
|
||||
self.size = 0
|
||||
|
||||
for key, value in items:
|
||||
self.append(key, value)
|
||||
|
||||
def append(self, key, value):
|
||||
tail = self.root.prev if self.root.prev is not self.root else self.root
|
||||
node = itemnode(tail, self.root, key=key, value=value)
|
||||
tail.next = node
|
||||
self.root.prev = node
|
||||
self.size += 1
|
||||
return node
|
||||
|
||||
def removenode(self, node):
|
||||
node.prev.next = node.next
|
||||
node.next.prev = node.prev
|
||||
self.size -= 1
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
for node, key, value in self:
|
||||
self.removenode(node)
|
||||
return self
|
||||
|
||||
def items(self):
|
||||
return list(self.iteritems())
|
||||
|
||||
def keys(self):
|
||||
return list(self.iterkeys())
|
||||
|
||||
def values(self):
|
||||
return list(self.itervalues())
|
||||
|
||||
def iteritems(self):
|
||||
for node, key, value in self:
|
||||
yield key, value
|
||||
|
||||
def iterkeys(self):
|
||||
for node, key, value in self:
|
||||
yield key
|
||||
|
||||
def itervalues(self):
|
||||
for node, key, value in self:
|
||||
yield value
|
||||
|
||||
def reverse(self):
|
||||
for node, key, value in self:
|
||||
node.prev, node.next = node.next, node.prev
|
||||
self.root.prev, self.root.next = self.root.next, self.root.prev
|
||||
return self
|
||||
|
||||
def __len__(self):
|
||||
return self.size
|
||||
|
||||
def __iter__(self):
|
||||
current = self.root.next
|
||||
while current and current is not self.root:
|
||||
# Record current.next here in case current.next changes after the
|
||||
# yield and before we return for the next iteration. For example,
|
||||
# methods like reverse() will change current.next() before yield
|
||||
# gets executed again.
|
||||
nextnode = current.next
|
||||
yield current, current.key, current.value
|
||||
current = nextnode
|
||||
|
||||
def __contains__(self, item):
|
||||
"""
|
||||
Params:
|
||||
item: Can either be a (key,value) tuple or an itemnode reference.
|
||||
"""
|
||||
node = key = value = _absent
|
||||
if hasattr(item, '__len__') and callable(item.__len__):
|
||||
if len(item) == 2:
|
||||
key, value = item
|
||||
elif len(item) == 3:
|
||||
node, key, value = item
|
||||
else:
|
||||
node = item
|
||||
|
||||
if node is not _absent or _absent not in [key, value]:
|
||||
for selfnode, selfkey, selfvalue in self:
|
||||
if ((node is _absent and key == selfkey and value == selfvalue)
|
||||
or (node is not _absent and node == selfnode)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getitem__(self, index):
|
||||
# Only support direct access to the first or last element, as this is
|
||||
# all orderedmultidict needs for now.
|
||||
if index == 0 and self.root.next is not self.root:
|
||||
return self.root.next
|
||||
elif index == -1 and self.root.prev is not self.root:
|
||||
return self.root.prev
|
||||
raise IndexError(index)
|
||||
|
||||
def __delitem__(self, index):
|
||||
self.removenode(self[index])
|
||||
|
||||
def __eq__(self, other):
|
||||
for (n1, key1, value1), (n2, key2, value2) in zip_longest(self, other):
|
||||
if key1 != key2 or value1 != value2:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.size > 0
|
||||
|
||||
def __str__(self):
|
||||
return '[%s]' % self.items()
|
||||
+811
@@ -0,0 +1,811 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# omdict - Ordered Multivalue Dictionary.
|
||||
#
|
||||
# Ansgar Grunseid
|
||||
# grunseid.com
|
||||
# grunseid@gmail.com
|
||||
#
|
||||
# License: Build Amazing Things (Unlicense)
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from itertools import chain
|
||||
from collections import MutableMapping
|
||||
|
||||
import six
|
||||
from six.moves import map, zip_longest
|
||||
|
||||
from .itemlist import itemlist
|
||||
|
||||
try:
|
||||
from collections import OrderedDict as odict # Python 2.7 and later.
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict as odict # Python 2.6 and earlier.
|
||||
|
||||
import sys
|
||||
items_attr = 'items' if sys.version_info[0] >= 3 else 'iteritems'
|
||||
|
||||
_absent = object() # Marker that means no parameter was provided.
|
||||
|
||||
|
||||
def callable_attr(obj, attr):
|
||||
return hasattr(obj, attr) and callable(getattr(obj, attr))
|
||||
|
||||
|
||||
#
|
||||
# TODO(grun): Create a subclass of list that values(), getlist(), allitems(),
|
||||
# etc return that the user can manipulate directly to control the omdict()
|
||||
# object.
|
||||
#
|
||||
# For example, users should be able to do things like
|
||||
#
|
||||
# omd = omdict([(1,1), (1,11)])
|
||||
# omd.values(1).append('sup')
|
||||
# omd.allitems() == [(1,1), (1,11), (1,'sup')]
|
||||
# omd.values(1).remove(11)
|
||||
# omd.allitems() == [(1,1), (1,'sup')]
|
||||
# omd.values(1).extend(['two', 'more'])
|
||||
# omd.allitems() == [(1,1), (1,'sup'), (1,'two'), (1,'more')]
|
||||
#
|
||||
# or
|
||||
#
|
||||
# omd = omdict([(1,1), (1,11)])
|
||||
# omd.allitems().extend([(2,2), (2,22)])
|
||||
# omd.allitems() == [(1,1), (1,11), (2,2), (2,22)])
|
||||
#
|
||||
# or
|
||||
#
|
||||
# omd = omdict()
|
||||
# omd.values(1) = [1, 11]
|
||||
# omd.allitems() == [(1,1), (1,11)]
|
||||
# omd.values(1) = list(map(lambda i: i * -10, omd.values(1)))
|
||||
# omd.allitems() == [(1,-10), (1,-110)]
|
||||
# omd.allitems() = filter(lambda (k,v): v > -100, omd.allitems())
|
||||
# omd.allitems() == [(1,-10)]
|
||||
#
|
||||
# etc.
|
||||
#
|
||||
# To accomplish this, subclass list in such a manner that each list element is
|
||||
# really a two tuple, where the first tuple value is the actual value and the
|
||||
# second tuple value is a reference to the itemlist node for that value. Users
|
||||
# only interact with the first tuple values, the actual values, but behind the
|
||||
# scenes when an element is modified, deleted, inserted, etc, the according
|
||||
# itemlist nodes are modified, deleted, inserted, etc accordingly. In this
|
||||
# manner, users can manipulate omdict objects directly through direct list
|
||||
# manipulation.
|
||||
#
|
||||
# Once accomplished, some methods become redundant and should be removed in
|
||||
# favor of the more intuitive direct value list manipulation. Such redundant
|
||||
# methods include getlist() (removed in favor of values()?), addlist(), and
|
||||
# setlist().
|
||||
#
|
||||
# With the removal of many of the 'list' methods, think about renaming all
|
||||
# remaining 'list' methods to 'values' methods, like poplist() -> popvalues(),
|
||||
# poplistitem() -> popvaluesitem(), etc. This would be an easy switch for most
|
||||
# methods, but wouldn't fit others so well. For example, iterlists() would
|
||||
# become itervalues(), a name extremely similar to iterallvalues() but quite
|
||||
# different in function.
|
||||
#
|
||||
|
||||
|
||||
class omdict(MutableMapping):
|
||||
|
||||
"""
|
||||
Ordered Multivalue Dictionary.
|
||||
|
||||
A multivalue dictionary is a dictionary that can store multiple values per
|
||||
key. An ordered multivalue dictionary is a multivalue dictionary that
|
||||
retains the order of insertions and deletions.
|
||||
|
||||
Internally, items are stored in a doubly linked list, self._items. A
|
||||
dictionary, self._map, is also maintained and stores an ordered list of
|
||||
linked list node references, one for each value associated with that key.
|
||||
|
||||
Standard dict methods interact with the first value associated with a given
|
||||
key. This means that omdict retains method parity with dict, and a dict
|
||||
object can be replaced with an omdict object and all interaction will
|
||||
behave identically. All dict methods that retain parity with omdict are:
|
||||
|
||||
get(), setdefault(), pop(), popitem(),
|
||||
clear(), copy(), update(), fromkeys(), len()
|
||||
__getitem__(), __setitem__(), __delitem__(), __contains__(),
|
||||
items(), keys(), values(), iteritems(), iterkeys(), itervalues(),
|
||||
|
||||
Optional parameters have been added to some dict methods, but because the
|
||||
added parameters are optional, existing use remains unaffected. An optional
|
||||
<key> parameter has been added to these methods:
|
||||
|
||||
items(), values(), iteritems(), itervalues()
|
||||
|
||||
New methods have also been added to omdict. Methods with 'list' in their
|
||||
name interact with lists of values, and methods with 'all' in their name
|
||||
interact with all items in the dictionary, including multiple items with
|
||||
the same key.
|
||||
|
||||
The new omdict methods are:
|
||||
|
||||
load(), size(), reverse(),
|
||||
getlist(), add(), addlist(), set(), setlist(), setdefaultlist(),
|
||||
poplist(), popvalue(), popvalues(), popitem(), poplistitem(),
|
||||
allitems(), allkeys(), allvalues(), lists(), listitems(),
|
||||
iterallitems(), iterallkeys(), iterallvalues(), iterlists(),
|
||||
iterlistitems()
|
||||
|
||||
Explanations and examples of the new methods above can be found in the
|
||||
function comments below and online at
|
||||
|
||||
https://github.com/gruns/orderedmultidict
|
||||
|
||||
Additional omdict information and documentation can also be found at the
|
||||
above url.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Doubly linked list of itemnodes. Each itemnode stores a key:value
|
||||
# item.
|
||||
self._items = itemlist()
|
||||
|
||||
# Ordered dictionary of keys and itemnode references. Each itemnode
|
||||
# reference points to one of that keys values.
|
||||
self._map = odict()
|
||||
|
||||
self.load(*args, **kwargs)
|
||||
|
||||
def load(self, *args, **kwargs):
|
||||
"""
|
||||
Clear all existing key:value items and import all key:value items from
|
||||
<mapping>. If multiple values exist for the same key in <mapping>, they
|
||||
are all be imported.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.load([(4,4), (4,44), (5,5)])
|
||||
omd.allitems() == [(4,4), (4,44), (5,5)]
|
||||
|
||||
Returns: <self>.
|
||||
"""
|
||||
self.clear()
|
||||
self.updateall(*args, **kwargs)
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self.allitems())
|
||||
|
||||
def clear(self):
|
||||
self._map.clear()
|
||||
self._items.clear()
|
||||
|
||||
def size(self):
|
||||
"""
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.size() == 5
|
||||
|
||||
Returns: Total number of items, including multiple items with the same
|
||||
key.
|
||||
"""
|
||||
return len(self._items)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
return cls([(key, value) for key in iterable])
|
||||
|
||||
def has_key(self, key):
|
||||
return key in self
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self._update_updateall(True, *args, **kwargs)
|
||||
|
||||
def updateall(self, *args, **kwargs):
|
||||
"""
|
||||
Update this dictionary with the items from <mapping>, replacing
|
||||
existing key:value items with shared keys before adding new key:value
|
||||
items.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (2,2)])
|
||||
omd.updateall([(2,'two'), (1,'one'), (2,222), (1,111)])
|
||||
omd.allitems() == [(1, 'one'), (2, 'two'), (2, 222), (1, 111)]
|
||||
|
||||
Returns: <self>.
|
||||
"""
|
||||
self._update_updateall(False, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def _update_updateall(self, replace_at_most_one, *args, **kwargs):
|
||||
# Bin the items in <args> and <kwargs> into <replacements> or
|
||||
# <leftovers>. Items in <replacements> are new values to replace old
|
||||
# values for a given key, and items in <leftovers> are new items to be
|
||||
# added.
|
||||
replacements, leftovers = dict(), []
|
||||
for mapping in chain(args, [kwargs]):
|
||||
self._bin_update_items(
|
||||
self._items_iterator(mapping), replace_at_most_one,
|
||||
replacements, leftovers)
|
||||
|
||||
# First, replace existing values for each key.
|
||||
for key, values in six.iteritems(replacements):
|
||||
self.setlist(key, values)
|
||||
# Then, add the leftover items to the end of the list of all items.
|
||||
for key, value in leftovers:
|
||||
self.add(key, value)
|
||||
|
||||
def _bin_update_items(self, items, replace_at_most_one,
|
||||
replacements, leftovers):
|
||||
"""
|
||||
<replacements and <leftovers> are modified directly, ala pass by
|
||||
reference.
|
||||
"""
|
||||
for key, value in items:
|
||||
# If there are existing items with key <key> that have yet to be
|
||||
# marked for replacement, mark that item's value to be replaced by
|
||||
# <value> by appending it to <replacements>.
|
||||
if key in self and key not in replacements:
|
||||
replacements[key] = [value]
|
||||
elif (key in self and not replace_at_most_one and
|
||||
len(replacements[key]) < len(self.values(key))):
|
||||
replacements[key].append(value)
|
||||
else:
|
||||
if replace_at_most_one:
|
||||
replacements[key] = [value]
|
||||
else:
|
||||
leftovers.append((key, value))
|
||||
|
||||
def _items_iterator(self, container):
|
||||
cont = container
|
||||
iterator = iter(cont)
|
||||
if callable_attr(cont, 'iterallitems'):
|
||||
iterator = cont.iterallitems()
|
||||
elif callable_attr(cont, 'allitems'):
|
||||
iterator = iter(cont.allitems())
|
||||
elif callable_attr(cont, 'iteritems'):
|
||||
iterator = cont.iteritems()
|
||||
elif callable_attr(cont, 'items'):
|
||||
iterator = iter(cont.items())
|
||||
return iterator
|
||||
|
||||
def get(self, key, default=None):
|
||||
if key in self:
|
||||
return self._map[key][0].value
|
||||
return default
|
||||
|
||||
def getlist(self, key, default=[]):
|
||||
"""
|
||||
Returns: The list of values for <key> if <key> is in the dictionary,
|
||||
else <default>. If <default> is not provided, an empty list is
|
||||
returned.
|
||||
"""
|
||||
if key in self:
|
||||
return [node.value for node in self._map[key]]
|
||||
return default
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
if key in self:
|
||||
return self[key]
|
||||
self.add(key, default)
|
||||
return default
|
||||
|
||||
def setdefaultlist(self, key, defaultlist=[None]):
|
||||
"""
|
||||
Similar to setdefault() except <defaultlist> is a list of values to set
|
||||
for <key>. If <key> already exists, its existing list of values is
|
||||
returned.
|
||||
|
||||
If <key> isn't a key and <defaultlist> is an empty list, [], no values
|
||||
are added for <key> and <key> will not be added as a key.
|
||||
|
||||
Returns: List of <key>'s values if <key> exists in the dictionary,
|
||||
otherwise <default>.
|
||||
"""
|
||||
if key in self:
|
||||
return self.getlist(key)
|
||||
self.addlist(key, defaultlist)
|
||||
return defaultlist
|
||||
|
||||
def add(self, key, value=None):
|
||||
"""
|
||||
Add <value> to the list of values for <key>. If <key> is not in the
|
||||
dictionary, then <value> is added as the sole value for <key>.
|
||||
|
||||
Example:
|
||||
omd = omdict()
|
||||
omd.add(1, 1) # omd.allitems() == [(1,1)]
|
||||
omd.add(1, 11) # omd.allitems() == [(1,1), (1,11)]
|
||||
omd.add(2, 2) # omd.allitems() == [(1,1), (1,11), (2,2)]
|
||||
|
||||
Returns: <self>.
|
||||
"""
|
||||
self._map.setdefault(key, [])
|
||||
node = self._items.append(key, value)
|
||||
self._map[key].append(node)
|
||||
return self
|
||||
|
||||
def addlist(self, key, valuelist=[]):
|
||||
"""
|
||||
Add the values in <valuelist> to the list of values for <key>. If <key>
|
||||
is not in the dictionary, the values in <valuelist> become the values
|
||||
for <key>.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1)])
|
||||
omd.addlist(1, [11, 111])
|
||||
omd.allitems() == [(1, 1), (1, 11), (1, 111)]
|
||||
omd.addlist(2, [2])
|
||||
omd.allitems() == [(1, 1), (1, 11), (1, 111), (2, 2)]
|
||||
|
||||
Returns: <self>.
|
||||
"""
|
||||
for value in valuelist:
|
||||
self.add(key, value)
|
||||
return self
|
||||
|
||||
def set(self, key, value=None):
|
||||
"""
|
||||
Sets <key>'s value to <value>. Identical in function to __setitem__().
|
||||
|
||||
Returns: <self>.
|
||||
"""
|
||||
self[key] = value
|
||||
return self
|
||||
|
||||
def setlist(self, key, values):
|
||||
"""
|
||||
Sets <key>'s list of values to <values>. Existing items with key <key>
|
||||
are first replaced with new values from <values>. Any remaining old
|
||||
items that haven't been replaced with new values are deleted, and any
|
||||
new values from <values> that don't have corresponding items with <key>
|
||||
to replace are appended to the end of the list of all items.
|
||||
|
||||
If values is an empty list, [], <key> is deleted, equivalent in action
|
||||
to del self[<key>].
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (2,2)])
|
||||
omd.setlist(1, [11, 111])
|
||||
omd.allitems() == [(1,11), (2,2), (1,111)]
|
||||
|
||||
omd = omdict([(1,1), (1,11), (2,2), (1,111)])
|
||||
omd.setlist(1, [None])
|
||||
omd.allitems() == [(1,None), (2,2)]
|
||||
|
||||
omd = omdict([(1,1), (1,11), (2,2), (1,111)])
|
||||
omd.setlist(1, [])
|
||||
omd.allitems() == [(2,2)]
|
||||
|
||||
Returns: <self>.
|
||||
"""
|
||||
if not values and key in self:
|
||||
self.pop(key)
|
||||
else:
|
||||
it = zip_longest(
|
||||
list(self._map.get(key, [])), values, fillvalue=_absent)
|
||||
for node, value in it:
|
||||
if node is not _absent and value is not _absent:
|
||||
node.value = value
|
||||
elif node is _absent:
|
||||
self.add(key, value)
|
||||
elif value is _absent:
|
||||
self._map[key].remove(node)
|
||||
self._items.removenode(node)
|
||||
return self
|
||||
|
||||
def removevalues(self, key, values):
|
||||
"""
|
||||
Removes all <values> from the values of <key>. If <key> has no
|
||||
remaining values after removevalues(), the key is popped.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1, 1), (1, 11), (1, 1), (1, 111)])
|
||||
omd.removevalues(1, [1, 111])
|
||||
omd.allitems() == [(1, 11)]
|
||||
|
||||
Returns: <self>.
|
||||
"""
|
||||
self.setlist(key, [v for v in self.getlist(key) if v not in values])
|
||||
return self
|
||||
|
||||
def pop(self, key, default=_absent):
|
||||
if key in self:
|
||||
return self.poplist(key)[0]
|
||||
elif key not in self._map and default is not _absent:
|
||||
return default
|
||||
raise KeyError(key)
|
||||
|
||||
def poplist(self, key, default=_absent):
|
||||
"""
|
||||
If <key> is in the dictionary, pop it and return its list of values. If
|
||||
<key> is not in the dictionary, return <default>. KeyError is raised if
|
||||
<default> is not provided and <key> is not in the dictionary.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.poplist(1) == [1, 11, 111]
|
||||
omd.allitems() == [(2,2), (3,3)]
|
||||
omd.poplist(2) == [2]
|
||||
omd.allitems() == [(3,3)]
|
||||
|
||||
Raises: KeyError if <key> isn't in the dictionary and <default> isn't
|
||||
provided.
|
||||
Returns: List of <key>'s values.
|
||||
"""
|
||||
if key in self:
|
||||
values = self.getlist(key)
|
||||
del self._map[key]
|
||||
for node, nodekey, nodevalue in self._items:
|
||||
if nodekey == key:
|
||||
self._items.removenode(node)
|
||||
return values
|
||||
elif key not in self._map and default is not _absent:
|
||||
return default
|
||||
raise KeyError(key)
|
||||
|
||||
def popvalue(self, key, value=_absent, default=_absent, last=True):
|
||||
"""
|
||||
If <value> is provided, pops the first or last (key,value) item in the
|
||||
dictionary if <key> is in the dictionary.
|
||||
|
||||
If <value> is not provided, pops the first or last value for <key> if
|
||||
<key> is in the dictionary.
|
||||
|
||||
If <key> no longer has any values after a popvalue() call, <key> is
|
||||
removed from the dictionary. If <key> isn't in the dictionary and
|
||||
<default> was provided, return default. KeyError is raised if <default>
|
||||
is not provided and <key> is not in the dictionary. ValueError is
|
||||
raised if <value> is provided but isn't a value for <key>.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3), (2,22)])
|
||||
omd.popvalue(1) == 111
|
||||
omd.allitems() == [(1,11), (1,111), (2,2), (3,3), (2,22)]
|
||||
omd.popvalue(1, last=False) == 1
|
||||
omd.allitems() == [(1,11), (2,2), (3,3), (2,22)]
|
||||
omd.popvalue(2, 2) == 2
|
||||
omd.allitems() == [(1,11), (3,3), (2,22)]
|
||||
omd.popvalue(1, 11) == 11
|
||||
omd.allitems() == [(3,3), (2,22)]
|
||||
omd.popvalue('not a key', default='sup') == 'sup'
|
||||
|
||||
Params:
|
||||
last: Boolean whether to return <key>'s first value (<last> is False)
|
||||
or last value (<last> is True).
|
||||
Raises:
|
||||
KeyError if <key> isn't in the dictionary and <default> isn't
|
||||
provided.
|
||||
ValueError if <value> isn't a value for <key>.
|
||||
Returns: The first or last of <key>'s values.
|
||||
"""
|
||||
def pop_node_with_index(key, index):
|
||||
node = self._map[key].pop(index)
|
||||
if not self._map[key]:
|
||||
del self._map[key]
|
||||
self._items.removenode(node)
|
||||
return node
|
||||
|
||||
if key in self:
|
||||
if value is not _absent:
|
||||
if last:
|
||||
pos = self.values(key)[::-1].index(value)
|
||||
else:
|
||||
pos = self.values(key).index(value)
|
||||
if pos == -1:
|
||||
raise ValueError(value)
|
||||
else:
|
||||
index = (len(self.values(key)) - 1 - pos) if last else pos
|
||||
return pop_node_with_index(key, index).value
|
||||
else:
|
||||
return pop_node_with_index(key, -1 if last else 0).value
|
||||
elif key not in self._map and default is not _absent:
|
||||
return default
|
||||
raise KeyError(key)
|
||||
|
||||
def popitem(self, fromall=False, last=True):
|
||||
"""
|
||||
Pop and return a key:value item.
|
||||
|
||||
If <fromall> is False, items()[0] is popped if <last> is False or
|
||||
items()[-1] is popped if <last> is True. All remaining items with the
|
||||
same key are removed.
|
||||
|
||||
If <fromall> is True, allitems()[0] is popped if <last> is False or
|
||||
allitems()[-1] is popped if <last> is True. Any remaining items with
|
||||
the same key remain.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.popitem() == (3,3)
|
||||
omd.popitem(fromall=False, last=False) == (1,1)
|
||||
omd.popitem(fromall=False, last=False) == (2,2)
|
||||
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.popitem(fromall=True, last=False) == (1,1)
|
||||
omd.popitem(fromall=True, last=False) == (1,11)
|
||||
omd.popitem(fromall=True, last=True) == (3,3)
|
||||
omd.popitem(fromall=True, last=False) == (1,111)
|
||||
|
||||
Params:
|
||||
fromall: Whether to pop an item from items() (<fromall> is True) or
|
||||
allitems() (<fromall> is False).
|
||||
last: Boolean whether to pop the first item or last item of items()
|
||||
or allitems().
|
||||
Raises: KeyError if the dictionary is empty.
|
||||
Returns: The first or last item from item() or allitem().
|
||||
"""
|
||||
if not self._items:
|
||||
raise KeyError('popitem(): %s is empty' % self.__class__.__name__)
|
||||
|
||||
if fromall:
|
||||
node = self._items[-1 if last else 0]
|
||||
key = node.key
|
||||
return key, self.popvalue(key, last=last)
|
||||
else:
|
||||
key = list(self._map.keys())[-1 if last else 0]
|
||||
return key, self.pop(key)
|
||||
|
||||
def poplistitem(self, last=True):
|
||||
"""
|
||||
Pop and return a key:valuelist item comprised of a key and that key's
|
||||
list of values. If <last> is False, a key:valuelist item comprised of
|
||||
keys()[0] and its list of values is popped and returned. If <last> is
|
||||
True, a key:valuelist item comprised of keys()[-1] and its list of
|
||||
values is popped and returned.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.poplistitem(last=True) == (3,[3])
|
||||
omd.poplistitem(last=False) == (1,[1,11,111])
|
||||
|
||||
Params:
|
||||
last: Boolean whether to pop the first or last key and its associated
|
||||
list of values.
|
||||
Raises: KeyError if the dictionary is empty.
|
||||
Returns: A two-tuple comprised of the first or last key and its
|
||||
associated list of values.
|
||||
"""
|
||||
if not self._items:
|
||||
s = 'poplistitem(): %s is empty' % self.__class__.__name__
|
||||
raise KeyError(s)
|
||||
|
||||
key = self.keys()[-1 if last else 0]
|
||||
return key, self.poplist(key)
|
||||
|
||||
def items(self, key=_absent):
|
||||
"""
|
||||
Raises: KeyError if <key> is provided and not in the dictionary.
|
||||
Returns: List created from iteritems(<key>). Only items with key <key>
|
||||
are returned if <key> is provided and is a dictionary key.
|
||||
"""
|
||||
return list(self.iteritems(key))
|
||||
|
||||
def keys(self):
|
||||
return list(self.iterkeys())
|
||||
|
||||
def values(self, key=_absent):
|
||||
"""
|
||||
Raises: KeyError if <key> is provided and not in the dictionary.
|
||||
Returns: List created from itervalues(<key>).If <key> is provided and
|
||||
is a dictionary key, only values of items with key <key> are
|
||||
returned.
|
||||
"""
|
||||
if key is not _absent and key in self._map:
|
||||
return self.getlist(key)
|
||||
return list(self.itervalues())
|
||||
|
||||
def lists(self):
|
||||
"""
|
||||
Returns: List created from iterlists().
|
||||
"""
|
||||
return list(self.iterlists())
|
||||
|
||||
def listitems(self):
|
||||
"""
|
||||
Returns: List created from iterlistitems().
|
||||
"""
|
||||
return list(self.iterlistitems())
|
||||
|
||||
def iteritems(self, key=_absent):
|
||||
"""
|
||||
Parity with dict.iteritems() except the optional <key> parameter has
|
||||
been added. If <key> is provided, only items with the provided key are
|
||||
iterated over. KeyError is raised if <key> is provided and not in the
|
||||
dictionary.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.iteritems(1) -> (1,1) -> (1,11) -> (1,111)
|
||||
omd.iteritems() -> (1,1) -> (2,2) -> (3,3)
|
||||
|
||||
Raises: KeyError if <key> is provided and not in the dictionary.
|
||||
Returns: An iterator over the items() of the dictionary, or only items
|
||||
with the key <key> if <key> is provided.
|
||||
"""
|
||||
if key is not _absent:
|
||||
if key in self:
|
||||
items = [(node.key, node.value) for node in self._map[key]]
|
||||
return iter(items)
|
||||
raise KeyError(key)
|
||||
items = six.iteritems(self._map)
|
||||
return iter((key, nodes[0].value) for (key, nodes) in items)
|
||||
|
||||
def iterkeys(self):
|
||||
return six.iterkeys(self._map)
|
||||
|
||||
def itervalues(self, key=_absent):
|
||||
"""
|
||||
Parity with dict.itervalues() except the optional <key> parameter has
|
||||
been added. If <key> is provided, only values from items with the
|
||||
provided key are iterated over. KeyError is raised if <key> is provided
|
||||
and not in the dictionary.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.itervalues(1) -> 1 -> 11 -> 111
|
||||
omd.itervalues() -> 1 -> 11 -> 111 -> 2 -> 3
|
||||
|
||||
Raises: KeyError if <key> is provided and isn't in the dictionary.
|
||||
Returns: An iterator over the values() of the dictionary, or only the
|
||||
values of key <key> if <key> is provided.
|
||||
"""
|
||||
if key is not _absent:
|
||||
if key in self:
|
||||
return iter([node.value for node in self._map[key]])
|
||||
raise KeyError(key)
|
||||
return iter([nodes[0].value for nodes in six.itervalues(self._map)])
|
||||
|
||||
def allitems(self, key=_absent):
|
||||
'''
|
||||
Raises: KeyError if <key> is provided and not in the dictionary.
|
||||
Returns: List created from iterallitems(<key>).
|
||||
'''
|
||||
return list(self.iterallitems(key))
|
||||
|
||||
def allkeys(self):
|
||||
'''
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.allkeys() == [1,1,1,2,3]
|
||||
|
||||
Returns: List created from iterallkeys().
|
||||
'''
|
||||
return list(self.iterallkeys())
|
||||
|
||||
def allvalues(self, key=_absent):
|
||||
'''
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.allvalues() == [1,11,111,2,3]
|
||||
omd.allvalues(1) == [1,11,111]
|
||||
|
||||
Raises: KeyError if <key> is provided and not in the dictionary.
|
||||
Returns: List created from iterallvalues(<key>).
|
||||
'''
|
||||
return list(self.iterallvalues(key))
|
||||
|
||||
def iterallitems(self, key=_absent):
|
||||
'''
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.iterallitems() == (1,1) -> (1,11) -> (1,111) -> (2,2) -> (3,3)
|
||||
omd.iterallitems(1) == (1,1) -> (1,11) -> (1,111)
|
||||
|
||||
Raises: KeyError if <key> is provided and not in the dictionary.
|
||||
Returns: An iterator over every item in the diciontary. If <key> is
|
||||
provided, only items with the key <key> are iterated over.
|
||||
'''
|
||||
if key is not _absent:
|
||||
# Raises KeyError if <key> is not in self._map.
|
||||
return self.iteritems(key)
|
||||
return self._items.iteritems()
|
||||
|
||||
def iterallkeys(self):
|
||||
'''
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.iterallkeys() == 1 -> 1 -> 1 -> 2 -> 3
|
||||
|
||||
Returns: An iterator over the keys of every item in the dictionary.
|
||||
'''
|
||||
return self._items.iterkeys()
|
||||
|
||||
def iterallvalues(self, key=_absent):
|
||||
'''
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.iterallvalues() == 1 -> 11 -> 111 -> 2 -> 3
|
||||
|
||||
Returns: An iterator over the values of every item in the dictionary.
|
||||
'''
|
||||
if key is not _absent:
|
||||
if key in self:
|
||||
return iter(self.getlist(key))
|
||||
raise KeyError(key)
|
||||
return self._items.itervalues()
|
||||
|
||||
def iterlists(self):
|
||||
'''
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.iterlists() -> [1,11,111] -> [2] -> [3]
|
||||
|
||||
Returns: An iterator over the list comprised of the lists of values for
|
||||
each key.
|
||||
'''
|
||||
return map(lambda key: self.getlist(key), self)
|
||||
|
||||
def iterlistitems(self):
|
||||
"""
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.iterlistitems() -> (1,[1,11,111]) -> (2,[2]) -> (3,[3])
|
||||
|
||||
Returns: An iterator over the list of key:valuelist items.
|
||||
"""
|
||||
return map(lambda key: (key, self.getlist(key)), self)
|
||||
|
||||
def reverse(self):
|
||||
"""
|
||||
Reverse the order of all items in the dictionary.
|
||||
|
||||
Example:
|
||||
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
|
||||
omd.reverse()
|
||||
omd.allitems() == [(3,3), (2,2), (1,111), (1,11), (1,1)]
|
||||
|
||||
Returns: <self>.
|
||||
"""
|
||||
for key in six.iterkeys(self._map):
|
||||
self._map[key].reverse()
|
||||
self._items.reverse()
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
if callable_attr(other, 'iterallitems'):
|
||||
myiter, otheriter = self.iterallitems(), other.iterallitems()
|
||||
for i1, i2 in zip_longest(myiter, otheriter, fillvalue=_absent):
|
||||
if i1 != i2 or i1 is _absent or i2 is _absent:
|
||||
return False
|
||||
elif not hasattr(other, '__len__') or not hasattr(other, items_attr):
|
||||
return False
|
||||
# Ignore order so we can compare ordered omdicts with unordered dicts.
|
||||
else:
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
for key, value in six.iteritems(other):
|
||||
if self.get(key, _absent) != value:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._map)
|
||||
|
||||
def __iter__(self):
|
||||
for key in self.iterkeys():
|
||||
yield key
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._map
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self:
|
||||
return self.get(key)
|
||||
raise KeyError(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.setlist(key, [value])
|
||||
|
||||
def __delitem__(self, key):
|
||||
return self.pop(key)
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self._map)
|
||||
|
||||
def __str__(self):
|
||||
return '{%s}' % ', '.join(
|
||||
map(lambda p: '%r: %r' % (p[0], p[1]), self.iterallitems()))
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, self.allitems())
|
||||
Vendored
+74
-57
@@ -3,7 +3,7 @@ r'''Parse strings using a specification based on the Python format() syntax.
|
||||
``parse()`` is the opposite of ``format()``
|
||||
|
||||
The module is set up to only export ``parse()``, ``search()``, ``findall()``,
|
||||
and ``with_pattern()`` when ``import *`` is used:
|
||||
and ``with_pattern()`` when ``import \*`` is used:
|
||||
|
||||
>>> from parse import *
|
||||
|
||||
@@ -78,9 +78,11 @@ Some simple parse() format string examples:
|
||||
{'item': 'hand grenade'}
|
||||
>>> print(r['item'])
|
||||
hand grenade
|
||||
>>> 'item' in r
|
||||
True
|
||||
|
||||
Dotted names and indexes are possible though the application must make
|
||||
additional sense of the result:
|
||||
Note that `in` only works if you have named fields. Dotted names and indexes
|
||||
are possible though the application must make additional sense of the result:
|
||||
|
||||
>>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!")
|
||||
>>> print(r)
|
||||
@@ -132,38 +134,39 @@ The differences between `parse()` and `format()` are:
|
||||
===== =========================================== ========
|
||||
Type Characters Matched Output
|
||||
===== =========================================== ========
|
||||
w Letters and underscore str
|
||||
W Non-letter and underscore str
|
||||
s Whitespace str
|
||||
S Non-whitespace str
|
||||
d Digits (effectively integer numbers) int
|
||||
D Non-digit str
|
||||
n Numbers with thousands separators (, or .) int
|
||||
% Percentage (converted to value/100.0) float
|
||||
f Fixed-point numbers float
|
||||
F Decimal numbers Decimal
|
||||
e Floating-point numbers with exponent float
|
||||
l Letters (ASCII) str
|
||||
w Letters, numbers and underscore str
|
||||
W Not letters, numbers and underscore str
|
||||
s Whitespace str
|
||||
S Non-whitespace str
|
||||
d Digits (effectively integer numbers) int
|
||||
D Non-digit str
|
||||
n Numbers with thousands separators (, or .) int
|
||||
% Percentage (converted to value/100.0) float
|
||||
f Fixed-point numbers float
|
||||
F Decimal numbers Decimal
|
||||
e Floating-point numbers with exponent float
|
||||
e.g. 1.1e-10, NAN (all case insensitive)
|
||||
g General number format (either d, f or e) float
|
||||
b Binary numbers int
|
||||
o Octal numbers int
|
||||
x Hexadecimal numbers (lower and upper case) int
|
||||
ti ISO 8601 format date/time datetime
|
||||
g General number format (either d, f or e) float
|
||||
b Binary numbers int
|
||||
o Octal numbers int
|
||||
x Hexadecimal numbers (lower and upper case) int
|
||||
ti ISO 8601 format date/time datetime
|
||||
e.g. 1972-01-20T10:21:36Z ("T" and "Z"
|
||||
optional)
|
||||
te RFC2822 e-mail format date/time datetime
|
||||
te RFC2822 e-mail format date/time datetime
|
||||
e.g. Mon, 20 Jan 1972 10:21:36 +1000
|
||||
tg Global (day/month) format date/time datetime
|
||||
tg Global (day/month) format date/time datetime
|
||||
e.g. 20/1/1972 10:21:36 AM +1:00
|
||||
ta US (month/day) format date/time datetime
|
||||
ta US (month/day) format date/time datetime
|
||||
e.g. 1/20/1972 10:21:36 PM +10:30
|
||||
tc ctime() format date/time datetime
|
||||
tc ctime() format date/time datetime
|
||||
e.g. Sun Sep 16 01:03:52 1973
|
||||
th HTTP log format date/time datetime
|
||||
th HTTP log format date/time datetime
|
||||
e.g. 21/Nov/2011:00:07:11 +0000
|
||||
ts Linux system log format date/time datetime
|
||||
ts Linux system log format date/time datetime
|
||||
e.g. Nov 9 03:37:44
|
||||
tt Time time
|
||||
tt Time time
|
||||
e.g. 10:21:36 PM -5:30
|
||||
===== =========================================== ========
|
||||
|
||||
@@ -342,6 +345,13 @@ the pattern, the actual match represents the shortest successful match for
|
||||
|
||||
**Version history (in brief)**:
|
||||
|
||||
- 1.11.1 Revert having unicode char in docstring, it breaks Bamboo builds(?!)
|
||||
- 1.11.0 Implement `__contains__` for Result instances.
|
||||
- 1.10.0 Introduce a "letters" matcher, since "w" matches numbers
|
||||
also.
|
||||
- 1.9.1 Fix deprecation warnings around backslashes in regex strings
|
||||
(thanks Mickael Schoentgen). Also fix some documentation formatting
|
||||
issues.
|
||||
- 1.9.0 We now honor precision and width specifiers when parsing numbers
|
||||
and strings, allowing parsing of concatenated elements of fixed width
|
||||
(thanks Julia Signell)
|
||||
@@ -400,12 +410,12 @@ the pattern, the actual match represents the shortest successful match for
|
||||
and removed the restriction on mixing fixed-position and named fields
|
||||
- 1.0.0 initial release
|
||||
|
||||
This code is copyright 2012-2017 Richard Jones <richard@python.org>
|
||||
This code is copyright 2012-2019 Richard Jones <richard@python.org>
|
||||
See the end of the source file for the license of use.
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
__version__ = '1.9.0'
|
||||
__version__ = '1.11.1'
|
||||
|
||||
# yes, I now have two problems
|
||||
import re
|
||||
@@ -530,9 +540,9 @@ MONTHS_MAP = dict(
|
||||
Nov=11, November=11,
|
||||
Dec=12, December=12
|
||||
)
|
||||
DAYS_PAT = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
|
||||
MONTHS_PAT = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'
|
||||
ALL_MONTHS_PAT = '(%s)' % '|'.join(MONTHS_MAP)
|
||||
DAYS_PAT = r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
|
||||
MONTHS_PAT = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'
|
||||
ALL_MONTHS_PAT = r'(%s)' % '|'.join(MONTHS_MAP)
|
||||
TIME_PAT = r'(\d{1,2}:\d{1,2}(:\d{1,2}(\.\d+)?)?)'
|
||||
AM_PAT = r'(\s+[AP]M)'
|
||||
TZ_PAT = r'(\s+[-+]\d\d?:?\d\d)'
|
||||
@@ -550,11 +560,11 @@ def date_convert(string, match, ymd=None, mdy=None, dmy=None,
|
||||
m=groups[mm]
|
||||
d=groups[dd]
|
||||
elif ymd is not None:
|
||||
y, m, d = re.split('[-/\s]', groups[ymd])
|
||||
y, m, d = re.split(r'[-/\s]', groups[ymd])
|
||||
elif mdy is not None:
|
||||
m, d, y = re.split('[-/\s]', groups[mdy])
|
||||
m, d, y = re.split(r'[-/\s]', groups[mdy])
|
||||
elif dmy is not None:
|
||||
d, m, y = re.split('[-/\s]', groups[dmy])
|
||||
d, m, y = re.split(r'[-/\s]', groups[dmy])
|
||||
elif d_m_y is not None:
|
||||
d, m, y = d_m_y
|
||||
d = groups[d]
|
||||
@@ -636,10 +646,10 @@ class RepeatedNameError(ValueError):
|
||||
|
||||
# note: {} are handled separately
|
||||
# note: I don't use r'' here because Sublime Text 2 syntax highlight has a fit
|
||||
REGEX_SAFETY = re.compile('([?\\\\.[\]()*+\^$!\|])')
|
||||
REGEX_SAFETY = re.compile(r'([?\\\\.[\]()*+\^$!\|])')
|
||||
|
||||
# allowed field types
|
||||
ALLOWED_TYPES = set(list('nbox%fFegwWdDsS') +
|
||||
ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') +
|
||||
['t' + c for c in 'ieahgcts'])
|
||||
|
||||
|
||||
@@ -745,7 +755,7 @@ class Parser(object):
|
||||
@property
|
||||
def _match_re(self):
|
||||
if self.__match_re is None:
|
||||
expression = '^%s$' % self._expression
|
||||
expression = r'^%s$' % self._expression
|
||||
try:
|
||||
self.__match_re = re.compile(expression, self._re_flags)
|
||||
except AssertionError:
|
||||
@@ -923,16 +933,16 @@ class Parser(object):
|
||||
name, self._name_types[name]))
|
||||
group = self._name_to_group_map[name]
|
||||
# match previously-seen value
|
||||
return '(?P=%s)' % group
|
||||
return r'(?P=%s)' % group
|
||||
else:
|
||||
group = self._to_group_name(name)
|
||||
self._name_types[name] = format
|
||||
self._named_fields.append(group)
|
||||
# this will become a group, which must not contain dots
|
||||
wrap = '(?P<%s>%%s)' % group
|
||||
wrap = r'(?P<%s>%%s)' % group
|
||||
else:
|
||||
self._fixed_fields.append(self._group_index)
|
||||
wrap = '(%s)'
|
||||
wrap = r'(%s)'
|
||||
if ':' in field:
|
||||
format = field[1:]
|
||||
group = self._group_index
|
||||
@@ -940,7 +950,7 @@ class Parser(object):
|
||||
# simplest case: no type specifier ({} or {name})
|
||||
if not format:
|
||||
self._group_index += 1
|
||||
return wrap % '.+?'
|
||||
return wrap % r'.+?'
|
||||
|
||||
# decode the format specification
|
||||
format = extract_format(format, self._extra_types)
|
||||
@@ -960,19 +970,19 @@ class Parser(object):
|
||||
return type_converter(string)
|
||||
self._type_conversions[group] = f
|
||||
elif type == 'n':
|
||||
s = '\d{1,3}([,.]\d{3})*'
|
||||
s = r'\d{1,3}([,.]\d{3})*'
|
||||
self._group_index += 1
|
||||
self._type_conversions[group] = int_convert(10)
|
||||
elif type == 'b':
|
||||
s = '(0[bB])?[01]+'
|
||||
s = r'(0[bB])?[01]+'
|
||||
self._type_conversions[group] = int_convert(2)
|
||||
self._group_index += 1
|
||||
elif type == 'o':
|
||||
s = '(0[oO])?[0-7]+'
|
||||
s = r'(0[oO])?[0-7]+'
|
||||
self._type_conversions[group] = int_convert(8)
|
||||
self._group_index += 1
|
||||
elif type == 'x':
|
||||
s = '(0[xX])?[0-9a-fA-F]+'
|
||||
s = r'(0[xX])?[0-9a-fA-F]+'
|
||||
self._type_conversions[group] = int_convert(16)
|
||||
self._group_index += 1
|
||||
elif type == '%':
|
||||
@@ -994,10 +1004,10 @@ class Parser(object):
|
||||
self._type_conversions[group] = lambda s, m: float(s)
|
||||
elif type == 'd':
|
||||
if format.get('width'):
|
||||
width = '{1,%s}' % int(format['width'])
|
||||
width = r'{1,%s}' % int(format['width'])
|
||||
else:
|
||||
width = '+'
|
||||
s = '\\d{w}|0[xX][0-9a-fA-F]{w}|0[bB][01]{w}|0[oO][0-7]{w}'.format(w=width)
|
||||
s = r'\d{w}|0[xX][0-9a-fA-F]{w}|0[bB][01]{w}|0[oO][0-7]{w}'.format(w=width)
|
||||
self._type_conversions[group] = int_convert(10)
|
||||
elif type == 'ti':
|
||||
s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % \
|
||||
@@ -1055,18 +1065,19 @@ class Parser(object):
|
||||
self._type_conversions[group] = partial(date_convert, mm=n+1, dd=n+3,
|
||||
hms=n + 5)
|
||||
self._group_index += 5
|
||||
|
||||
elif type == 'l':
|
||||
s = r'[A-Za-z]+'
|
||||
elif type:
|
||||
s = r'\%s+' % type
|
||||
elif format.get('precision'):
|
||||
if format.get('width'):
|
||||
s = '.{%s,%s}?' % (format['width'], format['precision'])
|
||||
s = r'.{%s,%s}?' % (format['width'], format['precision'])
|
||||
else:
|
||||
s = '.{1,%s}?' % format['precision']
|
||||
s = r'.{1,%s}?' % format['precision']
|
||||
elif format.get('width'):
|
||||
s = '.{%s,}?' % format['width']
|
||||
s = r'.{%s,}?' % format['width']
|
||||
else:
|
||||
s = '.+?'
|
||||
s = r'.+?'
|
||||
|
||||
align = format['align']
|
||||
fill = format['fill']
|
||||
@@ -1079,7 +1090,7 @@ class Parser(object):
|
||||
# configurable fill defaulting to "0"
|
||||
if not fill:
|
||||
fill = '0'
|
||||
s = '%s*' % fill + s
|
||||
s = r'%s*' % fill + s
|
||||
|
||||
# allow numbers to be prefixed with a sign
|
||||
s = r'[-+ ]?' + s
|
||||
@@ -1101,7 +1112,7 @@ class Parser(object):
|
||||
if not align:
|
||||
align = '>'
|
||||
|
||||
if fill in '.\+?*[](){}^$':
|
||||
if fill in r'.\+?*[](){}^$':
|
||||
fill = '\\' + fill
|
||||
|
||||
# align "=" has been handled
|
||||
@@ -1118,8 +1129,11 @@ class Parser(object):
|
||||
class Result(object):
|
||||
'''The result of a parse() or search().
|
||||
|
||||
Fixed results may be looked up using result[index]. Named results may be
|
||||
looked up using result['name'].
|
||||
Fixed results may be looked up using `result[index]`.
|
||||
|
||||
Named results may be looked up using `result['name']`.
|
||||
|
||||
Named results may be tested for existence using `'name' in result`.
|
||||
'''
|
||||
def __init__(self, fixed, named, spans):
|
||||
self.fixed = fixed
|
||||
@@ -1135,6 +1149,9 @@ class Result(object):
|
||||
return '<%s %r %r>' % (self.__class__.__name__, self.fixed,
|
||||
self.named)
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self.named
|
||||
|
||||
|
||||
class Match(object):
|
||||
'''The result of a parse() or search() if no results are generated.
|
||||
@@ -1295,7 +1312,7 @@ def compile(format, extra_types=None, case_sensitive=False):
|
||||
return Parser(format, extra_types=extra_types)
|
||||
|
||||
|
||||
# Copyright (c) 2012-2013 Richard Jones <richard@python.org>
|
||||
# Copyright (c) 2012-2019 Richard Jones <richard@python.org>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
Vendored
+10
-3
@@ -22,7 +22,7 @@ import pkg_resources
|
||||
# from graphviz import backend, Digraph
|
||||
|
||||
|
||||
__version__ = '0.13.1'
|
||||
__version__ = '0.13.2'
|
||||
|
||||
|
||||
flatten = chain.from_iterable
|
||||
@@ -127,6 +127,13 @@ def guess_version(pkg_key, default='?'):
|
||||
return getattr(m, '__version__', default)
|
||||
|
||||
|
||||
def frozen_req_from_dist(dist):
|
||||
try:
|
||||
return FrozenRequirement.from_dist(dist)
|
||||
except TypeError:
|
||||
return FrozenRequirement.from_dist(dist, [])
|
||||
|
||||
|
||||
class Package(object):
|
||||
"""Abstract class for wrappers around objects that pip returns.
|
||||
|
||||
@@ -154,7 +161,7 @@ class Package(object):
|
||||
|
||||
@staticmethod
|
||||
def frozen_repr(obj):
|
||||
fr = FrozenRequirement.from_dist(obj, [])
|
||||
fr = frozen_req_from_dist(obj)
|
||||
return str(fr).strip()
|
||||
|
||||
def __getattr__(self, key):
|
||||
@@ -563,7 +570,7 @@ def _get_args():
|
||||
def main():
|
||||
args = _get_args()
|
||||
pkgs = get_installed_distributions(local_only=args.local_only,
|
||||
user_only=args.user_only)
|
||||
user_only=args.user_only)
|
||||
|
||||
dist_index = build_dist_index(pkgs)
|
||||
tree = construct_tree(dist_index)
|
||||
|
||||
+8
-5
@@ -1,16 +1,19 @@
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
__version__ = '1.1.11'
|
||||
|
||||
# Add NullHandler to "pythonfinder" logger, because Python2's default root
|
||||
# logger has no handler and warnings like this would be reported:
|
||||
#
|
||||
# > No handlers could be found for logger "pythonfinder.models.pyenv"
|
||||
import logging
|
||||
|
||||
from .exceptions import InvalidPythonVersion
|
||||
from .models import SystemPath, WindowsFinder
|
||||
from .pythonfinder import Finder
|
||||
|
||||
__version__ = "1.2.0"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(logging.NullHandler())
|
||||
|
||||
__all__ = ["Finder", "WindowsFinder", "SystemPath", "InvalidPythonVersion"]
|
||||
from .pythonfinder import Finder
|
||||
from .models import SystemPath, WindowsFinder
|
||||
from .exceptions import InvalidPythonVersion
|
||||
|
||||
Vendored
+1
-3
@@ -13,9 +13,7 @@ from .pythonfinder import Finder
|
||||
@click.command()
|
||||
@click.option("--find", default=False, nargs=1, help="Find a specific python version.")
|
||||
@click.option("--which", default=False, nargs=1, help="Run the which command.")
|
||||
@click.option(
|
||||
"--findall", is_flag=True, default=False, help="Find all python versions."
|
||||
)
|
||||
@click.option("--findall", is_flag=True, default=False, help="Find all python versions.")
|
||||
@click.option(
|
||||
"--version", is_flag=True, default=False, help="Display PythonFinder version."
|
||||
)
|
||||
|
||||
+3
@@ -36,6 +36,7 @@ else:
|
||||
IGNORE_UNSUPPORTED = bool(os.environ.get("PYTHONFINDER_IGNORE_UNSUPPORTED", False))
|
||||
MYPY_RUNNING = os.environ.get("MYPY_RUNNING", is_type_checking())
|
||||
|
||||
|
||||
def get_shim_paths():
|
||||
shim_paths = []
|
||||
if ASDF_INSTALLED:
|
||||
@@ -43,4 +44,6 @@ def get_shim_paths():
|
||||
if PYENV_INSTALLED:
|
||||
shim_paths.append(os.path.join(PYENV_ROOT, "shims"))
|
||||
return [os.path.normpath(os.path.normcase(p)) for p in shim_paths]
|
||||
|
||||
|
||||
SHIM_PATHS = get_shim_paths()
|
||||
|
||||
+19
-21
@@ -3,23 +3,23 @@ from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import abc
|
||||
import operator
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import attr
|
||||
import six
|
||||
|
||||
from cached_property import cached_property
|
||||
from vistir.compat import fs_str
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from ..utils import (
|
||||
KNOWN_EXTS, Sequence, expand_paths, looks_like_python,
|
||||
path_is_known_executable
|
||||
KNOWN_EXTS,
|
||||
Sequence,
|
||||
expand_paths,
|
||||
looks_like_python,
|
||||
path_is_known_executable,
|
||||
)
|
||||
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from .path import PathEntry
|
||||
from .python import PythonVersion
|
||||
@@ -48,7 +48,9 @@ class BasePath(object):
|
||||
only_python = attr.ib(default=False) # type: bool
|
||||
name = attr.ib(type=str)
|
||||
_py_version = attr.ib(default=None) # type: Optional[PythonVersion]
|
||||
_pythons = attr.ib(default=attr.Factory(defaultdict)) # type: DefaultDict[str, PathEntry]
|
||||
_pythons = attr.ib(
|
||||
default=attr.Factory(defaultdict)
|
||||
) # type: DefaultDict[str, PathEntry]
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
@@ -197,6 +199,7 @@ class BasePath(object):
|
||||
# type: () -> DefaultDict[Union[str, Path], PathEntry]
|
||||
if not self._pythons:
|
||||
from .path import PathEntry
|
||||
|
||||
self._pythons = defaultdict(PathEntry)
|
||||
for python in self._iter_pythons():
|
||||
python_path = python.path.as_posix() # type: ignore
|
||||
@@ -241,17 +244,13 @@ class BasePath(object):
|
||||
:rtype: List[:class:`~pythonfinder.models.PathEntry`]
|
||||
"""
|
||||
|
||||
call_method = (
|
||||
"find_all_python_versions" if self.is_dir else "find_python_version"
|
||||
)
|
||||
call_method = "find_all_python_versions" if self.is_dir else "find_python_version"
|
||||
sub_finder = operator.methodcaller(
|
||||
call_method, major, minor, patch, pre, dev, arch, name
|
||||
)
|
||||
if not self.is_dir:
|
||||
return sub_finder(self)
|
||||
unnested = [
|
||||
sub_finder(path) for path in expand_paths(self)
|
||||
]
|
||||
unnested = [sub_finder(path) for path in expand_paths(self)]
|
||||
version_sort = operator.attrgetter("as_python.version_sort")
|
||||
unnested = [p for p in unnested if p is not None and p.as_python is not None]
|
||||
paths = sorted(unnested, key=version_sort, reverse=True)
|
||||
@@ -291,13 +290,13 @@ class BasePath(object):
|
||||
matching_pythons = [
|
||||
[entry, entry.as_python.version_sort]
|
||||
for entry in self._iter_pythons()
|
||||
if (entry is not None and entry.as_python is not None and
|
||||
version_matcher(entry.py_version))
|
||||
if (
|
||||
entry is not None
|
||||
and entry.as_python is not None
|
||||
and version_matcher(entry.py_version)
|
||||
)
|
||||
]
|
||||
results = sorted(matching_pythons,
|
||||
key=operator.itemgetter(1, 0),
|
||||
reverse=True,
|
||||
)
|
||||
results = sorted(matching_pythons, key=operator.itemgetter(1, 0), reverse=True)
|
||||
return next(iter(r[0] for r in results if r is not None), None)
|
||||
|
||||
|
||||
@@ -316,9 +315,8 @@ class BaseFinder(object):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def create(cls, # type: Type[BaseFinderType]
|
||||
*args, # type: Any
|
||||
**kwargs # type: Any
|
||||
def create(
|
||||
cls, *args, **kwargs # type: Type[BaseFinderType] # type: Any # type: Any
|
||||
):
|
||||
# type: (...) -> BaseFinderType
|
||||
raise NotImplementedError
|
||||
|
||||
+87
-49
@@ -5,60 +5,90 @@ import copy
|
||||
import operator
|
||||
import os
|
||||
import sys
|
||||
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
|
||||
import attr
|
||||
import six
|
||||
|
||||
from cached_property import cached_property
|
||||
from vistir.compat import Path, fs_str
|
||||
|
||||
from .mixins import BaseFinder, BasePath
|
||||
from .python import PythonVersion
|
||||
from ..environment import (
|
||||
ASDF_DATA_DIR, ASDF_INSTALLED, MYPY_RUNNING, PYENV_INSTALLED, PYENV_ROOT,
|
||||
SHIM_PATHS
|
||||
ASDF_DATA_DIR,
|
||||
ASDF_INSTALLED,
|
||||
MYPY_RUNNING,
|
||||
PYENV_INSTALLED,
|
||||
PYENV_ROOT,
|
||||
SHIM_PATHS,
|
||||
)
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from ..utils import (
|
||||
Iterable, Sequence, ensure_path, expand_paths, filter_pythons, is_in_path,
|
||||
looks_like_python, normalize_path, optional_instance_of,
|
||||
parse_asdf_version_order, parse_pyenv_version_order,
|
||||
path_is_known_executable, unnest
|
||||
Iterable,
|
||||
Sequence,
|
||||
ensure_path,
|
||||
expand_paths,
|
||||
filter_pythons,
|
||||
is_in_path,
|
||||
looks_like_python,
|
||||
normalize_path,
|
||||
optional_instance_of,
|
||||
parse_asdf_version_order,
|
||||
parse_pyenv_version_order,
|
||||
path_is_known_executable,
|
||||
unnest,
|
||||
)
|
||||
from .mixins import BaseFinder, BasePath
|
||||
from .python import PythonVersion
|
||||
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import (
|
||||
Optional, Dict, DefaultDict, Iterator, List, Union, Tuple, Generator, Callable,
|
||||
Type, Any, TypeVar
|
||||
Optional,
|
||||
Dict,
|
||||
DefaultDict,
|
||||
Iterator,
|
||||
List,
|
||||
Union,
|
||||
Tuple,
|
||||
Generator,
|
||||
Callable,
|
||||
Type,
|
||||
Any,
|
||||
TypeVar,
|
||||
)
|
||||
from .mixins import BaseFinder
|
||||
from .python import PythonFinder
|
||||
from .windows import WindowsFinder
|
||||
FinderType = TypeVar('FinderType', BaseFinder, PythonFinder, WindowsFinder)
|
||||
ChildType = Union[PythonFinder, PathEntry]
|
||||
PathType = Union[PythonFinder, PathEntry]
|
||||
|
||||
FinderType = TypeVar("FinderType", BaseFinder, PythonFinder, WindowsFinder)
|
||||
ChildType = Union[PythonFinder, "PathEntry"]
|
||||
PathType = Union[PythonFinder, "PathEntry"]
|
||||
|
||||
|
||||
@attr.s
|
||||
class SystemPath(object):
|
||||
global_search = attr.ib(default=True)
|
||||
paths = attr.ib(default=attr.Factory(defaultdict)) # type: DefaultDict[str, Union[PythonFinder, PathEntry]]
|
||||
paths = attr.ib(
|
||||
default=attr.Factory(defaultdict)
|
||||
) # type: DefaultDict[str, Union[PythonFinder, PathEntry]]
|
||||
_executables = attr.ib(default=attr.Factory(list)) # type: List[PathEntry]
|
||||
_python_executables = attr.ib(default=attr.Factory(dict)) # type: Dict[str, PathEntry]
|
||||
_python_executables = attr.ib(
|
||||
default=attr.Factory(dict)
|
||||
) # type: Dict[str, PathEntry]
|
||||
path_order = attr.ib(default=attr.Factory(list)) # type: List[str]
|
||||
python_version_dict = attr.ib() # type: DefaultDict[Tuple, List[PythonVersion]]
|
||||
only_python = attr.ib(default=False, type=bool)
|
||||
pyenv_finder = attr.ib(default=None, validator=optional_instance_of("PythonFinder")) # type: Optional[PythonFinder]
|
||||
pyenv_finder = attr.ib(
|
||||
default=None, validator=optional_instance_of("PythonFinder")
|
||||
) # type: Optional[PythonFinder]
|
||||
asdf_finder = attr.ib(default=None) # type: Optional[PythonFinder]
|
||||
system = attr.ib(default=False, type=bool)
|
||||
_version_dict = attr.ib(default=attr.Factory(defaultdict)) # type: DefaultDict[Tuple, List[PathEntry]]
|
||||
_version_dict = attr.ib(
|
||||
default=attr.Factory(defaultdict)
|
||||
) # type: DefaultDict[Tuple, List[PathEntry]]
|
||||
ignore_unsupported = attr.ib(default=False, type=bool)
|
||||
|
||||
__finders = attr.ib(default=attr.Factory(dict)) # type: Dict[str, Union[WindowsFinder, PythonFinder]]
|
||||
__finders = attr.ib(
|
||||
default=attr.Factory(dict)
|
||||
) # type: Dict[str, Union[WindowsFinder, PythonFinder]]
|
||||
|
||||
def _register_finder(self, finder_name, finder):
|
||||
# type: (str, Union[WindowsFinder, PythonFinder]) -> None
|
||||
@@ -117,7 +147,9 @@ class SystemPath(object):
|
||||
@cached_property
|
||||
def version_dict(self):
|
||||
# type: () -> DefaultDict[Tuple, List[PathEntry]]
|
||||
self._version_dict = defaultdict(list) # type: DefaultDict[Tuple, List[PathEntry]]
|
||||
self._version_dict = defaultdict(
|
||||
list
|
||||
) # type: DefaultDict[Tuple, List[PathEntry]]
|
||||
for finder_name, finder in self.__finders.items():
|
||||
for version, entry in finder.versions.items():
|
||||
if finder_name == "windows":
|
||||
@@ -171,9 +203,7 @@ class SystemPath(object):
|
||||
reversed_paths = reversed(self.path_order)
|
||||
paths = [normalize_path(p) for p in reversed_paths]
|
||||
normalized_target = normalize_path(path)
|
||||
last_instance = next(
|
||||
iter(p for p in paths if normalized_target in p), None
|
||||
)
|
||||
last_instance = next(iter(p for p in paths if normalized_target in p), None)
|
||||
if last_instance is None:
|
||||
raise ValueError("No instance found on path for target: {0!s}".format(path))
|
||||
path_index = self.path_order.index(last_instance)
|
||||
@@ -190,19 +220,14 @@ class SystemPath(object):
|
||||
else:
|
||||
before_path = self.path_order[: start_idx + 1]
|
||||
after_path = self.path_order[start_idx + 2 :]
|
||||
self.path_order = (
|
||||
before_path + [p.as_posix() for p in paths] + after_path
|
||||
)
|
||||
self.path_order = before_path + [p.as_posix() for p in paths] + after_path
|
||||
|
||||
def _remove_path(self, path):
|
||||
# type: (str) -> None
|
||||
path_copy = [p for p in reversed(self.path_order[:])]
|
||||
new_order = []
|
||||
target = normalize_path(path)
|
||||
path_map = {
|
||||
normalize_path(pth): pth
|
||||
for pth in self.paths.keys()
|
||||
}
|
||||
path_map = {normalize_path(pth): pth for pth in self.paths.keys()}
|
||||
if target in path_map:
|
||||
del self.paths[path_map[target]]
|
||||
for current_path in path_copy:
|
||||
@@ -215,10 +240,14 @@ class SystemPath(object):
|
||||
def _setup_asdf(self):
|
||||
# type: () -> None
|
||||
from .python import PythonFinder
|
||||
|
||||
os_path = os.environ["PATH"].split(os.pathsep)
|
||||
self.asdf_finder = PythonFinder.create(
|
||||
root=ASDF_DATA_DIR, ignore_unsupported=True,
|
||||
sort_function=parse_asdf_version_order, version_glob_path="installs/python/*")
|
||||
root=ASDF_DATA_DIR,
|
||||
ignore_unsupported=True,
|
||||
sort_function=parse_asdf_version_order,
|
||||
version_glob_path="installs/python/*",
|
||||
)
|
||||
asdf_index = None
|
||||
try:
|
||||
asdf_index = self._get_last_instance(ASDF_DATA_DIR)
|
||||
@@ -253,7 +282,9 @@ class SystemPath(object):
|
||||
# TODO: This is called 'reload', should we load a new finder for the first
|
||||
# time here? lets just skip that for now to avoid unallowed finders
|
||||
pass
|
||||
if (finder_name == "pyenv" and not PYENV_INSTALLED) or (finder_name == "asdf" and not ASDF_INSTALLED):
|
||||
if (finder_name == "pyenv" and not PYENV_INSTALLED) or (
|
||||
finder_name == "asdf" and not ASDF_INSTALLED
|
||||
):
|
||||
# Don't allow loading of finders that aren't explicitly 'installed' as it were
|
||||
pass
|
||||
setattr(self, finder_attr, None)
|
||||
@@ -264,10 +295,15 @@ class SystemPath(object):
|
||||
def _setup_pyenv(self):
|
||||
# type: () -> None
|
||||
from .python import PythonFinder
|
||||
|
||||
os_path = os.environ["PATH"].split(os.pathsep)
|
||||
|
||||
self.pyenv_finder = PythonFinder.create(
|
||||
root=PYENV_ROOT, sort_function=parse_pyenv_version_order, version_glob_path="versions/*", ignore_unsupported=self.ignore_unsupported)
|
||||
root=PYENV_ROOT,
|
||||
sort_function=parse_pyenv_version_order,
|
||||
version_glob_path="versions/*",
|
||||
ignore_unsupported=self.ignore_unsupported,
|
||||
)
|
||||
pyenv_index = None
|
||||
try:
|
||||
pyenv_index = self._get_last_instance(PYENV_ROOT)
|
||||
@@ -469,8 +505,7 @@ class SystemPath(object):
|
||||
name = "{0!s}".format(major)
|
||||
major = None
|
||||
sub_finder = operator.methodcaller(
|
||||
"find_python_version",
|
||||
major, minor, patch, pre, dev, arch, name,
|
||||
"find_python_version", major, minor, patch, pre, dev, arch, name
|
||||
)
|
||||
alternate_sub_finder = None
|
||||
if name and not (minor or patch or pre or dev or arch or major):
|
||||
@@ -517,7 +552,9 @@ class SystemPath(object):
|
||||
:rtype: :class:`pythonfinder.models.SystemPath`
|
||||
"""
|
||||
|
||||
path_entries = defaultdict(PathEntry) # type: DefaultDict[str, Union[PythonFinder, PathEntry]]
|
||||
path_entries = defaultdict(
|
||||
PathEntry
|
||||
) # type: DefaultDict[str, Union[PythonFinder, PathEntry]]
|
||||
paths = [] # type: List[str]
|
||||
if ignore_unsupported:
|
||||
os.environ["PYTHONFINDER_IGNORE_UNSUPPORTED"] = fs_str("1")
|
||||
@@ -567,6 +604,7 @@ class PathEntry(BasePath):
|
||||
def _gen_children(self):
|
||||
# type: () -> Iterator
|
||||
from ..environment import get_shim_paths
|
||||
|
||||
shim_paths = get_shim_paths()
|
||||
pass_name = self.name != self.path.name
|
||||
pass_args = {"is_root": False, "only_python": self.only_python}
|
||||
@@ -602,7 +640,6 @@ class PathEntry(BasePath):
|
||||
self._children = children
|
||||
return self._children
|
||||
|
||||
|
||||
@classmethod
|
||||
def create(cls, path, is_root=False, only_python=False, pythons=None, name=None):
|
||||
# type: (Union[str, Path], bool, bool, Dict[str, PythonVersion], Optional[str]) -> PathEntry
|
||||
@@ -622,16 +659,18 @@ class PathEntry(BasePath):
|
||||
if not name:
|
||||
guessed_name = True
|
||||
name = target.name
|
||||
creation_args = {"path": target, "is_root": is_root, "only_python": only_python, "name": name}
|
||||
creation_args = {
|
||||
"path": target,
|
||||
"is_root": is_root,
|
||||
"only_python": only_python,
|
||||
"name": name,
|
||||
}
|
||||
if pythons:
|
||||
creation_args["pythons"] = pythons
|
||||
_new = cls(**creation_args)
|
||||
if pythons and only_python:
|
||||
children = {}
|
||||
child_creation_args = {
|
||||
"is_root": False,
|
||||
"only_python": only_python
|
||||
}
|
||||
child_creation_args = {"is_root": False, "only_python": only_python}
|
||||
if not guessed_name:
|
||||
child_creation_args["name"] = _new.name # type: ignore
|
||||
for pth, python in pythons.items():
|
||||
@@ -639,9 +678,7 @@ class PathEntry(BasePath):
|
||||
continue
|
||||
pth = ensure_path(pth)
|
||||
children[pth.as_posix()] = PathEntry( # type: ignore
|
||||
py_version=python,
|
||||
path=pth,
|
||||
**child_creation_args
|
||||
py_version=python, path=pth, **child_creation_args
|
||||
)
|
||||
_new._children = children
|
||||
return _new
|
||||
@@ -658,6 +695,7 @@ class VersionPath(SystemPath):
|
||||
|
||||
Generates the version listings for it"""
|
||||
from .path import PathEntry
|
||||
|
||||
path = ensure_path(path)
|
||||
path_entries = defaultdict(PathEntry)
|
||||
bin_ = "{base}/bin"
|
||||
|
||||
+68
-34
@@ -6,29 +6,44 @@ import logging
|
||||
import operator
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import attr
|
||||
import six
|
||||
|
||||
from packaging.version import Version
|
||||
from vistir.compat import Path, lru_cache
|
||||
|
||||
from .mixins import BaseFinder, BasePath
|
||||
from ..environment import ASDF_DATA_DIR, MYPY_RUNNING, PYENV_ROOT, SYSTEM_ARCH
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from ..utils import (
|
||||
RE_MATCHER, _filter_none, ensure_path, get_python_version, is_in_path,
|
||||
looks_like_python, optional_instance_of, parse_asdf_version_order,
|
||||
parse_pyenv_version_order, parse_python_version, unnest
|
||||
RE_MATCHER,
|
||||
_filter_none,
|
||||
ensure_path,
|
||||
get_python_version,
|
||||
is_in_path,
|
||||
looks_like_python,
|
||||
optional_instance_of,
|
||||
parse_asdf_version_order,
|
||||
parse_pyenv_version_order,
|
||||
parse_python_version,
|
||||
unnest,
|
||||
)
|
||||
from .mixins import BaseFinder, BasePath
|
||||
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import (
|
||||
DefaultDict, Optional, Callable, Generator, Any, Union, Tuple, List, Dict, Type,
|
||||
TypeVar, Iterator
|
||||
DefaultDict,
|
||||
Optional,
|
||||
Callable,
|
||||
Generator,
|
||||
Any,
|
||||
Union,
|
||||
Tuple,
|
||||
List,
|
||||
Dict,
|
||||
Type,
|
||||
TypeVar,
|
||||
Iterator,
|
||||
)
|
||||
from .path import PathEntry
|
||||
from .._vendor.pep514tools.environment import Environment
|
||||
@@ -68,8 +83,7 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
def expanded_paths(self):
|
||||
# type: () -> Generator
|
||||
return (
|
||||
path for path in unnest(p for p in self.versions.values())
|
||||
if path is not None
|
||||
path for path in unnest(p for p in self.versions.values()) if path is not None
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -85,15 +99,20 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
def get_version_order(self):
|
||||
# type: () -> List[Path]
|
||||
version_paths = [
|
||||
p for p in self.root.glob(self.version_glob_path)
|
||||
p
|
||||
for p in self.root.glob(self.version_glob_path)
|
||||
if not (p.parent.name == "envs" or p.name == "envs")
|
||||
]
|
||||
versions = {v.name: v for v in version_paths}
|
||||
version_order = [] # type: List[Path]
|
||||
if self.is_pyenv:
|
||||
version_order = [versions[v] for v in parse_pyenv_version_order() if v in versions]
|
||||
version_order = [
|
||||
versions[v] for v in parse_pyenv_version_order() if v in versions
|
||||
]
|
||||
elif self.is_asdf:
|
||||
version_order = [versions[v] for v in parse_asdf_version_order() if v in versions]
|
||||
version_order = [
|
||||
versions[v] for v in parse_asdf_version_order() if v in versions
|
||||
]
|
||||
for version in version_order:
|
||||
version_paths.remove(version)
|
||||
if version_order:
|
||||
@@ -118,12 +137,12 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
def _iter_version_bases(self):
|
||||
# type: () -> Iterator[Tuple[Path, PathEntry]]
|
||||
from .path import PathEntry
|
||||
|
||||
for p in self.get_version_order():
|
||||
bin_dir = self.get_bin_dir(p)
|
||||
if bin_dir.exists() and bin_dir.is_dir():
|
||||
entry = PathEntry.create(
|
||||
path=bin_dir.absolute(), only_python=False, name=p.name,
|
||||
is_root=True
|
||||
path=bin_dir.absolute(), only_python=False, name=p.name, is_root=True
|
||||
)
|
||||
self.roots[p] = entry
|
||||
yield (p, entry)
|
||||
@@ -146,8 +165,11 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
except Exception:
|
||||
if not self.ignore_unsupported:
|
||||
raise
|
||||
logger.warning("Unsupported Python version %r, ignoring...",
|
||||
base_path.name, exc_info=True)
|
||||
logger.warning(
|
||||
"Unsupported Python version %r, ignoring...",
|
||||
base_path.name,
|
||||
exc_info=True,
|
||||
)
|
||||
continue
|
||||
if version is not None:
|
||||
version_tuple = (
|
||||
@@ -190,6 +212,7 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
# type: () -> DefaultDict[str, PathEntry]
|
||||
if not self._pythons:
|
||||
from .path import PathEntry
|
||||
|
||||
self._pythons = defaultdict(PathEntry) # type: DefaultDict[str, PathEntry]
|
||||
for python in self._iter_pythons():
|
||||
python_path = python.path.as_posix() # type: ignore
|
||||
@@ -206,13 +229,20 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
return self.pythons
|
||||
|
||||
@classmethod
|
||||
def create(cls, root, sort_function, version_glob_path=None, ignore_unsupported=True): # type: ignore
|
||||
def create(
|
||||
cls, root, sort_function, version_glob_path=None, ignore_unsupported=True
|
||||
): # type: ignore
|
||||
# type: (Type[PythonFinder], str, Callable, Optional[str], bool) -> PythonFinder
|
||||
root = ensure_path(root)
|
||||
if not version_glob_path:
|
||||
version_glob_path = "versions/*"
|
||||
return cls(root=root, path=root, ignore_unsupported=ignore_unsupported, # type: ignore
|
||||
sort_function=sort_function, version_glob_path=version_glob_path)
|
||||
return cls(
|
||||
root=root,
|
||||
path=root,
|
||||
ignore_unsupported=ignore_unsupported, # type: ignore
|
||||
sort_function=sort_function,
|
||||
version_glob_path=version_glob_path,
|
||||
)
|
||||
|
||||
def find_all_python_versions(
|
||||
self,
|
||||
@@ -239,9 +269,7 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
:rtype: List[:class:`~pythonfinder.models.PathEntry`]
|
||||
"""
|
||||
|
||||
call_method = (
|
||||
"find_all_python_versions" if self.is_dir else "find_python_version"
|
||||
)
|
||||
call_method = "find_all_python_versions" if self.is_dir else "find_python_version"
|
||||
sub_finder = operator.methodcaller(
|
||||
call_method, major, minor, patch, pre, dev, arch, name
|
||||
)
|
||||
@@ -251,13 +279,12 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
for _, base in self._iter_version_bases()
|
||||
]
|
||||
else:
|
||||
pythons = [
|
||||
sub_finder(path) for path in self.paths
|
||||
]
|
||||
pythons = [sub_finder(path) for path in self.paths]
|
||||
pythons = [p for p in pythons if p and p.is_python and p.as_python is not None]
|
||||
version_sort = operator.attrgetter("as_python.version_sort")
|
||||
paths = [
|
||||
p for p in sorted(list(pythons), key=version_sort, reverse=True)
|
||||
p
|
||||
for p in sorted(list(pythons), key=version_sort, reverse=True)
|
||||
if p is not None
|
||||
]
|
||||
return paths
|
||||
@@ -292,7 +319,8 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
version_sort = operator.attrgetter("as_python.version_sort")
|
||||
unnested = [sub_finder(self.roots[path]) for path in self.roots]
|
||||
unnested = [
|
||||
p for p in unnested
|
||||
p
|
||||
for p in unnested
|
||||
if p is not None and p.is_python and p.as_python is not None
|
||||
]
|
||||
paths = sorted(list(unnested), key=version_sort, reverse=True)
|
||||
@@ -527,6 +555,7 @@ class PythonVersion(object):
|
||||
if not isinstance(path, PathEntry):
|
||||
path = PathEntry.create(path, is_root=False, only_python=True, name=name)
|
||||
from ..environment import IGNORE_UNSUPPORTED
|
||||
|
||||
ignore_unsupported = ignore_unsupported or IGNORE_UNSUPPORTED
|
||||
path_name = getattr(path, "name", path.path.name) # str
|
||||
if not path.is_python:
|
||||
@@ -540,7 +569,10 @@ class PythonVersion(object):
|
||||
if instance_dict.get("minor") is None and looks_like_python(path.path.name):
|
||||
instance_dict = cls.parse_executable(path.path.absolute().as_posix())
|
||||
|
||||
if not isinstance(instance_dict.get("version"), Version) and not ignore_unsupported:
|
||||
if (
|
||||
not isinstance(instance_dict.get("version"), Version)
|
||||
and not ignore_unsupported
|
||||
):
|
||||
raise ValueError("Not a valid python path: %s" % path)
|
||||
if instance_dict.get("patch") is None:
|
||||
instance_dict = cls.parse_executable(path.path.absolute().as_posix())
|
||||
@@ -596,7 +628,7 @@ class PythonVersion(object):
|
||||
launcher_entry.info, "sys_architecture", SYSTEM_ARCH
|
||||
),
|
||||
"executable": exe_path,
|
||||
"name": name
|
||||
"name": name,
|
||||
}
|
||||
)
|
||||
py_version = cls.create(**creation_dict)
|
||||
@@ -616,7 +648,9 @@ class PythonVersion(object):
|
||||
|
||||
@attr.s
|
||||
class VersionMap(object):
|
||||
versions = attr.ib(factory=defaultdict) # type: DefaultDict[Tuple[int, Optional[int], Optional[int], bool, bool, bool], List[PathEntry]]
|
||||
versions = attr.ib(
|
||||
factory=defaultdict
|
||||
) # type: DefaultDict[Tuple[int, Optional[int], Optional[int], bool, bool, bool], List[PathEntry]]
|
||||
|
||||
def add_entry(self, entry):
|
||||
# type: (...) -> None
|
||||
@@ -634,8 +668,8 @@ class VersionMap(object):
|
||||
self.versions[version] = entries
|
||||
else:
|
||||
current_entries = {
|
||||
p.path for p in
|
||||
self.versions[version] # type: ignore
|
||||
p.path
|
||||
for p in self.versions[version] # type: ignore
|
||||
if version in self.versions
|
||||
}
|
||||
new_entries = {p.path for p in entries}
|
||||
|
||||
+26
-23
@@ -2,22 +2,21 @@
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import operator
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import attr
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from ..utils import ensure_path
|
||||
from .mixins import BaseFinder
|
||||
from .path import PathEntry
|
||||
from .python import PythonVersion, VersionMap
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from ..utils import ensure_path
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import DefaultDict, Tuple, List, Optional, Union, TypeVar, Type, Any
|
||||
FinderType = TypeVar('FinderType')
|
||||
|
||||
FinderType = TypeVar("FinderType")
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -41,9 +40,7 @@ class WindowsFinder(BaseFinder):
|
||||
version_matcher = operator.methodcaller(
|
||||
"matches", major, minor, patch, pre, dev, arch, python_name=name
|
||||
)
|
||||
pythons = [
|
||||
py for py in self.version_list if version_matcher(py)
|
||||
]
|
||||
pythons = [py for py in self.version_list if version_matcher(py)]
|
||||
version_sort = operator.attrgetter("version_sort")
|
||||
return [c.comes_from for c in sorted(pythons, key=version_sort, reverse=True)]
|
||||
|
||||
@@ -58,15 +55,20 @@ class WindowsFinder(BaseFinder):
|
||||
name=None, # type: Optional[str]
|
||||
):
|
||||
# type: (...) -> Optional[PathEntry]
|
||||
return next(iter(v for v in self.find_all_python_versions(
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
pre=pre,
|
||||
dev=dev,
|
||||
arch=arch,
|
||||
name=name,
|
||||
)), None
|
||||
return next(
|
||||
iter(
|
||||
v
|
||||
for v in self.find_all_python_versions(
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
pre=pre,
|
||||
dev=dev,
|
||||
arch=arch,
|
||||
name=name,
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
@_versions.default
|
||||
@@ -92,13 +94,14 @@ class WindowsFinder(BaseFinder):
|
||||
if py_version is None:
|
||||
continue
|
||||
self.version_list.append(py_version)
|
||||
python_path = py_version.comes_from.path if py_version.comes_from else py_version.executable
|
||||
python_path = (
|
||||
py_version.comes_from.path
|
||||
if py_version.comes_from
|
||||
else py_version.executable
|
||||
)
|
||||
python_kwargs = {python_path: py_version} if python_path is not None else {}
|
||||
base_dir = PathEntry.create(
|
||||
path,
|
||||
is_root=True,
|
||||
only_python=True,
|
||||
pythons=python_kwargs,
|
||||
path, is_root=True, only_python=True, pythons=python_kwargs
|
||||
)
|
||||
versions[py_version.version_tuple[:5]] = base_dir
|
||||
self.paths.append(base_dir)
|
||||
|
||||
+31
-18
@@ -5,16 +5,14 @@ import operator
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
from click import secho
|
||||
from vistir.compat import lru_cache
|
||||
|
||||
from . import environment
|
||||
from .exceptions import InvalidPythonVersion
|
||||
from .models import path
|
||||
from .models import path as pyfinder_path
|
||||
from .utils import Iterable, filter_pythons, version_re
|
||||
|
||||
|
||||
if environment.MYPY_RUNNING:
|
||||
from typing import Optional, Dict, Any, Union, List, Iterator
|
||||
from .models.path import Path, PathEntry
|
||||
@@ -34,7 +32,9 @@ class Finder(object):
|
||||
*path* and *system*.
|
||||
"""
|
||||
|
||||
def __init__(self, path=None, system=False, global_search=True, ignore_unsupported=True):
|
||||
def __init__(
|
||||
self, path=None, system=False, global_search=True, ignore_unsupported=True
|
||||
):
|
||||
# type: (Optional[str], bool, bool, bool) -> None
|
||||
"""Create a new :class:`~pythonfinder.pythonfinder.Finder` instance.
|
||||
|
||||
@@ -68,9 +68,11 @@ class Finder(object):
|
||||
|
||||
def create_system_path(self):
|
||||
# type: () -> SystemPath
|
||||
return path.SystemPath.create(
|
||||
path=self.path_prepend, system=self.system, global_search=self.global_search,
|
||||
ignore_unsupported=self.ignore_unsupported
|
||||
return pyfinder_path.SystemPath.create(
|
||||
path=self.path_prepend,
|
||||
system=self.system,
|
||||
global_search=self.global_search,
|
||||
ignore_unsupported=self.ignore_unsupported,
|
||||
)
|
||||
|
||||
def reload_system_path(self):
|
||||
@@ -84,7 +86,7 @@ class Finder(object):
|
||||
if self._system_path is not None:
|
||||
self._system_path.clear_caches()
|
||||
self._system_path = None
|
||||
six.moves.reload_module(path)
|
||||
six.moves.reload_module(pyfinder_path)
|
||||
self._system_path = self.create_system_path()
|
||||
|
||||
def rehash(self):
|
||||
@@ -136,12 +138,13 @@ class Finder(object):
|
||||
"""
|
||||
|
||||
from .models import PythonVersion
|
||||
|
||||
minor = int(minor) if minor is not None else minor
|
||||
patch = int(patch) if patch is not None else patch
|
||||
|
||||
version_dict = {
|
||||
"minor": minor,
|
||||
"patch": patch
|
||||
"patch": patch,
|
||||
} # type: Dict[str, Union[str, int, Any]]
|
||||
|
||||
if (
|
||||
@@ -177,7 +180,9 @@ class Finder(object):
|
||||
if "." in major and all(part.isdigit() for part in major.split(".")[:2]):
|
||||
match = version_re.match(major)
|
||||
version_dict = match.groupdict()
|
||||
version_dict["is_prerelease"] = bool(version_dict.get("prerel", False))
|
||||
version_dict["is_prerelease"] = bool(
|
||||
version_dict.get("prerel", False)
|
||||
)
|
||||
version_dict["is_devrelease"] = bool(version_dict.get("dev", False))
|
||||
else:
|
||||
version_dict = {
|
||||
@@ -186,7 +191,7 @@ class Finder(object):
|
||||
"patch": patch,
|
||||
"pre": pre,
|
||||
"dev": dev,
|
||||
"arch": arch
|
||||
"arch": arch,
|
||||
}
|
||||
if version_dict.get("minor") is not None:
|
||||
minor = int(version_dict["minor"])
|
||||
@@ -198,10 +203,18 @@ class Finder(object):
|
||||
pre = bool(_pre) if _pre is not None else pre
|
||||
_dev = version_dict.get("is_devrelease", dev)
|
||||
dev = bool(_dev) if _dev is not None else dev
|
||||
arch = version_dict.get("architecture", None) if arch is None else arch # type: ignore
|
||||
arch = (
|
||||
version_dict.get("architecture", None) if arch is None else arch
|
||||
) # type: ignore
|
||||
if os.name == "nt" and self.windows_finder is not None:
|
||||
match = self.windows_finder.find_python_version(
|
||||
major=major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch, name=name
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
pre=pre,
|
||||
dev=dev,
|
||||
arch=arch,
|
||||
name=name,
|
||||
)
|
||||
if match:
|
||||
return match
|
||||
@@ -218,10 +231,10 @@ class Finder(object):
|
||||
python_version_dict = getattr(self.system_path, "python_version_dict")
|
||||
if python_version_dict:
|
||||
paths = (
|
||||
path
|
||||
for version in python_version_dict.values()
|
||||
for path in version
|
||||
if path is not None and path.as_python
|
||||
path
|
||||
for version in python_version_dict.values()
|
||||
for path in version
|
||||
if path is not None and path.as_python
|
||||
)
|
||||
path_list = sorted(paths, key=version_sort, reverse=True)
|
||||
return path_list
|
||||
@@ -229,7 +242,7 @@ class Finder(object):
|
||||
major=major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch, name=name
|
||||
)
|
||||
if not isinstance(versions, Iterable):
|
||||
versions = [versions,]
|
||||
versions = [versions]
|
||||
path_list = sorted(versions, key=version_sort, reverse=True)
|
||||
path_map = {} # type: Dict[str, PathEntry]
|
||||
for path in path_list:
|
||||
|
||||
Vendored
+61
-34
@@ -5,23 +5,26 @@ import io
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
|
||||
from fnmatch import fnmatch
|
||||
|
||||
import attr
|
||||
import six
|
||||
import vistir
|
||||
|
||||
from packaging.version import LegacyVersion, Version
|
||||
|
||||
from .environment import MYPY_RUNNING, PYENV_ROOT
|
||||
from .exceptions import InvalidPythonVersion
|
||||
|
||||
|
||||
six.add_move(six.MovedAttribute("Iterable", "collections", "collections.abc")) # type: ignore # noqa
|
||||
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc")) # type: ignore # noqa
|
||||
from six.moves import Iterable # type: ignore # noqa
|
||||
from six.moves import Sequence # type: ignore # noqa
|
||||
six.add_move(
|
||||
six.MovedAttribute("Iterable", "collections", "collections.abc")
|
||||
) # type: ignore # noqa
|
||||
six.add_move(
|
||||
six.MovedAttribute("Sequence", "collections", "collections.abc")
|
||||
) # type: ignore # noqa
|
||||
# fmt: off
|
||||
from six.moves import Iterable # type: ignore # noqa # isort:skip
|
||||
from six.moves import Sequence # type: ignore # noqa # isort:skip
|
||||
# fmt: on
|
||||
|
||||
try:
|
||||
from functools import lru_cache
|
||||
@@ -29,27 +32,42 @@ except ImportError:
|
||||
from backports.functools_lru_cache import lru_cache # type: ignore # noqa
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import (
|
||||
Any, Union, List, Callable, Iterable, Set, Tuple, Dict, Optional, Iterator
|
||||
)
|
||||
from typing import Any, Union, List, Callable, Set, Tuple, Dict, Optional, Iterator
|
||||
from attr.validators import _OptionalValidator # type: ignore
|
||||
from .models.path import PathEntry
|
||||
|
||||
|
||||
version_re = re.compile(r"(?P<major>\d+)(?:\.(?P<minor>\d+))?(?:\.(?P<patch>(?<=\.)[0-9]+))?\.?"
|
||||
r"(?:(?P<prerel>[abc]|rc|dev)(?:(?P<prerelversion>\d+(?:\.\d+)*))?)"
|
||||
r"?(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?")
|
||||
version_re = re.compile(
|
||||
r"(?P<major>\d+)(?:\.(?P<minor>\d+))?(?:\.(?P<patch>(?<=\.)[0-9]+))?\.?"
|
||||
r"(?:(?P<prerel>[abc]|rc|dev)(?:(?P<prerelversion>\d+(?:\.\d+)*))?)"
|
||||
r"?(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?"
|
||||
)
|
||||
|
||||
|
||||
PYTHON_IMPLEMENTATIONS = (
|
||||
"python", "ironpython", "jython", "pypy", "anaconda", "miniconda",
|
||||
"stackless", "activepython", "micropython"
|
||||
"python",
|
||||
"ironpython",
|
||||
"jython",
|
||||
"pypy",
|
||||
"anaconda",
|
||||
"miniconda",
|
||||
"stackless",
|
||||
"activepython",
|
||||
"micropython",
|
||||
)
|
||||
RE_MATCHER = re.compile(
|
||||
r"(({0})(?:\d?(?:\.\d[cpm]{{0,3}}))?(?:-?[\d\.]+)*[^z])".format(
|
||||
"|".join(PYTHON_IMPLEMENTATIONS)
|
||||
)
|
||||
)
|
||||
RE_MATCHER = re.compile(r"(({0})(?:\d?(?:\.\d[cpm]{{0,3}}))?(?:-?[\d\.]+)*[^z])".format(
|
||||
"|".join(PYTHON_IMPLEMENTATIONS)
|
||||
))
|
||||
RULES_BASE = [
|
||||
"*{0}", "*{0}?", "*{0}?.?", "*{0}?.?m", "{0}?-?.?", "{0}?-?.?.?", "{0}?.?-?.?.?"
|
||||
"*{0}",
|
||||
"*{0}?",
|
||||
"*{0}?.?",
|
||||
"*{0}?.?m",
|
||||
"{0}?-?.?",
|
||||
"{0}?-?.?.?",
|
||||
"{0}?.?-?.?.?",
|
||||
]
|
||||
RULES = [rule.format(impl) for impl in PYTHON_IMPLEMENTATIONS for rule in RULES_BASE]
|
||||
|
||||
@@ -61,10 +79,7 @@ KNOWN_EXTS = KNOWN_EXTS | set(
|
||||
MATCH_RULES = []
|
||||
for rule in RULES:
|
||||
MATCH_RULES.extend(
|
||||
[
|
||||
"{0}.{1}".format(rule, ext) if ext else "{0}".format(rule)
|
||||
for ext in KNOWN_EXTS
|
||||
]
|
||||
["{0}.{1}".format(rule, ext) if ext else "{0}".format(rule) for ext in KNOWN_EXTS]
|
||||
)
|
||||
|
||||
|
||||
@@ -74,8 +89,14 @@ def get_python_version(path):
|
||||
"""Get python version string using subprocess from a given path."""
|
||||
version_cmd = [path, "-c", "import sys; print(sys.version.split()[0])"]
|
||||
try:
|
||||
c = vistir.misc.run(version_cmd, block=True, nospin=True, return_object=True,
|
||||
combine_stderr=False, write_to_stdout=False)
|
||||
c = vistir.misc.run(
|
||||
version_cmd,
|
||||
block=True,
|
||||
nospin=True,
|
||||
return_object=True,
|
||||
combine_stderr=False,
|
||||
write_to_stdout=False,
|
||||
)
|
||||
except OSError:
|
||||
raise InvalidPythonVersion("%s is not a valid python path" % path)
|
||||
if not c.out:
|
||||
@@ -87,6 +108,7 @@ def get_python_version(path):
|
||||
def parse_python_version(version_str):
|
||||
# type: (str) -> Dict[str, Union[str, int, Version]]
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
is_debug = False
|
||||
if version_str.endswith("-debug"):
|
||||
is_debug = True
|
||||
@@ -127,7 +149,7 @@ def parse_python_version(version_str):
|
||||
"is_prerelease": is_prerelease,
|
||||
"is_devrelease": is_devrelease,
|
||||
"is_debug": is_debug,
|
||||
"version": version
|
||||
"version": version,
|
||||
}
|
||||
|
||||
|
||||
@@ -237,9 +259,11 @@ def _filter_none(k, v):
|
||||
# TODO: Reimplement in vistir
|
||||
def normalize_path(path):
|
||||
# type: (str) -> str
|
||||
return os.path.normpath(os.path.normcase(
|
||||
os.path.abspath(os.path.expandvars(os.path.expanduser(str(path))))
|
||||
))
|
||||
return os.path.normpath(
|
||||
os.path.normcase(
|
||||
os.path.abspath(os.path.expandvars(os.path.expanduser(str(path))))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
@@ -290,9 +314,10 @@ def parse_asdf_version_order(filename=".tool-versions"):
|
||||
if os.path.exists(version_order_file) and os.path.isfile(version_order_file):
|
||||
with io.open(version_order_file, encoding="utf-8") as fh:
|
||||
contents = fh.read()
|
||||
python_section = next(iter(
|
||||
line for line in contents.splitlines() if line.startswith("python")
|
||||
), None)
|
||||
python_section = next(
|
||||
iter(line for line in contents.splitlines() if line.startswith("python")),
|
||||
None,
|
||||
)
|
||||
if python_section:
|
||||
# python_key, _, versions
|
||||
_, _, versions = python_section.partition(" ")
|
||||
@@ -317,8 +342,10 @@ def expand_paths(path, only_python=True):
|
||||
:rtype: Iterator[PathEntry]
|
||||
"""
|
||||
|
||||
if path is not None and (isinstance(path, Sequence) and
|
||||
not getattr(path.__class__, "__name__", "") == "PathEntry"):
|
||||
if path is not None and (
|
||||
isinstance(path, Sequence)
|
||||
and not getattr(path.__class__, "__name__", "") == "PathEntry"
|
||||
):
|
||||
for p in unnest(path):
|
||||
if p is None:
|
||||
continue
|
||||
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
No-notice MIT License
|
||||
|
||||
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 rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+8
-4
@@ -1,17 +1,21 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, print_function
|
||||
__version__ = '1.4.1.dev0'
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from vistir.compat import ResourceWarning
|
||||
|
||||
from .models.lockfile import Lockfile
|
||||
from .models.pipfile import Pipfile
|
||||
from .models.requirements import Requirement
|
||||
|
||||
__version__ = "1.4.2"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(logging.NullHandler())
|
||||
warnings.filterwarnings("ignore", category=ResourceWarning)
|
||||
|
||||
from .models.requirements import Requirement
|
||||
from .models.lockfile import Lockfile
|
||||
from .models.pipfile import Pipfile
|
||||
|
||||
__all__ = ["Lockfile", "Pipfile", "Requirement"]
|
||||
|
||||
+17
-1
@@ -20,6 +20,7 @@ from vistir.contextmanagers import cd, temp_environ
|
||||
from vistir.misc import partialclass
|
||||
from vistir.path import create_tracked_tempdir
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..utils import prepare_pip_source_args, _ensure_dir
|
||||
from .cache import CACHE_DIR, DependencyCache
|
||||
from .utils import (
|
||||
@@ -29,6 +30,17 @@ from .utils import (
|
||||
)
|
||||
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import Any, Dict, List, Generator, Optional, Union, Tuple, TypeVar, Text, Set, AnyStr
|
||||
from pip_shims.shims import InstallRequirement, InstallationCandidate, PackageFinder, Command
|
||||
from packaging.requirements import Requirement as PackagingRequirement
|
||||
TRequirement = TypeVar("TRequirement")
|
||||
RequirementType = TypeVar('RequirementType', covariant=True, bound=PackagingRequirement)
|
||||
MarkerType = TypeVar('MarkerType', covariant=True, bound=Marker)
|
||||
STRING_TYPE = Union[str, bytes, Text]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
|
||||
|
||||
PKGS_DOWNLOAD_DIR = fs_str(os.path.join(CACHE_DIR, "pkgs"))
|
||||
WHEEL_DOWNLOAD_DIR = fs_str(os.path.join(CACHE_DIR, "wheels"))
|
||||
|
||||
@@ -43,6 +55,7 @@ def _get_filtered_versions(ireq, versions, prereleases):
|
||||
|
||||
|
||||
def find_all_matches(finder, ireq, pre=False):
|
||||
# type: (PackageFinder, InstallRequirement, bool) -> List[InstallationCandidate]
|
||||
"""Find all matching dependencies using the supplied finder and the
|
||||
given ireq.
|
||||
|
||||
@@ -65,6 +78,7 @@ def find_all_matches(finder, ireq, pre=False):
|
||||
|
||||
|
||||
def get_pip_command():
|
||||
# type: () -> Command
|
||||
# Use pip's parser for pip.conf management and defaults.
|
||||
# General options (find_links, index_url, extra_index_url, trusted_host,
|
||||
# and pre) are defered to pip.
|
||||
@@ -89,7 +103,7 @@ def get_pip_command():
|
||||
|
||||
@attr.s
|
||||
class AbstractDependency(object):
|
||||
name = attr.ib()
|
||||
name = attr.ib() # type: STRING_TYPE
|
||||
specifiers = attr.ib()
|
||||
markers = attr.ib()
|
||||
candidates = attr.ib()
|
||||
@@ -284,6 +298,7 @@ def get_abstract_dependencies(reqs, sources=None, parent=None):
|
||||
|
||||
|
||||
def get_dependencies(ireq, sources=None, parent=None):
|
||||
# type: (Union[InstallRequirement, InstallationCandidate], Optional[List[Dict[S, Union[S, bool]]]], Optional[AbstractDependency]) -> Set[S, ...]
|
||||
"""Get all dependencies for a given install requirement.
|
||||
|
||||
:param ireq: A single InstallRequirement
|
||||
@@ -556,6 +571,7 @@ def get_pip_options(args=[], sources=None, pip_command=None):
|
||||
|
||||
|
||||
def get_finder(sources=None, pip_command=None, pip_options=None):
|
||||
# type: (List[Dict[S, Union[S, bool]]], Optional[Command], Any) -> PackageFinder
|
||||
"""Get a package finder for looking up candidates to install
|
||||
|
||||
:param sources: A list of pipfile-formatted sources, defaults to None
|
||||
|
||||
+947
-616
File diff suppressed because it is too large
Load Diff
+214
-127
@@ -19,22 +19,21 @@ from distlib.wheel import Wheel
|
||||
from packaging.markers import Marker
|
||||
from six.moves import configparser
|
||||
from six.moves.urllib.parse import unquote, urlparse, urlunparse
|
||||
|
||||
from vistir.compat import Iterable, Path, lru_cache
|
||||
from vistir.contextmanagers import cd, temp_path
|
||||
from vistir.misc import run
|
||||
from vistir.path import create_tracked_tempdir, ensure_mkdir_p, mkdir_p, rmtree
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..exceptions import RequirementError
|
||||
from .utils import (
|
||||
get_default_pyproject_backend,
|
||||
get_name_variants,
|
||||
get_pyproject,
|
||||
init_requirement,
|
||||
split_vcs_method_from_uri,
|
||||
strip_extras_markers_from_requirement,
|
||||
get_default_pyproject_backend
|
||||
)
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..exceptions import RequirementError
|
||||
|
||||
try:
|
||||
from setuptools.dist import distutils
|
||||
@@ -49,15 +48,34 @@ except ImportError:
|
||||
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import Any, Dict, List, Generator, Optional, Union, Tuple, TypeVar, Text, Set
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Generator,
|
||||
Optional,
|
||||
Union,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Text,
|
||||
Set,
|
||||
AnyStr,
|
||||
)
|
||||
from pip_shims.shims import InstallRequirement, PackageFinder
|
||||
from pkg_resources import (
|
||||
PathMetadata, DistInfoDistribution, Requirement as PkgResourcesRequirement
|
||||
PathMetadata,
|
||||
DistInfoDistribution,
|
||||
Requirement as PkgResourcesRequirement,
|
||||
)
|
||||
from packaging.requirements import Requirement as PackagingRequirement
|
||||
|
||||
TRequirement = TypeVar("TRequirement")
|
||||
RequirementType = TypeVar('RequirementType', covariant=True, bound=PackagingRequirement)
|
||||
MarkerType = TypeVar('MarkerType', covariant=True, bound=Marker)
|
||||
RequirementType = TypeVar(
|
||||
"RequirementType", covariant=True, bound=PackagingRequirement
|
||||
)
|
||||
MarkerType = TypeVar("MarkerType", covariant=True, bound=Marker)
|
||||
STRING_TYPE = Union[str, bytes, Text]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
|
||||
|
||||
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
|
||||
@@ -69,22 +87,43 @@ _setup_distribution = None
|
||||
|
||||
|
||||
def pep517_subprocess_runner(cmd, cwd=None, extra_environ=None):
|
||||
# type: (List[Text], Optional[Text], Optional[Dict[Text, Text]]) -> None
|
||||
# type: (List[AnyStr], Optional[AnyStr], Optional[Dict[AnyStr, AnyStr]]) -> None
|
||||
"""The default method of calling the wrapper subprocess."""
|
||||
env = os.environ.copy()
|
||||
if extra_environ:
|
||||
env.update(extra_environ)
|
||||
|
||||
run(cmd, cwd=cwd, env=env, block=True, combine_stderr=True, return_object=False,
|
||||
write_to_stdout=False, nospin=True)
|
||||
run(
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
env=env,
|
||||
block=True,
|
||||
combine_stderr=True,
|
||||
return_object=False,
|
||||
write_to_stdout=False,
|
||||
nospin=True,
|
||||
)
|
||||
|
||||
|
||||
class BuildEnv(pep517.envbuild.BuildEnvironment):
|
||||
def pip_install(self, reqs):
|
||||
cmd = [sys.executable, '-m', 'pip', 'install', '--ignore-installed', '--prefix',
|
||||
self.path] + list(reqs)
|
||||
run(cmd, block=True, combine_stderr=True, return_object=False,
|
||||
write_to_stdout=False, nospin=True)
|
||||
cmd = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"--ignore-installed",
|
||||
"--prefix",
|
||||
self.path,
|
||||
] + list(reqs)
|
||||
run(
|
||||
cmd,
|
||||
block=True,
|
||||
combine_stderr=True,
|
||||
return_object=False,
|
||||
write_to_stdout=False,
|
||||
nospin=True,
|
||||
)
|
||||
|
||||
|
||||
class HookCaller(pep517.wrappers.Pep517HookCaller):
|
||||
@@ -135,7 +174,7 @@ def build_pep517(source_dir, build_dir, config_settings=None, dist_type="wheel")
|
||||
|
||||
@ensure_mkdir_p(mode=0o775)
|
||||
def _get_src_dir(root):
|
||||
# type: (Text) -> Text
|
||||
# type: (AnyStr) -> AnyStr
|
||||
src = os.environ.get("PIP_SRC")
|
||||
if src:
|
||||
return src
|
||||
@@ -152,8 +191,9 @@ def _get_src_dir(root):
|
||||
|
||||
@lru_cache()
|
||||
def ensure_reqs(reqs):
|
||||
# type: (List[Union[Text, PkgResourcesRequirement]]) -> List[PkgResourcesRequirement]
|
||||
# type: (List[Union[S, PkgResourcesRequirement]]) -> List[PkgResourcesRequirement]
|
||||
import pkg_resources
|
||||
|
||||
if not isinstance(reqs, Iterable):
|
||||
raise TypeError("Expecting an Iterable, got %r" % reqs)
|
||||
new_reqs = []
|
||||
@@ -167,19 +207,21 @@ def ensure_reqs(reqs):
|
||||
return new_reqs
|
||||
|
||||
|
||||
def _prepare_wheel_building_kwargs(ireq=None, src_root=None, src_dir=None, editable=False):
|
||||
# type: (Optional[InstallRequirement], Optional[Text], Optional[Text], bool) -> Dict[Text, Text]
|
||||
download_dir = os.path.join(CACHE_DIR, "pkgs") # type: Text
|
||||
def _prepare_wheel_building_kwargs(
|
||||
ireq=None, src_root=None, src_dir=None, editable=False
|
||||
):
|
||||
# type: (Optional[InstallRequirement], Optional[AnyStr], Optional[AnyStr], bool) -> Dict[AnyStr, AnyStr]
|
||||
download_dir = os.path.join(CACHE_DIR, "pkgs") # type: STRING_TYPE
|
||||
mkdir_p(download_dir)
|
||||
|
||||
wheel_download_dir = os.path.join(CACHE_DIR, "wheels") # type: Text
|
||||
wheel_download_dir = os.path.join(CACHE_DIR, "wheels") # type: STRING_TYPE
|
||||
mkdir_p(wheel_download_dir)
|
||||
|
||||
if src_dir is None:
|
||||
if editable and src_root is not None:
|
||||
src_dir = src_root
|
||||
elif ireq is None and src_root is not None:
|
||||
src_dir = _get_src_dir(root=src_root) # type: Text
|
||||
src_dir = _get_src_dir(root=src_root) # type: STRING_TYPE
|
||||
elif ireq is not None and ireq.editable and src_root is not None:
|
||||
src_dir = _get_src_dir(root=src_root)
|
||||
else:
|
||||
@@ -199,7 +241,7 @@ def _prepare_wheel_building_kwargs(ireq=None, src_root=None, src_dir=None, edita
|
||||
|
||||
|
||||
def iter_metadata(path, pkg_name=None, metadata_type="egg-info"):
|
||||
# type: (Text, Optional[Text], Text) -> Generator
|
||||
# type: (AnyStr, Optional[AnyStr], AnyStr) -> Generator
|
||||
if pkg_name is not None:
|
||||
pkg_variants = get_name_variants(pkg_name)
|
||||
non_matching_dirs = []
|
||||
@@ -212,14 +254,17 @@ def iter_metadata(path, pkg_name=None, metadata_type="egg-info"):
|
||||
elif not entry.name.endswith(metadata_type):
|
||||
non_matching_dirs.append(entry)
|
||||
for entry in non_matching_dirs:
|
||||
for dir_entry in iter_metadata(entry.path, pkg_name=pkg_name, metadata_type=metadata_type):
|
||||
for dir_entry in iter_metadata(
|
||||
entry.path, pkg_name=pkg_name, metadata_type=metadata_type
|
||||
):
|
||||
yield dir_entry
|
||||
|
||||
|
||||
def find_egginfo(target, pkg_name=None):
|
||||
# type: (Text, Optional[Text]) -> Generator
|
||||
# type: (AnyStr, Optional[AnyStr]) -> Generator
|
||||
egg_dirs = (
|
||||
egg_dir for egg_dir in iter_metadata(target, pkg_name=pkg_name)
|
||||
egg_dir
|
||||
for egg_dir in iter_metadata(target, pkg_name=pkg_name)
|
||||
if egg_dir is not None
|
||||
)
|
||||
if pkg_name:
|
||||
@@ -230,9 +275,12 @@ def find_egginfo(target, pkg_name=None):
|
||||
|
||||
|
||||
def find_distinfo(target, pkg_name=None):
|
||||
# type: (Text, Optional[Text]) -> Generator
|
||||
# type: (AnyStr, Optional[AnyStr]) -> Generator
|
||||
dist_dirs = (
|
||||
dist_dir for dist_dir in iter_metadata(target, pkg_name=pkg_name, metadata_type="dist-info")
|
||||
dist_dir
|
||||
for dist_dir in iter_metadata(
|
||||
target, pkg_name=pkg_name, metadata_type="dist-info"
|
||||
)
|
||||
if dist_dir is not None
|
||||
)
|
||||
if pkg_name:
|
||||
@@ -243,7 +291,7 @@ def find_distinfo(target, pkg_name=None):
|
||||
|
||||
|
||||
def get_metadata(path, pkg_name=None, metadata_type=None):
|
||||
# type: (Text, Optional[Text], Optional[Text]) -> Dict[Text, Union[Text, List[RequirementType], Dict[Text, RequirementType]]]
|
||||
# type: (S, Optional[S], Optional[S]) -> Dict[S, Union[S, List[RequirementType], Dict[S, RequirementType]]]
|
||||
metadata_dirs = []
|
||||
wheel_allowed = metadata_type == "wheel" or metadata_type is None
|
||||
egg_allowed = metadata_type == "egg" or metadata_type is None
|
||||
@@ -258,6 +306,7 @@ def get_metadata(path, pkg_name=None, metadata_type=None):
|
||||
base_dir = None
|
||||
if matched_dir is not None:
|
||||
import pkg_resources
|
||||
|
||||
metadata_dir = os.path.abspath(matched_dir.path)
|
||||
base_dir = os.path.dirname(metadata_dir)
|
||||
dist = None
|
||||
@@ -279,7 +328,7 @@ def get_metadata(path, pkg_name=None, metadata_type=None):
|
||||
|
||||
@lru_cache()
|
||||
def get_extra_name_from_marker(marker):
|
||||
# type: (MarkerType) -> Optional[Text]
|
||||
# type: (MarkerType) -> Optional[S]
|
||||
if not marker:
|
||||
raise ValueError("Invalid value for marker: {0!r}".format(marker))
|
||||
if not getattr(marker, "_markers", None):
|
||||
@@ -291,7 +340,7 @@ def get_extra_name_from_marker(marker):
|
||||
|
||||
|
||||
def get_metadata_from_wheel(wheel_path):
|
||||
# type: (Text) -> Dict[Any, Any]
|
||||
# type: (S) -> Dict[Any, Any]
|
||||
if not isinstance(wheel_path, six.string_types):
|
||||
raise TypeError("Expected string instance, received {0!r}".format(wheel_path))
|
||||
try:
|
||||
@@ -318,16 +367,11 @@ def get_metadata_from_wheel(wheel_path):
|
||||
extras[extra].append(parsed_req)
|
||||
else:
|
||||
requires.append(parsed_req)
|
||||
return {
|
||||
"name": name,
|
||||
"version": version,
|
||||
"requires": requires,
|
||||
"extras": extras
|
||||
}
|
||||
return {"name": name, "version": version, "requires": requires, "extras": extras}
|
||||
|
||||
|
||||
def get_metadata_from_dist(dist):
|
||||
# type: (Union[PathMetadata, DistInfoDistribution]) -> Dict[Text, Union[Text, List[RequirementType], Dict[Text, RequirementType]]]
|
||||
# type: (Union[PathMetadata, DistInfoDistribution]) -> Dict[S, Union[S, List[RequirementType], Dict[S, RequirementType]]]
|
||||
try:
|
||||
requires = dist.requires()
|
||||
except Exception:
|
||||
@@ -360,31 +404,33 @@ def get_metadata_from_dist(dist):
|
||||
"name": dist.project_name,
|
||||
"version": dist.version,
|
||||
"requires": requires,
|
||||
"extras": extras
|
||||
"extras": extras,
|
||||
}
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class BaseRequirement(object):
|
||||
name = attr.ib(default="", cmp=True) # type: Text
|
||||
requirement = attr.ib(default=None, cmp=True) # type: Optional[PkgResourcesRequirement]
|
||||
name = attr.ib(default="", cmp=True) # type: STRING_TYPE
|
||||
requirement = attr.ib(
|
||||
default=None, cmp=True
|
||||
) # type: Optional[PkgResourcesRequirement]
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> Text
|
||||
# type: () -> S
|
||||
return "{0}".format(str(self.requirement))
|
||||
|
||||
def as_dict(self):
|
||||
# type: () -> Dict[Text, Optional[PkgResourcesRequirement]]
|
||||
# type: () -> Dict[S, Optional[PkgResourcesRequirement]]
|
||||
return {self.name: self.requirement}
|
||||
|
||||
def as_tuple(self):
|
||||
# type: () -> Tuple[Text, Optional[PkgResourcesRequirement]]
|
||||
# type: () -> Tuple[S, Optional[PkgResourcesRequirement]]
|
||||
return (self.name, self.requirement)
|
||||
|
||||
@classmethod
|
||||
@lru_cache()
|
||||
def from_string(cls, line):
|
||||
# type: (Text) -> BaseRequirement
|
||||
# type: (S) -> BaseRequirement
|
||||
line = line.strip()
|
||||
req = init_requirement(line)
|
||||
return cls.from_req(req)
|
||||
@@ -406,54 +452,63 @@ class BaseRequirement(object):
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class Extra(object):
|
||||
name = attr.ib(default=None, cmp=True) # type: Text
|
||||
name = attr.ib(default=None, cmp=True) # type: STRING_TYPE
|
||||
requirements = attr.ib(factory=frozenset, cmp=True, type=frozenset)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> Text
|
||||
return "{0}: {{{1}}}".format(self.section, ", ".join([r.name for r in self.requirements]))
|
||||
# type: () -> S
|
||||
return "{0}: {{{1}}}".format(
|
||||
self.section, ", ".join([r.name for r in self.requirements])
|
||||
)
|
||||
|
||||
def add(self, req):
|
||||
# type: (BaseRequirement) -> None
|
||||
if req not in self.requirements:
|
||||
return attr.evolve(self, requirements=frozenset(set(self.requirements).add(req)))
|
||||
return attr.evolve(
|
||||
self, requirements=frozenset(set(self.requirements).add(req))
|
||||
)
|
||||
return self
|
||||
|
||||
def as_dict(self):
|
||||
# type: () -> Dict[Text, Tuple[RequirementType, ...]]
|
||||
# type: () -> Dict[S, Tuple[RequirementType, ...]]
|
||||
return {self.name: tuple([r.requirement for r in self.requirements])}
|
||||
|
||||
|
||||
@attr.s(slots=True, cmp=True, hash=True)
|
||||
class SetupInfo(object):
|
||||
name = attr.ib(default=None, cmp=True) # type: Text
|
||||
base_dir = attr.ib(default=None, cmp=True, hash=False) # type: Text
|
||||
version = attr.ib(default=None, cmp=True) # type: Text
|
||||
name = attr.ib(default=None, cmp=True) # type: STRING_TYPE
|
||||
base_dir = attr.ib(default=None, cmp=True, hash=False) # type: STRING_TYPE
|
||||
version = attr.ib(default=None, cmp=True) # type: STRING_TYPE
|
||||
_requirements = attr.ib(type=frozenset, factory=frozenset, cmp=True, hash=True)
|
||||
build_requires = attr.ib(type=tuple, default=attr.Factory(tuple), cmp=True)
|
||||
build_backend = attr.ib(cmp=True) # type: Text
|
||||
build_backend = attr.ib(cmp=True) # type: STRING_TYPE
|
||||
setup_requires = attr.ib(type=tuple, default=attr.Factory(tuple), cmp=True)
|
||||
python_requires = attr.ib(type=packaging.specifiers.SpecifierSet, default=None, cmp=True)
|
||||
python_requires = attr.ib(
|
||||
type=packaging.specifiers.SpecifierSet, default=None, cmp=True
|
||||
)
|
||||
_extras_requirements = attr.ib(type=tuple, default=attr.Factory(tuple), cmp=True)
|
||||
setup_cfg = attr.ib(type=Path, default=None, cmp=True, hash=False)
|
||||
setup_py = attr.ib(type=Path, default=None, cmp=True, hash=False)
|
||||
pyproject = attr.ib(type=Path, default=None, cmp=True, hash=False)
|
||||
ireq = attr.ib(default=None, cmp=True, hash=False) # type: Optional[InstallRequirement]
|
||||
ireq = attr.ib(
|
||||
default=None, cmp=True, hash=False
|
||||
) # type: Optional[InstallRequirement]
|
||||
extra_kwargs = attr.ib(default=attr.Factory(dict), type=dict, cmp=False, hash=False)
|
||||
metadata = attr.ib(default=None) # type: Optional[Tuple[Text]]
|
||||
metadata = attr.ib(default=None) # type: Optional[Tuple[STRING_TYPE]]
|
||||
|
||||
@build_backend.default
|
||||
def get_build_backend(self):
|
||||
# type: () -> S
|
||||
return get_default_pyproject_backend()
|
||||
|
||||
@property
|
||||
def requires(self):
|
||||
# type: () -> Dict[Text, RequirementType]
|
||||
# type: () -> Dict[S, RequirementType]
|
||||
return {req.name: req.requirement for req in self._requirements}
|
||||
|
||||
@property
|
||||
def extras(self):
|
||||
# type: () -> Dict[Text, Optional[Any]]
|
||||
# type: () -> Dict[S, Optional[Any]]
|
||||
extras_dict = {}
|
||||
extras = set(self._extras_requirements)
|
||||
for section, deps in extras:
|
||||
@@ -465,7 +520,7 @@ class SetupInfo(object):
|
||||
|
||||
@classmethod
|
||||
def get_setup_cfg(cls, setup_cfg_path):
|
||||
# type: (Text) -> Dict[Text, Union[Text, None, Set[BaseRequirement], List[Text], Tuple[Text, Tuple[BaseRequirement]]]]
|
||||
# type: (S) -> Dict[S, Union[S, None, Set[BaseRequirement], List[S], Tuple[S, Tuple[BaseRequirement]]]]
|
||||
if os.path.exists(setup_cfg_path):
|
||||
default_opts = {
|
||||
"metadata": {"name": "", "version": ""},
|
||||
@@ -486,35 +541,40 @@ class SetupInfo(object):
|
||||
results["version"] = parser.get("metadata", "version")
|
||||
install_requires = set() # type: Set[BaseRequirement]
|
||||
if parser.has_option("options", "install_requires"):
|
||||
install_requires = set([
|
||||
BaseRequirement.from_string(dep)
|
||||
for dep in parser.get("options", "install_requires").split("\n")
|
||||
if dep
|
||||
])
|
||||
install_requires = set(
|
||||
[
|
||||
BaseRequirement.from_string(dep)
|
||||
for dep in parser.get("options", "install_requires").split("\n")
|
||||
if dep
|
||||
]
|
||||
)
|
||||
results["install_requires"] = install_requires
|
||||
if parser.has_option("options", "python_requires"):
|
||||
results["python_requires"] = parser.get("options", "python_requires")
|
||||
if parser.has_option("options", "build_requires"):
|
||||
results["build_requires"] = parser.get("options", "build_requires")
|
||||
extras_require = ()
|
||||
extras = []
|
||||
if "options.extras_require" in parser.sections():
|
||||
extras_require = tuple([
|
||||
(section, tuple([
|
||||
BaseRequirement.from_string(dep)
|
||||
for dep in parser.get(
|
||||
"options.extras_require", section
|
||||
).split("\n")
|
||||
if dep
|
||||
]))
|
||||
for section in parser.options("options.extras_require")
|
||||
if section not in ["options", "metadata"]
|
||||
])
|
||||
results["extras_require"] = extras_require
|
||||
extras_require_section = parser.options("options.extras_require")
|
||||
for section in extras_require_section:
|
||||
if section in ["options", "metadata"]:
|
||||
continue
|
||||
section_contents = parser.get("options.extras_require", section)
|
||||
section_list = section_contents.split("\n")
|
||||
section_extras = []
|
||||
for extra_name in section_list:
|
||||
if not extra_name or extra_name.startswith("#"):
|
||||
continue
|
||||
section_extras.append(BaseRequirement.from_string(extra_name))
|
||||
if section_extras:
|
||||
extras.append(tuple([section, tuple(section_extras)]))
|
||||
results["extras_require"] = tuple(extras)
|
||||
return results
|
||||
|
||||
@property
|
||||
def egg_base(self):
|
||||
base = None # type: Optional[Text]
|
||||
# type: () -> S
|
||||
base = None # type: Optional[STRING_TYPE]
|
||||
if self.setup_py.exists():
|
||||
base = self.setup_py.parent
|
||||
elif self.pyproject.exists():
|
||||
@@ -541,21 +601,30 @@ class SetupInfo(object):
|
||||
self.version = parsed.get("version")
|
||||
build_requires = parsed.get("build_requires", [])
|
||||
if self.build_requires:
|
||||
self.build_requires = tuple(set(self.build_requires) | set(build_requires))
|
||||
self.build_requires = tuple(
|
||||
set(self.build_requires) | set(build_requires)
|
||||
)
|
||||
self._requirements = frozenset(
|
||||
set(self._requirements) | set(parsed["install_requires"])
|
||||
)
|
||||
if self.python_requires is None:
|
||||
self.python_requires = parsed.get("python_requires")
|
||||
if not self._extras_requirements:
|
||||
self._extras_requirements = (parsed["extras_require"])
|
||||
self._extras_requirements = parsed["extras_require"]
|
||||
else:
|
||||
self._extras_requirements = self._extras_requirements + parsed["extras_require"]
|
||||
self._extras_requirements = (
|
||||
self._extras_requirements + parsed["extras_require"]
|
||||
)
|
||||
if self.ireq is not None and self.ireq.extras:
|
||||
for extra in self.ireq.extras:
|
||||
if extra in self.extras:
|
||||
extras_tuple = tuple([BaseRequirement.from_req(req) for req in self.extras[extra]])
|
||||
extras_tuple = tuple(
|
||||
[BaseRequirement.from_req(req) for req in self.extras[extra]]
|
||||
)
|
||||
self._extras_requirements += ((extra, extras_tuple),)
|
||||
self._requirements = frozenset(
|
||||
set(self._requirements) | set(list(extras_tuple))
|
||||
)
|
||||
|
||||
def run_setup(self):
|
||||
# type: () -> None
|
||||
@@ -578,16 +647,22 @@ class SetupInfo(object):
|
||||
_setup_stop_after = "run"
|
||||
sys.argv[0] = script_name
|
||||
sys.argv[1:] = args
|
||||
with open(script_name, 'rb') as f:
|
||||
with open(script_name, "rb") as f:
|
||||
if sys.version_info < (3, 5):
|
||||
exec(f.read(), g, local_dict)
|
||||
else:
|
||||
exec(f.read(), g)
|
||||
# We couldn't import everything needed to run setup
|
||||
except NameError:
|
||||
python = os.environ.get('PIP_PYTHON_PATH', sys.executable)
|
||||
out, _ = run([python, "setup.py"] + args, cwd=target_cwd, block=True,
|
||||
combine_stderr=False, return_object=False, nospin=True)
|
||||
python = os.environ.get("PIP_PYTHON_PATH", sys.executable)
|
||||
out, _ = run(
|
||||
[python, "setup.py"] + args,
|
||||
cwd=target_cwd,
|
||||
block=True,
|
||||
combine_stderr=False,
|
||||
return_object=False,
|
||||
nospin=True,
|
||||
)
|
||||
finally:
|
||||
_setup_stop_after = None
|
||||
sys.argv = save_argv
|
||||
@@ -615,15 +690,13 @@ class SetupInfo(object):
|
||||
if not install_requires:
|
||||
install_requires = dist.install_requires
|
||||
if install_requires and not self.requires:
|
||||
requirements = set([
|
||||
BaseRequirement.from_req(req) for req in install_requires
|
||||
])
|
||||
requirements = set(
|
||||
[BaseRequirement.from_req(req) for req in install_requires]
|
||||
)
|
||||
if getattr(self.ireq, "extras", None):
|
||||
for extra in self.ireq.extras:
|
||||
requirements |= set(list(self.extras.get(extra, [])))
|
||||
self._requirements = frozenset(
|
||||
set(self._requirements) | requirements
|
||||
)
|
||||
self._requirements = frozenset(set(self._requirements) | requirements)
|
||||
if dist.setup_requires and not self.setup_requires:
|
||||
self.setup_requires = tuple(dist.setup_requires)
|
||||
if not self.version:
|
||||
@@ -637,38 +710,48 @@ class SetupInfo(object):
|
||||
return config
|
||||
|
||||
def build_wheel(self):
|
||||
# type: () -> Text
|
||||
# type: () -> S
|
||||
if not self.pyproject.exists():
|
||||
build_requires = ", ".join(['"{0}"'.format(r) for r in self.build_requires])
|
||||
self.pyproject.write_text(u"""
|
||||
self.pyproject.write_text(
|
||||
u"""
|
||||
[build-system]
|
||||
requires = [{0}]
|
||||
build-backend = "{1}"
|
||||
""".format(build_requires, self.build_backend).strip())
|
||||
""".format(
|
||||
build_requires, self.build_backend
|
||||
).strip()
|
||||
)
|
||||
return build_pep517(
|
||||
self.base_dir, self.extra_kwargs["build_dir"],
|
||||
self.base_dir,
|
||||
self.extra_kwargs["build_dir"],
|
||||
config_settings=self.pep517_config,
|
||||
dist_type="wheel"
|
||||
dist_type="wheel",
|
||||
)
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
def build_sdist(self):
|
||||
# type: () -> Text
|
||||
# type: () -> S
|
||||
if not self.pyproject.exists():
|
||||
build_requires = ", ".join(['"{0}"'.format(r) for r in self.build_requires])
|
||||
self.pyproject.write_text(u"""
|
||||
self.pyproject.write_text(
|
||||
u"""
|
||||
[build-system]
|
||||
requires = [{0}]
|
||||
build-backend = "{1}"
|
||||
""".format(build_requires, self.build_backend).strip())
|
||||
""".format(
|
||||
build_requires, self.build_backend
|
||||
).strip()
|
||||
)
|
||||
return build_pep517(
|
||||
self.base_dir, self.extra_kwargs["build_dir"],
|
||||
self.base_dir,
|
||||
self.extra_kwargs["build_dir"],
|
||||
config_settings=self.pep517_config,
|
||||
dist_type="sdist"
|
||||
dist_type="sdist",
|
||||
)
|
||||
|
||||
def build(self):
|
||||
# type: () -> Optional[Text]
|
||||
# type: () -> None
|
||||
dist_path = None
|
||||
try:
|
||||
dist_path = self.build_wheel()
|
||||
@@ -686,9 +769,10 @@ build-backend = "{1}"
|
||||
self.get_egg_metadata()
|
||||
if not self.metadata or not self.name:
|
||||
self.run_setup()
|
||||
return None
|
||||
|
||||
def reload(self):
|
||||
# type: () -> Dict[Text, Any]
|
||||
# type: () -> Dict[S, Any]
|
||||
"""
|
||||
Wipe existing distribution info metadata for rebuilding.
|
||||
"""
|
||||
@@ -700,23 +784,28 @@ build-backend = "{1}"
|
||||
self.get_info()
|
||||
|
||||
def get_metadata_from_wheel(self, wheel_path):
|
||||
# type: (Text) -> Dict[Any, Any]
|
||||
# type: (S) -> Dict[Any, Any]
|
||||
metadata_dict = get_metadata_from_wheel(wheel_path)
|
||||
if metadata_dict:
|
||||
self.populate_metadata(metadata_dict)
|
||||
|
||||
def get_egg_metadata(self, metadata_dir=None, metadata_type=None):
|
||||
# type: (Optional[Text], Optional[Text]) -> None
|
||||
# type: (Optional[AnyStr], Optional[AnyStr]) -> None
|
||||
package_indicators = [self.pyproject, self.setup_py, self.setup_cfg]
|
||||
# if self.setup_py is not None and self.setup_py.exists():
|
||||
metadata_dirs = []
|
||||
if any([fn is not None and fn.exists() for fn in package_indicators]):
|
||||
metadata_dirs = [self.extra_kwargs["build_dir"], self.egg_base, self.extra_kwargs["src_dir"]]
|
||||
metadata_dirs = [
|
||||
self.extra_kwargs["build_dir"],
|
||||
self.egg_base,
|
||||
self.extra_kwargs["src_dir"],
|
||||
]
|
||||
if metadata_dir is not None:
|
||||
metadata_dirs = [metadata_dir] + metadata_dirs
|
||||
metadata = [
|
||||
get_metadata(d, pkg_name=self.name, metadata_type=metadata_type)
|
||||
for d in metadata_dirs if os.path.exists(d)
|
||||
for d in metadata_dirs
|
||||
if os.path.exists(d)
|
||||
]
|
||||
metadata = next(iter(d for d in metadata if d), None)
|
||||
if metadata is not None:
|
||||
@@ -741,20 +830,20 @@ build-backend = "{1}"
|
||||
if not self.version:
|
||||
self.version = metadata.get("version", self.version)
|
||||
self._requirements = frozenset(
|
||||
set(self._requirements) | set([
|
||||
BaseRequirement.from_req(req)
|
||||
for req in metadata.get("requires", [])
|
||||
])
|
||||
set(self._requirements)
|
||||
| set([BaseRequirement.from_req(req) for req in metadata.get("requires", [])])
|
||||
)
|
||||
if getattr(self.ireq, "extras", None):
|
||||
for extra in self.ireq.extras:
|
||||
extras = metadata.get("extras", {}).get(extra, [])
|
||||
if extras:
|
||||
extras_tuple = tuple([
|
||||
BaseRequirement.from_req(req)
|
||||
for req in ensure_reqs(tuple(extras))
|
||||
if req is not None
|
||||
])
|
||||
extras_tuple = tuple(
|
||||
[
|
||||
BaseRequirement.from_req(req)
|
||||
for req in ensure_reqs(tuple(extras))
|
||||
if req is not None
|
||||
]
|
||||
)
|
||||
self._extras_requirements += ((extra, extras_tuple),)
|
||||
self._requirements = frozenset(
|
||||
set(self._requirements) | set(extras_tuple)
|
||||
@@ -776,7 +865,7 @@ build-backend = "{1}"
|
||||
self.build_requires = ("setuptools", "wheel")
|
||||
|
||||
def get_info(self):
|
||||
# type: () -> Dict[Text, Any]
|
||||
# type: () -> Dict[S, Any]
|
||||
if self.setup_cfg and self.setup_cfg.exists():
|
||||
with cd(self.base_dir):
|
||||
self.parse_setup_cfg()
|
||||
@@ -800,7 +889,7 @@ build-backend = "{1}"
|
||||
return self.as_dict()
|
||||
|
||||
def as_dict(self):
|
||||
# type: () -> Dict[Text, Any]
|
||||
# type: () -> Dict[S, Any]
|
||||
prop_dict = {
|
||||
"name": self.name,
|
||||
"version": self.version,
|
||||
@@ -829,8 +918,9 @@ build-backend = "{1}"
|
||||
@classmethod
|
||||
@lru_cache()
|
||||
def from_ireq(cls, ireq, subdir=None, finder=None):
|
||||
# type: (InstallRequirement, Optional[Text], Optional[PackageFinder]) -> Optional[SetupInfo]
|
||||
# type: (InstallRequirement, Optional[AnyStr], Optional[PackageFinder]) -> Optional[SetupInfo]
|
||||
import pip_shims.shims
|
||||
|
||||
if not ireq.link:
|
||||
return
|
||||
if ireq.link.is_wheel:
|
||||
@@ -867,8 +957,7 @@ build-backend = "{1}"
|
||||
download_dir = kwargs["download_dir"]
|
||||
elif path is not None and os.path.isdir(path):
|
||||
raise RequirementError(
|
||||
"The file URL points to a directory not installable: {}"
|
||||
.format(ireq.link)
|
||||
"The file URL points to a directory not installable: {}".format(ireq.link)
|
||||
)
|
||||
ireq.build_location(kwargs["build_dir"])
|
||||
src_dir = ireq.ensure_has_source_dir(kwargs["src_dir"])
|
||||
@@ -884,14 +973,12 @@ build-backend = "{1}"
|
||||
hashes=ireq.hashes(False),
|
||||
progress_bar="off",
|
||||
)
|
||||
created = cls.create(
|
||||
src_dir, subdirectory=subdir, ireq=ireq, kwargs=kwargs
|
||||
)
|
||||
created = cls.create(src_dir, subdirectory=subdir, ireq=ireq, kwargs=kwargs)
|
||||
return created
|
||||
|
||||
@classmethod
|
||||
def create(cls, base_dir, subdirectory=None, ireq=None, kwargs=None):
|
||||
# type: (Text, Optional[Text], Optional[InstallRequirement], Optional[Dict[Text, Text]]) -> Optional[SetupInfo]
|
||||
# type: (AnyStr, Optional[AnyStr], Optional[InstallRequirement], Optional[Dict[AnyStr, AnyStr]]) -> Optional[SetupInfo]
|
||||
if not base_dir or base_dir is None:
|
||||
return
|
||||
|
||||
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import attr
|
||||
import pip_shims.shims
|
||||
from orderedmultidict import omdict
|
||||
from six.moves.urllib.parse import quote_plus, unquote_plus
|
||||
from urllib3 import util as urllib3_util
|
||||
from urllib3.util import parse_url as urllib3_parse
|
||||
from urllib3.util.url import Url
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import List, Tuple, Text, Union, TypeVar, Optional
|
||||
from pip_shims.shims import Link
|
||||
from vistir.compat import Path
|
||||
|
||||
_T = TypeVar("_T")
|
||||
STRING_TYPE = Union[bytes, str, Text]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
|
||||
|
||||
def _get_parsed_url(url):
|
||||
# type: (S) -> Url
|
||||
"""
|
||||
This is a stand-in function for `urllib3.util.parse_url`
|
||||
|
||||
The orignal function doesn't handle special characters very well, this simply splits
|
||||
out the authentication section, creates the parsed url, then puts the authentication
|
||||
section back in, bypassing validation.
|
||||
|
||||
:return: The new, parsed URL object
|
||||
:rtype: :class:`~urllib3.util.url.Url`
|
||||
"""
|
||||
|
||||
try:
|
||||
parsed = urllib3_parse(url)
|
||||
except ValueError:
|
||||
scheme, _, url = url.partition("://")
|
||||
auth, _, url = url.rpartition("@")
|
||||
url = "{scheme}://{url}".format(scheme=scheme, url=url)
|
||||
parsed = urllib3_parse(url)._replace(auth=auth)
|
||||
return parsed
|
||||
|
||||
|
||||
def remove_password_from_url(url):
|
||||
# type: (S) -> S
|
||||
"""
|
||||
Given a url, remove the password and insert 4 dashes
|
||||
|
||||
:param url: The url to replace the authentication in
|
||||
:type url: S
|
||||
:return: The new URL without authentication
|
||||
:rtype: S
|
||||
"""
|
||||
|
||||
parsed = _get_parsed_url(url)
|
||||
if parsed.auth:
|
||||
auth, _, _ = parsed.auth.partition(":")
|
||||
return parsed._replace(auth="{auth}:----".format(auth=auth)).url
|
||||
return parsed.url
|
||||
|
||||
|
||||
@attr.s
|
||||
class URI(object):
|
||||
#: The target hostname, e.g. `amazon.com`
|
||||
host = attr.ib(type=str)
|
||||
#: The URI Scheme, e.g. `salesforce`
|
||||
scheme = attr.ib(default="https", type=str)
|
||||
#: The numeric port of the url if specified
|
||||
port = attr.ib(default=None, type=int)
|
||||
#: The url path, e.g. `/path/to/endpoint`
|
||||
path = attr.ib(default="", type=str)
|
||||
#: Query parameters, e.g. `?variable=value...`
|
||||
query = attr.ib(default="", type=str)
|
||||
#: URL Fragments, e.g. `#fragment=value`
|
||||
fragment = attr.ib(default="", type=str)
|
||||
#: Subdirectory fragment, e.g. `&subdirectory=blah...`
|
||||
subdirectory = attr.ib(default="", type=str)
|
||||
#: VCS ref this URI points at, if available
|
||||
ref = attr.ib(default="", type=str)
|
||||
#: The username if provided, parsed from `user:password@hostname`
|
||||
username = attr.ib(default="", type=str)
|
||||
#: Password parsed from `user:password@hostname`
|
||||
password = attr.ib(default="", type=str, repr=False)
|
||||
#: An orderedmultidict representing query fragments
|
||||
query_dict = attr.ib(factory=omdict, type=omdict)
|
||||
#: The name of the specified package in case it is a VCS URI with an egg fragment
|
||||
name = attr.ib(default="", type=str)
|
||||
#: Any extras requested from the requirement
|
||||
extras = attr.ib(factory=tuple, type=tuple)
|
||||
#: Whether the url was parsed as a direct pep508-style URL
|
||||
is_direct_url = attr.ib(default=False, type=bool)
|
||||
#: Whether the url was an implicit `git+ssh` url (passed as `git+git@`)
|
||||
is_implicit_ssh = attr.ib(default=False, type=bool)
|
||||
_auth = attr.ib(default=None, type=str, repr=False)
|
||||
_fragment_dict = attr.ib(factory=dict, type=dict)
|
||||
|
||||
def _parse_query(self):
|
||||
# type: () -> URI
|
||||
query = self.query if self.query is not None else ""
|
||||
query_dict = omdict()
|
||||
queries = query.split("&")
|
||||
query_items = []
|
||||
for q in queries:
|
||||
key, _, val = q.partition("=")
|
||||
val = unquote_plus(val.replace("+", " "))
|
||||
query_items.append((key, val))
|
||||
query_dict.load(query_items)
|
||||
return attr.evolve(self, query_dict=query_dict, query=query)
|
||||
|
||||
def _parse_fragment(self):
|
||||
# type: () -> URI
|
||||
subdirectory = self.subdirectory if self.subdirectory else ""
|
||||
fragment = self.fragment if self.fragment else ""
|
||||
if self.fragment is None:
|
||||
return self
|
||||
fragments = self.fragment.split("&")
|
||||
fragment_items = {}
|
||||
name = self.name if self.name else ""
|
||||
extras = self.extras
|
||||
for q in fragments:
|
||||
key, _, val = q.partition("=")
|
||||
val = unquote_plus(val.replace("+", " "))
|
||||
fragment_items[key] = val
|
||||
if key == "egg":
|
||||
from .utils import parse_extras
|
||||
|
||||
name, stripped_extras = pip_shims.shims._strip_extras(val)
|
||||
if stripped_extras:
|
||||
extras = tuple(parse_extras(stripped_extras))
|
||||
elif key == "subdirectory":
|
||||
subdirectory = val
|
||||
return attr.evolve(
|
||||
self,
|
||||
fragment_dict=fragment_items,
|
||||
subdirectory=subdirectory,
|
||||
fragment=fragment,
|
||||
extras=extras,
|
||||
name=name,
|
||||
)
|
||||
|
||||
def _parse_auth(self):
|
||||
# type: () -> URI
|
||||
if self._auth:
|
||||
username, _, password = self._auth.partition(":")
|
||||
password = quote_plus(password)
|
||||
return attr.evolve(self, username=username, password=password)
|
||||
return self
|
||||
|
||||
def get_password(self, unquote=False, include_token=True):
|
||||
# type: (bool, bool) -> str
|
||||
password = self.password
|
||||
if password and unquote:
|
||||
password = unquote_plus(password)
|
||||
else:
|
||||
password = ""
|
||||
return password
|
||||
|
||||
@staticmethod
|
||||
def parse_subdirectory(url_part):
|
||||
# type: (str) -> Tuple[str, Optional[str]]
|
||||
subdir = None
|
||||
if "&subdirectory" in url_part:
|
||||
url_part, _, subdir = url_part.rpartition("&")
|
||||
subdir = "&{0}".format(subdir.strip())
|
||||
return url_part.strip(), subdir
|
||||
|
||||
@classmethod
|
||||
def parse(cls, url):
|
||||
# type: (S) -> URI
|
||||
from .utils import DIRECT_URL_RE, split_ref_from_uri
|
||||
|
||||
is_direct_url = False
|
||||
name_with_extras = None
|
||||
is_implicit_ssh = url.strip().startswith("git+git@")
|
||||
if is_implicit_ssh:
|
||||
from ..utils import add_ssh_scheme_to_git_uri
|
||||
|
||||
url = add_ssh_scheme_to_git_uri(url)
|
||||
direct_match = DIRECT_URL_RE.match(url)
|
||||
if direct_match is not None:
|
||||
is_direct_url = True
|
||||
name_with_extras, _, url = url.partition("@")
|
||||
name_with_extras = name_with_extras.strip()
|
||||
url, ref = split_ref_from_uri(url.strip())
|
||||
if "file:/" in url and "file:///" not in url:
|
||||
url = url.replace("file:/", "file:///")
|
||||
parsed = _get_parsed_url(url)
|
||||
if not (parsed.scheme and parsed.host and parsed.path):
|
||||
# check if this is a file uri
|
||||
if not (
|
||||
parsed.scheme
|
||||
and parsed.path
|
||||
and (parsed.scheme == "file" or parsed.scheme.endswith("+file"))
|
||||
):
|
||||
raise ValueError("Failed parsing URL {0!r} - Not a valid url".format(url))
|
||||
parsed_dict = dict(parsed._asdict()).copy()
|
||||
parsed_dict["is_direct_url"] = is_direct_url
|
||||
parsed_dict["is_implicit_ssh"] = is_implicit_ssh
|
||||
if name_with_extras:
|
||||
fragment = ""
|
||||
if parsed_dict["fragment"] is not None:
|
||||
fragment = "{0}".format(parsed_dict["fragment"])
|
||||
elif "&subdirectory" in parsed_dict["path"]:
|
||||
path, fragment = cls.parse_subdirectory(parsed_dict["path"])
|
||||
parsed_dict["path"] = path
|
||||
elif ref is not None and "&subdirectory" in ref:
|
||||
ref, fragment = cls.parse_subdirectory(ref)
|
||||
parsed_dict["fragment"] = "egg={0}{1}".format(name_with_extras, fragment)
|
||||
if ref is not None:
|
||||
parsed_dict["ref"] = ref.strip()
|
||||
return cls(**parsed_dict)._parse_auth()._parse_query()._parse_fragment()
|
||||
|
||||
def to_string(
|
||||
self,
|
||||
escape_password=True, # type: bool
|
||||
unquote=True, # type: bool
|
||||
direct=None, # type: Optional[bool]
|
||||
strip_ssh=False, # type: bool
|
||||
strip_ref=False, # type: bool
|
||||
strip_name=False, # type: bool
|
||||
strip_subdir=False, # type: bool
|
||||
):
|
||||
# type: (...) -> str
|
||||
"""
|
||||
Converts the current URI to a string, unquoting or escaping the password as needed
|
||||
|
||||
:param escape_password: Whether to replace password with ``----``, default True
|
||||
:param escape_password: bool, optional
|
||||
:param unquote: Whether to unquote url-escapes in the password, default False
|
||||
:param unquote: bool, optional
|
||||
:param bool direct: Whether to format as a direct URL
|
||||
:param bool strip_ssh: Whether to strip the SSH scheme from the url (git only)
|
||||
:param bool strip_ref: Whether to drop the VCS ref (if present)
|
||||
:param bool strip_name: Whether to drop the name and extras (if present)
|
||||
:param bool strip_subdir: Whether to drop the subdirectory (if present)
|
||||
:return: The reconstructed string representing the URI
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
if direct is None:
|
||||
direct = self.is_direct_url
|
||||
if escape_password:
|
||||
password = "----" if (self.password or self.username) else ""
|
||||
else:
|
||||
password = self.get_password(unquote=unquote)
|
||||
auth = ""
|
||||
if self.username:
|
||||
if password:
|
||||
auth = "{self.username}:{password}@".format(password=password, self=self)
|
||||
else:
|
||||
auth = "{self.username}@".format(self=self)
|
||||
query = ""
|
||||
if self.query:
|
||||
query = "{query}?{self.query}".format(query=query, self=self)
|
||||
if not direct:
|
||||
if self.name and not strip_name:
|
||||
fragment = "#egg={self.name_with_extras}".format(self=self)
|
||||
elif not strip_name and (
|
||||
self.extras and self.scheme and self.scheme.startswith("file")
|
||||
):
|
||||
from .utils import extras_to_string
|
||||
|
||||
fragment = extras_to_string(self.extras)
|
||||
else:
|
||||
fragment = ""
|
||||
query = "{query}{fragment}".format(query=query, fragment=fragment)
|
||||
if self.subdirectory and not strip_subdir:
|
||||
query = "{query}&subdirectory={self.subdirectory}".format(
|
||||
query=query, self=self
|
||||
)
|
||||
host_port_path = self.get_host_port_path(strip_ref=strip_ref)
|
||||
url = "{self.scheme}://{auth}{host_port_path}{query}".format(
|
||||
self=self, auth=auth, host_port_path=host_port_path, query=query
|
||||
)
|
||||
if strip_ssh:
|
||||
from ..utils import strip_ssh_from_git_uri
|
||||
|
||||
url = strip_ssh_from_git_uri(url)
|
||||
if self.name and direct and not strip_name:
|
||||
return "{self.name_with_extras}@ {url}".format(self=self, url=url)
|
||||
return url
|
||||
|
||||
def get_host_port_path(self, strip_ref=False):
|
||||
# type: (bool) -> str
|
||||
host = self.host if self.host else ""
|
||||
if self.port:
|
||||
host = "{host}:{self.port!s}".format(host=host, self=self)
|
||||
path = "{self.path}".format(self=self)
|
||||
if self.ref and not strip_ref:
|
||||
path = "{path}@{self.ref}".format(path=path, self=self)
|
||||
return "{host}{path}".format(host=host, path=path)
|
||||
|
||||
@property
|
||||
def name_with_extras(self):
|
||||
# type: () -> str
|
||||
from .utils import extras_to_string
|
||||
|
||||
if not self.name:
|
||||
return ""
|
||||
extras = extras_to_string(self.extras)
|
||||
return "{self.name}{extras}".format(self=self, extras=extras)
|
||||
|
||||
@property
|
||||
def as_link(self):
|
||||
# type: () -> Link
|
||||
link = pip_shims.shims.Link(
|
||||
self.to_string(escape_password=False, strip_ssh=False, direct=False)
|
||||
)
|
||||
return link
|
||||
|
||||
@property
|
||||
def bare_url(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
strip_name=True,
|
||||
strip_ref=True,
|
||||
strip_subdir=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def url_without_fragment_or_ref(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
strip_name=True,
|
||||
strip_ref=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def url_without_fragment(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
strip_name=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def url_without_ref(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
strip_ref=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def base_url(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False, strip_ssh=self.is_implicit_ssh, direct=False
|
||||
)
|
||||
|
||||
@property
|
||||
def full_url(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=False, strip_ssh=False, direct=False)
|
||||
|
||||
@property
|
||||
def safe_string(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=True, unquote=True)
|
||||
|
||||
@property
|
||||
def unsafe_string(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=False, unquote=True)
|
||||
|
||||
@property
|
||||
def uri_escape(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=False, unquote=False)
|
||||
|
||||
@property
|
||||
def is_vcs(self):
|
||||
# type: () -> bool
|
||||
from ..utils import VCS_SCHEMES
|
||||
|
||||
return self.scheme in VCS_SCHEMES
|
||||
|
||||
@property
|
||||
def is_file_url(self):
|
||||
# type: () -> bool
|
||||
return all([self.scheme, self.scheme == "file"])
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=True, unquote=True)
|
||||
+129
-91
@@ -6,14 +6,12 @@ import os
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
|
||||
from collections import defaultdict
|
||||
from itertools import chain, groupby
|
||||
from operator import attrgetter
|
||||
|
||||
import six
|
||||
import tomlkit
|
||||
|
||||
from attr import validators
|
||||
from first import first
|
||||
from packaging.markers import InvalidMarker, Marker, Op, Value, Variable
|
||||
@@ -25,22 +23,37 @@ from vistir.compat import lru_cache
|
||||
from vistir.misc import dedup
|
||||
from vistir.path import is_valid_url
|
||||
|
||||
|
||||
from ..utils import SCHEME_LIST, VCS_LIST, is_star, add_ssh_scheme_to_git_uri
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..utils import SCHEME_LIST, VCS_LIST, add_ssh_scheme_to_git_uri, is_star
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import Union, Optional, List, Set, Any, TypeVar, Tuple, Sequence, Dict, Text
|
||||
from typing import (
|
||||
Union,
|
||||
Optional,
|
||||
List,
|
||||
Set,
|
||||
Any,
|
||||
TypeVar,
|
||||
Tuple,
|
||||
Sequence,
|
||||
Dict,
|
||||
Text,
|
||||
AnyStr,
|
||||
Match,
|
||||
Iterable,
|
||||
)
|
||||
from attr import _ValidatorType
|
||||
from packaging.requirements import Requirement as PackagingRequirement
|
||||
from pkg_resources import Requirement as PkgResourcesRequirement
|
||||
from pkg_resources.extern.packaging.markers import (
|
||||
Op as PkgResourcesOp, Variable as PkgResourcesVariable,
|
||||
Value as PkgResourcesValue, Marker as PkgResourcesMarker
|
||||
Op as PkgResourcesOp,
|
||||
Variable as PkgResourcesVariable,
|
||||
Value as PkgResourcesValue,
|
||||
Marker as PkgResourcesMarker,
|
||||
)
|
||||
from pip_shims.shims import Link
|
||||
from vistir.compat import Path
|
||||
|
||||
_T = TypeVar("_T")
|
||||
TMarker = Union[Marker, PkgResourcesMarker]
|
||||
TVariable = TypeVar("TVariable", PkgResourcesVariable, Variable)
|
||||
@@ -48,6 +61,8 @@ if MYPY_RUNNING:
|
||||
TOp = TypeVar("TOp", PkgResourcesOp, Op)
|
||||
MarkerTuple = Tuple[TVariable, TOp, TValue]
|
||||
TRequirement = Union[PackagingRequirement, PkgResourcesRequirement]
|
||||
STRING_TYPE = Union[bytes, str, Text]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
|
||||
|
||||
HASH_STRING = " --hash={0}"
|
||||
@@ -63,13 +78,19 @@ NAME_RE = re.compile(NAME_WITH_EXTRAS)
|
||||
SUBDIR_RE = r"(?:[&#]subdirectory=(?P<subdirectory>.*))"
|
||||
URL_NAME = r"(?:#egg={0})".format(NAME_WITH_EXTRAS)
|
||||
REF_RE = r"(?:@(?P<ref>{0}+)?)".format(REF)
|
||||
URL = r"(?P<scheme>[^ ]+://)(?:(?P<host>[^ ]+?\.?{0}+(?P<port>:\d+)?))?(?P<pathsep>[:/])(?P<path>[^ @]+){1}?".format(ALPHA_NUMERIC, REF_RE)
|
||||
PATH_RE = r"(?P<pathsep>[:/])(?P<path>[^ @]+){0}?".format(REF_RE)
|
||||
PASS_RE = r"(?:(?<=:)(?P<password>[^ ]+))"
|
||||
AUTH_RE = r"(?:(?P<username>[^ ]+)[:@]{0}?@)".format(PASS_RE)
|
||||
HOST_RE = r"(?:{0}?(?P<host>[^ ]+?\.?{1}+(?P<port>:\d+)?))?".format(
|
||||
AUTH_RE, ALPHA_NUMERIC
|
||||
)
|
||||
URL = r"(?P<scheme>[^ ]+://){0}{1}".format(HOST_RE, PATH_RE)
|
||||
URL_RE = re.compile(r"{0}(?:{1}?{2}?)?".format(URL, URL_NAME, SUBDIR_RE))
|
||||
DIRECT_URL_RE = re.compile(r"{0}\s?@\s?{1}".format(NAME_WITH_EXTRAS, URL))
|
||||
|
||||
|
||||
def filter_none(k, v):
|
||||
# type: (Text, Any) -> bool
|
||||
# type: (AnyStr, Any) -> bool
|
||||
if v:
|
||||
return True
|
||||
return False
|
||||
@@ -81,16 +102,17 @@ def optional_instance_of(cls):
|
||||
|
||||
|
||||
def create_link(link):
|
||||
# type: (Text) -> Link
|
||||
# type: (AnyStr) -> Link
|
||||
|
||||
if not isinstance(link, six.string_types):
|
||||
raise TypeError("must provide a string to instantiate a new link")
|
||||
from pip_shims.shims import Link
|
||||
|
||||
return Link(link)
|
||||
|
||||
|
||||
def get_url_name(url):
|
||||
# type: (Text) -> Text
|
||||
# type: (AnyStr) -> AnyStr
|
||||
"""
|
||||
Given a url, derive an appropriate name to use in a pipfile.
|
||||
|
||||
@@ -104,11 +126,12 @@ def get_url_name(url):
|
||||
|
||||
|
||||
def init_requirement(name):
|
||||
# type: (Text) -> TRequirement
|
||||
# type: (AnyStr) -> TRequirement
|
||||
|
||||
if not isinstance(name, six.string_types):
|
||||
raise TypeError("must supply a name to generate a requirement")
|
||||
from pkg_resources import Requirement
|
||||
|
||||
req = Requirement.parse(name)
|
||||
req.vcs = None
|
||||
req.local_file = None
|
||||
@@ -118,7 +141,7 @@ def init_requirement(name):
|
||||
|
||||
|
||||
def extras_to_string(extras):
|
||||
# type: (Sequence) -> Text
|
||||
# type: (Iterable[S]) -> S
|
||||
"""Turn a list of extras into a string"""
|
||||
if isinstance(extras, six.string_types):
|
||||
if extras.startswith("["):
|
||||
@@ -127,22 +150,23 @@ def extras_to_string(extras):
|
||||
extras = [extras]
|
||||
if not extras:
|
||||
return ""
|
||||
return "[{0}]".format(",".join(sorted(set(extras))))
|
||||
return "[{0}]".format(",".join(sorted(set(extras)))) # type: ignore
|
||||
|
||||
|
||||
def parse_extras(extras_str):
|
||||
# type: (Text) -> List
|
||||
# type: (AnyStr) -> List[AnyStr]
|
||||
"""
|
||||
Turn a string of extras into a parsed extras list
|
||||
"""
|
||||
|
||||
from pkg_resources import Requirement
|
||||
|
||||
extras = Requirement.parse("fakepkg{0}".format(extras_to_string(extras_str))).extras
|
||||
return sorted(dedup([extra.lower() for extra in extras]))
|
||||
|
||||
|
||||
def specs_to_string(specs):
|
||||
# type: (List[Union[Text, Specifier]]) -> Text
|
||||
# type: (List[Union[STRING_TYPE, Specifier]]) -> AnyStr
|
||||
"""
|
||||
Turn a list of specifier tuples into a string
|
||||
"""
|
||||
@@ -153,20 +177,20 @@ def specs_to_string(specs):
|
||||
try:
|
||||
extras = ",".join(["".join(spec) for spec in specs])
|
||||
except TypeError:
|
||||
extras = ",".join(["".join(spec._spec) for spec in specs])
|
||||
extras = ",".join(["".join(spec._spec) for spec in specs]) # type: ignore
|
||||
return extras
|
||||
return ""
|
||||
|
||||
|
||||
def build_vcs_uri(
|
||||
vcs, # type: Optional[Text]
|
||||
uri, # type: Text
|
||||
name=None, # type: Optional[Text]
|
||||
ref=None, # type: Optional[Text]
|
||||
subdirectory=None, # type: Optional[Text]
|
||||
extras=None # type: Optional[List[Text]]
|
||||
vcs, # type: Optional[S]
|
||||
uri, # type: S
|
||||
name=None, # type: Optional[S]
|
||||
ref=None, # type: Optional[S]
|
||||
subdirectory=None, # type: Optional[S]
|
||||
extras=None, # type: Optional[Iterable[S]]
|
||||
):
|
||||
# type: (...) -> Text
|
||||
# type: (...) -> STRING_TYPE
|
||||
if extras is None:
|
||||
extras = []
|
||||
vcs_start = ""
|
||||
@@ -187,46 +211,55 @@ def build_vcs_uri(
|
||||
|
||||
|
||||
def convert_direct_url_to_url(direct_url):
|
||||
# type: (Text) -> Text
|
||||
# type: (AnyStr) -> AnyStr
|
||||
"""
|
||||
Given a direct url as defined by *PEP 508*, convert to a :class:`~pip_shims.shims.Link`
|
||||
compatible URL by moving the name and extras into an **egg_fragment**.
|
||||
|
||||
:param str direct_url: A pep-508 compliant direct url.
|
||||
:return: A reformatted URL for use with Link objects and :class:`~pip_shims.shims.InstallRequirement` objects.
|
||||
:rtype: Text
|
||||
:rtype: AnyStr
|
||||
"""
|
||||
direct_match = DIRECT_URL_RE.match(direct_url)
|
||||
direct_match = DIRECT_URL_RE.match(direct_url) # type: Optional[Match]
|
||||
if direct_match is None:
|
||||
url_match = URL_RE.match(direct_url)
|
||||
if url_match or is_valid_url(direct_url):
|
||||
return direct_url
|
||||
match_dict = direct_match.groupdict()
|
||||
match_dict = (
|
||||
{}
|
||||
) # type: Dict[STRING_TYPE, Union[Tuple[STRING_TYPE, ...], STRING_TYPE]]
|
||||
if direct_match is not None:
|
||||
match_dict = direct_match.groupdict() # type: ignore
|
||||
if not match_dict:
|
||||
raise ValueError("Failed converting value to normal URL, is it a direct URL? {0!r}".format(direct_url))
|
||||
raise ValueError(
|
||||
"Failed converting value to normal URL, is it a direct URL? {0!r}".format(
|
||||
direct_url
|
||||
)
|
||||
)
|
||||
url_segments = [match_dict.get(s) for s in ("scheme", "host", "path", "pathsep")]
|
||||
url = "".join([s for s in url_segments if s is not None])
|
||||
url = "" # type: STRING_TYPE
|
||||
url = "".join([s for s in url_segments if s is not None]) # type: ignore
|
||||
new_url = build_vcs_uri(
|
||||
None,
|
||||
url,
|
||||
ref=match_dict.get("ref"),
|
||||
name=match_dict.get("name"),
|
||||
extras=match_dict.get("extras"),
|
||||
subdirectory=match_dict.get("subdirectory")
|
||||
subdirectory=match_dict.get("subdirectory"),
|
||||
)
|
||||
return new_url
|
||||
|
||||
|
||||
def convert_url_to_direct_url(url, name=None):
|
||||
# type: (Text, Optional[Text]) -> Text
|
||||
# type: (AnyStr, Optional[AnyStr]) -> AnyStr
|
||||
"""
|
||||
Given a :class:`~pip_shims.shims.Link` compatible URL, convert to a direct url as
|
||||
defined by *PEP 508* by extracting the name and extras from the **egg_fragment**.
|
||||
|
||||
:param Text url: A :class:`~pip_shims.shims.InstallRequirement` compliant URL.
|
||||
:param Optiona[Text] name: A name to use in case the supplied URL doesn't provide one.
|
||||
:param AnyStr url: A :class:`~pip_shims.shims.InstallRequirement` compliant URL.
|
||||
:param Optiona[AnyStr] name: A name to use in case the supplied URL doesn't provide one.
|
||||
:return: A pep-508 compliant direct url.
|
||||
:rtype: Text
|
||||
:rtype: AnyStr
|
||||
|
||||
:raises ValueError: Raised when the URL can't be parsed or a name can't be found.
|
||||
:raises TypeError: When a non-string input is provided.
|
||||
@@ -266,7 +299,7 @@ def convert_url_to_direct_url(url, name=None):
|
||||
|
||||
|
||||
def get_version(pipfile_entry):
|
||||
# type: (Union[Text, Dict[Text, bool, List[Text]]]) -> Text
|
||||
# type: (Union[STRING_TYPE, Dict[STRING_TYPE, Union[STRING_TYPE, bool, Iterable[STRING_TYPE]]]]) -> STRING_TYPE
|
||||
if str(pipfile_entry) == "{}" or is_star(pipfile_entry):
|
||||
return ""
|
||||
|
||||
@@ -287,7 +320,7 @@ def strip_extras_markers_from_requirement(req):
|
||||
*extra == 'name'*, strip out the extras from the markers and return the cleaned
|
||||
requirement
|
||||
|
||||
:param PackagingRequirement req: A pacakaging requirement to clean
|
||||
:param PackagingRequirement req: A packaging requirement to clean
|
||||
:return: A cleaned requirement
|
||||
:rtype: PackagingRequirement
|
||||
"""
|
||||
@@ -327,8 +360,9 @@ def _strip_extras_markers(marker):
|
||||
|
||||
@lru_cache()
|
||||
def get_setuptools_version():
|
||||
# type: () -> Optional[Text]
|
||||
# type: () -> Optional[STRING_TYPE]
|
||||
import pkg_resources
|
||||
|
||||
setuptools_dist = pkg_resources.get_distribution(
|
||||
pkg_resources.Requirement("setuptools")
|
||||
)
|
||||
@@ -336,7 +370,7 @@ def get_setuptools_version():
|
||||
|
||||
|
||||
def get_default_pyproject_backend():
|
||||
# type: () -> Text
|
||||
# type: () -> STRING_TYPE
|
||||
st_version = get_setuptools_version()
|
||||
if st_version is not None:
|
||||
parsed_st_version = parse_version(st_version)
|
||||
@@ -346,19 +380,20 @@ def get_default_pyproject_backend():
|
||||
|
||||
|
||||
def get_pyproject(path):
|
||||
# type: (Union[Text, Path]) -> Tuple[List[Text], Text]
|
||||
# type: (Union[STRING_TYPE, Path]) -> Optional[Tuple[List[STRING_TYPE], STRING_TYPE]]
|
||||
"""
|
||||
Given a base path, look for the corresponding ``pyproject.toml`` file and return its
|
||||
build_requires and build_backend.
|
||||
|
||||
:param Text path: The root path of the project, should be a directory (will be truncated)
|
||||
:param AnyStr path: The root path of the project, should be a directory (will be truncated)
|
||||
:return: A 2 tuple of build requirements and the build backend
|
||||
:rtype: Tuple[List[Text], Text]
|
||||
:rtype: Optional[Tuple[List[AnyStr], AnyStr]]
|
||||
"""
|
||||
|
||||
if not path:
|
||||
return
|
||||
from vistir.compat import Path
|
||||
|
||||
if not isinstance(path, Path):
|
||||
path = Path(path)
|
||||
if not path.is_dir():
|
||||
@@ -382,19 +417,16 @@ def get_pyproject(path):
|
||||
else:
|
||||
requires = ["setuptools>=40.8", "wheel"]
|
||||
backend = get_default_pyproject_backend()
|
||||
build_system = {
|
||||
"requires": requires,
|
||||
"build-backend": backend
|
||||
}
|
||||
build_system = {"requires": requires, "build-backend": backend}
|
||||
pyproject_data["build_system"] = build_system
|
||||
else:
|
||||
requires = build_system.get("requires", ["setuptools>=40.8", "wheel"])
|
||||
backend = build_system.get("build-backend", get_default_pyproject_backend())
|
||||
return (requires, backend)
|
||||
return requires, backend
|
||||
|
||||
|
||||
def split_markers_from_line(line):
|
||||
# type: (Text) -> Tuple[Text, Optional[Text]]
|
||||
# type: (AnyStr) -> Tuple[AnyStr, Optional[AnyStr]]
|
||||
"""Split markers from a dependency"""
|
||||
if not any(line.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
|
||||
marker_sep = ";"
|
||||
@@ -408,9 +440,10 @@ def split_markers_from_line(line):
|
||||
|
||||
|
||||
def split_vcs_method_from_uri(uri):
|
||||
# type: (Text) -> Tuple[Optional[Text], Text]
|
||||
# type: (AnyStr) -> Tuple[Optional[STRING_TYPE], STRING_TYPE]
|
||||
"""Split a vcs+uri formatted uri into (vcs, uri)"""
|
||||
vcs_start = "{0}+"
|
||||
vcs = None # type: Optional[STRING_TYPE]
|
||||
vcs = first([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))])
|
||||
if vcs:
|
||||
vcs, uri = uri.split("+", 1)
|
||||
@@ -418,14 +451,14 @@ def split_vcs_method_from_uri(uri):
|
||||
|
||||
|
||||
def split_ref_from_uri(uri):
|
||||
# type: (Text) -> Tuple[Text, Optional[Text]]
|
||||
# type: (AnyStr) -> Tuple[AnyStr, Optional[AnyStr]]
|
||||
"""
|
||||
Given a path or URI, check for a ref and split it from the path if it is present,
|
||||
returning a tuple of the original input and the ref or None.
|
||||
|
||||
:param Text uri: The path or URI to split
|
||||
:param AnyStr uri: The path or URI to split
|
||||
:returns: A 2-tuple of the path or URI and the ref
|
||||
:rtype: Tuple[Text, Optional[Text]]
|
||||
:rtype: Tuple[AnyStr, Optional[AnyStr]]
|
||||
"""
|
||||
if not isinstance(uri, six.string_types):
|
||||
raise TypeError("Expected a string, received {0!r}".format(uri))
|
||||
@@ -474,14 +507,14 @@ def key_from_ireq(ireq):
|
||||
|
||||
def key_from_req(req):
|
||||
"""Get an all-lowercase version of the requirement's name."""
|
||||
if hasattr(req, 'key'):
|
||||
if hasattr(req, "key"):
|
||||
# from pkg_resources, such as installed dists for pip-sync
|
||||
key = req.key
|
||||
else:
|
||||
# from packaging, such as install requirements from requirements.txt
|
||||
key = req.name
|
||||
|
||||
key = key.replace('_', '-').lower()
|
||||
key = key.replace("_", "-").lower()
|
||||
return key
|
||||
|
||||
|
||||
@@ -494,8 +527,8 @@ def _requirement_to_str_lowercase_name(requirement):
|
||||
modified to lowercase the dependency name.
|
||||
|
||||
Previously, we were invoking the original Requirement.__str__ method and
|
||||
lowercasing the entire result, which would lowercase the name, *and* other,
|
||||
important stuff that should not be lowercased (such as the marker). See
|
||||
lower-casing the entire result, which would lowercase the name, *and* other,
|
||||
important stuff that should not be lower-cased (such as the marker). See
|
||||
this issue for more information: https://github.com/pypa/pipenv/issues/2113.
|
||||
"""
|
||||
|
||||
@@ -523,17 +556,17 @@ def format_requirement(ireq):
|
||||
"""
|
||||
|
||||
if ireq.editable:
|
||||
line = '-e {}'.format(ireq.link)
|
||||
line = "-e {}".format(ireq.link)
|
||||
else:
|
||||
line = _requirement_to_str_lowercase_name(ireq.req)
|
||||
|
||||
if str(ireq.req.marker) != str(ireq.markers):
|
||||
if not ireq.req.marker:
|
||||
line = '{}; {}'.format(line, ireq.markers)
|
||||
line = "{}; {}".format(line, ireq.markers)
|
||||
else:
|
||||
name, markers = line.split(";", 1)
|
||||
markers = markers.strip()
|
||||
line = '{}; ({}) and ({})'.format(name, markers, ireq.markers)
|
||||
line = "{}; ({}) and ({})".format(name, markers, ireq.markers)
|
||||
|
||||
return line
|
||||
|
||||
@@ -546,7 +579,7 @@ def format_specifier(ireq):
|
||||
# TODO: Ideally, this is carried over to the pip library itself
|
||||
specs = ireq.specifier._specs if ireq.req is not None else []
|
||||
specs = sorted(specs, key=lambda x: x._spec[1])
|
||||
return ','.join(str(s) for s in specs) or '<any>'
|
||||
return ",".join(str(s) for s in specs) or "<any>"
|
||||
|
||||
|
||||
def get_pinned_version(ireq):
|
||||
@@ -573,9 +606,7 @@ def get_pinned_version(ireq):
|
||||
try:
|
||||
specifier = ireq.specifier
|
||||
except AttributeError:
|
||||
raise TypeError("Expected InstallRequirement, not {}".format(
|
||||
type(ireq).__name__,
|
||||
))
|
||||
raise TypeError("Expected InstallRequirement, not {}".format(type(ireq).__name__))
|
||||
|
||||
if ireq.editable:
|
||||
raise ValueError("InstallRequirement is editable")
|
||||
@@ -585,10 +616,8 @@ def get_pinned_version(ireq):
|
||||
raise ValueError("InstallRequirement has multiple specifications")
|
||||
|
||||
op, version = next(iter(specifier._specs))._spec
|
||||
if op not in ('==', '===') or version.endswith('.*'):
|
||||
raise ValueError("InstallRequirement not pinned (is {0!r})".format(
|
||||
op + version,
|
||||
))
|
||||
if op not in ("==", "===") or version.endswith(".*"):
|
||||
raise ValueError("InstallRequirement not pinned (is {0!r})".format(op + version))
|
||||
|
||||
return version
|
||||
|
||||
@@ -624,7 +653,7 @@ def as_tuple(ireq):
|
||||
"""
|
||||
|
||||
if not is_pinned_requirement(ireq):
|
||||
raise TypeError('Expected a pinned InstallRequirement, got {}'.format(ireq))
|
||||
raise TypeError("Expected a pinned InstallRequirement, got {}".format(ireq))
|
||||
|
||||
name = key_from_req(ireq.req)
|
||||
version = first(ireq.specifier._specs)._spec[1]
|
||||
@@ -686,9 +715,9 @@ def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False):
|
||||
|
||||
if keyval is None:
|
||||
if key is None:
|
||||
keyval = (lambda v: v)
|
||||
keyval = lambda v: v
|
||||
else:
|
||||
keyval = (lambda v: (key(v), v))
|
||||
keyval = lambda v: (key(v), v)
|
||||
|
||||
if unique:
|
||||
return dict(keyval(v) for v in values)
|
||||
@@ -712,7 +741,7 @@ def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False):
|
||||
|
||||
def name_from_req(req):
|
||||
"""Get the name of the requirement"""
|
||||
if hasattr(req, 'project_name'):
|
||||
if hasattr(req, "project_name"):
|
||||
# from pkg_resources, such as installed dists for pip-sync
|
||||
return req.project_name
|
||||
else:
|
||||
@@ -742,6 +771,7 @@ def make_install_requirement(name, version, extras, markers, constraint=False):
|
||||
|
||||
# If no extras are specified, the extras string is blank
|
||||
from pip_shims.shims import install_req_from_line
|
||||
|
||||
extras_string = ""
|
||||
if extras:
|
||||
# Sort extras for stability
|
||||
@@ -749,12 +779,13 @@ def make_install_requirement(name, version, extras, markers, constraint=False):
|
||||
|
||||
if not markers:
|
||||
return install_req_from_line(
|
||||
str('{}{}=={}'.format(name, extras_string, version)),
|
||||
constraint=constraint)
|
||||
str("{}{}=={}".format(name, extras_string, version)), constraint=constraint
|
||||
)
|
||||
else:
|
||||
return install_req_from_line(
|
||||
str('{}{}=={}; {}'.format(name, extras_string, version, str(markers))),
|
||||
constraint=constraint)
|
||||
str("{}{}=={}; {}".format(name, extras_string, version, str(markers))),
|
||||
constraint=constraint,
|
||||
)
|
||||
|
||||
|
||||
def version_from_ireq(ireq):
|
||||
@@ -772,9 +803,10 @@ def version_from_ireq(ireq):
|
||||
def clean_requires_python(candidates):
|
||||
"""Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes."""
|
||||
all_candidates = []
|
||||
sys_version = '.'.join(map(str, sys.version_info[:3]))
|
||||
sys_version = ".".join(map(str, sys.version_info[:3]))
|
||||
from packaging.version import parse as parse_version
|
||||
py_version = parse_version(os.environ.get('PIP_PYTHON_VERSION', sys_version))
|
||||
|
||||
py_version = parse_version(os.environ.get("PIP_PYTHON_VERSION", sys_version))
|
||||
for c in candidates:
|
||||
from_location = attrgetter("location.requires_python")
|
||||
requires_python = getattr(c, "requires_python", from_location(c))
|
||||
@@ -782,7 +814,9 @@ def clean_requires_python(candidates):
|
||||
# Old specifications had people setting this to single digits
|
||||
# which is effectively the same as '>=digit,<digit+1'
|
||||
if requires_python.isdigit():
|
||||
requires_python = '>={0},<{1}'.format(requires_python, int(requires_python) + 1)
|
||||
requires_python = ">={0},<{1}".format(
|
||||
requires_python, int(requires_python) + 1
|
||||
)
|
||||
try:
|
||||
specifierset = SpecifierSet(requires_python)
|
||||
except InvalidSpecifier:
|
||||
@@ -796,7 +830,8 @@ def clean_requires_python(candidates):
|
||||
|
||||
def fix_requires_python_marker(requires_python):
|
||||
from packaging.requirements import Requirement as PackagingRequirement
|
||||
marker_str = ''
|
||||
|
||||
marker_str = ""
|
||||
if any(requires_python.startswith(op) for op in Specifier._operators.keys()):
|
||||
spec_dict = defaultdict(set)
|
||||
# We are checking first if we have leading specifier operator
|
||||
@@ -804,26 +839,28 @@ def fix_requires_python_marker(requires_python):
|
||||
specifierset = list(SpecifierSet(requires_python))
|
||||
# for multiple specifiers, the correct way to represent that in
|
||||
# a specifierset is `Requirement('fakepkg; python_version<"3.0,>=2.6"')`
|
||||
marker_key = Variable('python_version')
|
||||
marker_key = Variable("python_version")
|
||||
for spec in specifierset:
|
||||
operator, val = spec._spec
|
||||
cleaned_val = Value(val).serialize().replace('"', "")
|
||||
spec_dict[Op(operator).serialize()].add(cleaned_val)
|
||||
marker_str = ' and '.join([
|
||||
"{0}{1}'{2}'".format(marker_key.serialize(), op, ','.join(vals))
|
||||
for op, vals in spec_dict.items()
|
||||
])
|
||||
marker_to_add = PackagingRequirement('fakepkg; {0}'.format(marker_str)).marker
|
||||
marker_str = " and ".join(
|
||||
[
|
||||
"{0}{1}'{2}'".format(marker_key.serialize(), op, ",".join(vals))
|
||||
for op, vals in spec_dict.items()
|
||||
]
|
||||
)
|
||||
marker_to_add = PackagingRequirement("fakepkg; {0}".format(marker_str)).marker
|
||||
return marker_to_add
|
||||
|
||||
|
||||
def normalize_name(pkg):
|
||||
# type: (Text) -> Text
|
||||
# type: (AnyStr) -> AnyStr
|
||||
"""Given a package name, return its normalized, non-canonicalized form.
|
||||
|
||||
:param Text pkg: The name of a package
|
||||
:param AnyStr pkg: The name of a package
|
||||
:return: A normalized package name
|
||||
:rtype: Text
|
||||
:rtype: AnyStr
|
||||
"""
|
||||
|
||||
assert isinstance(pkg, six.string_types)
|
||||
@@ -831,12 +868,12 @@ def normalize_name(pkg):
|
||||
|
||||
|
||||
def get_name_variants(pkg):
|
||||
# type: (Text) -> Set[Text]
|
||||
# type: (STRING_TYPE) -> Set[STRING_TYPE]
|
||||
"""
|
||||
Given a packager name, get the variants of its name for both the canonicalized
|
||||
and "safe" forms.
|
||||
|
||||
:param Text pkg: The package to lookup
|
||||
:param AnyStr pkg: The package to lookup
|
||||
:returns: A list of names.
|
||||
:rtype: Set
|
||||
"""
|
||||
@@ -845,6 +882,7 @@ def get_name_variants(pkg):
|
||||
raise TypeError("must provide a string to derive package names")
|
||||
from pkg_resources import safe_name
|
||||
from packaging.utils import canonicalize_name
|
||||
|
||||
pkg = pkg.lower()
|
||||
names = {safe_name(pkg), canonicalize_name(pkg), pkg.replace("-", "_")}
|
||||
return names
|
||||
|
||||
+72
-38
@@ -4,27 +4,53 @@ from __future__ import absolute_import, print_function
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
|
||||
import six
|
||||
import sys
|
||||
import tomlkit
|
||||
import vistir
|
||||
|
||||
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc")) # type: ignore # noqa
|
||||
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc")) # type: ignore # noqa
|
||||
six.add_move(six.MovedAttribute("Set", "collections", "collections.abc")) # type: ignore # noqa
|
||||
six.add_move(six.MovedAttribute("ItemsView", "collections", "collections.abc")) # type: ignore # noqa
|
||||
from six.moves import Mapping, Sequence, Set, ItemsView # type: ignore # noqa
|
||||
from six.moves.urllib.parse import urlparse, urlsplit, urlunparse
|
||||
|
||||
import pip_shims.shims
|
||||
import six
|
||||
import tomlkit
|
||||
import vistir
|
||||
from six.moves.urllib.parse import urlparse, urlsplit, urlunparse
|
||||
from vistir.compat import Path
|
||||
from vistir.path import is_valid_url, ensure_mkdir_p, create_tracked_tempdir
|
||||
from vistir.path import create_tracked_tempdir, ensure_mkdir_p, is_valid_url
|
||||
|
||||
from .environment import MYPY_RUNNING
|
||||
|
||||
# fmt: off
|
||||
six.add_move(
|
||||
six.MovedAttribute("Mapping", "collections", "collections.abc")
|
||||
) # type: ignore # noqa # isort:skip
|
||||
six.add_move(
|
||||
six.MovedAttribute("Sequence", "collections", "collections.abc")
|
||||
) # type: ignore # noqa # isort:skip
|
||||
six.add_move(
|
||||
six.MovedAttribute("Set", "collections", "collections.abc")
|
||||
) # type: ignore # noqa # isort:skip
|
||||
six.add_move(
|
||||
six.MovedAttribute("ItemsView", "collections", "collections.abc")
|
||||
) # type: ignore # noqa
|
||||
from six.moves import ItemsView, Mapping, Sequence, Set # type: ignore # noqa # isort:skip
|
||||
# fmt: on
|
||||
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import Dict, Any, Optional, Union, Tuple, List, Iterable, Generator, Text
|
||||
from typing import (
|
||||
Dict,
|
||||
Any,
|
||||
Optional,
|
||||
Union,
|
||||
Tuple,
|
||||
List,
|
||||
Iterable,
|
||||
Generator,
|
||||
Text,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
STRING_TYPE = Union[bytes, str, Text]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
PipfileEntryType = Union[STRING_TYPE, bool, Tuple[STRING_TYPE], List[STRING_TYPE]]
|
||||
PipfileType = Union[STRING_TYPE, Dict[STRING_TYPE, PipfileEntryType]]
|
||||
|
||||
|
||||
VCS_LIST = ("git", "svn", "hg", "bzr")
|
||||
@@ -74,7 +100,7 @@ VCS_SCHEMES = [
|
||||
|
||||
|
||||
def is_installable_dir(path):
|
||||
# type: (Text) -> bool
|
||||
# type: (STRING_TYPE) -> bool
|
||||
if pip_shims.shims.is_installable_dir(path):
|
||||
return True
|
||||
pyproject_path = os.path.join(path, "pyproject.toml")
|
||||
@@ -88,7 +114,7 @@ def is_installable_dir(path):
|
||||
|
||||
|
||||
def strip_ssh_from_git_uri(uri):
|
||||
# type: (Text) -> Text
|
||||
# type: (S) -> S
|
||||
"""Return git+ssh:// formatted URI to git+git@ format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
if "git+ssh://" in uri:
|
||||
@@ -105,8 +131,8 @@ def strip_ssh_from_git_uri(uri):
|
||||
|
||||
|
||||
def add_ssh_scheme_to_git_uri(uri):
|
||||
# type: (Text) -> Text
|
||||
"""Cleans VCS uris from pip format"""
|
||||
# type: (S) -> S
|
||||
"""Cleans VCS uris from pipenv.patched.notpip format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
# Add scheme for parsing purposes, this is also what pip does
|
||||
if uri.startswith("git+") and "://" not in uri:
|
||||
@@ -120,7 +146,7 @@ def add_ssh_scheme_to_git_uri(uri):
|
||||
|
||||
|
||||
def is_vcs(pipfile_entry):
|
||||
# type: (Union[Text, Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]]) -> bool
|
||||
# type: (PipfileType) -> bool
|
||||
"""Determine if dictionary entry from Pipfile is for a vcs dependency."""
|
||||
if isinstance(pipfile_entry, Mapping):
|
||||
return any(key for key in pipfile_entry.keys() if key in VCS_LIST)
|
||||
@@ -135,7 +161,7 @@ def is_vcs(pipfile_entry):
|
||||
|
||||
|
||||
def is_editable(pipfile_entry):
|
||||
# type: (Union[Text, Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]]) -> bool
|
||||
# type: (PipfileType) -> bool
|
||||
if isinstance(pipfile_entry, Mapping):
|
||||
return pipfile_entry.get("editable", False) is True
|
||||
if isinstance(pipfile_entry, six.string_types):
|
||||
@@ -144,7 +170,7 @@ def is_editable(pipfile_entry):
|
||||
|
||||
|
||||
def multi_split(s, split):
|
||||
# type: (Text, Iterable[Text]) -> List[Text]
|
||||
# type: (S, Iterable[S]) -> List[S]
|
||||
"""Splits on multiple given separators."""
|
||||
for r in split:
|
||||
s = s.replace(r, "|")
|
||||
@@ -152,14 +178,14 @@ def multi_split(s, split):
|
||||
|
||||
|
||||
def is_star(val):
|
||||
# type: (Union[Text, Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]]) -> bool
|
||||
# type: (PipfileType) -> bool
|
||||
return (isinstance(val, six.string_types) and val == "*") or (
|
||||
isinstance(val, Mapping) and val.get("version", "") == "*"
|
||||
)
|
||||
|
||||
|
||||
def convert_entry_to_path(path):
|
||||
# type: (Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]) -> Text
|
||||
# type: (Dict[S, Union[S, bool, Tuple[S], List[S]]]) -> S
|
||||
"""Convert a pipfile entry to a string"""
|
||||
|
||||
if not isinstance(path, Mapping):
|
||||
@@ -177,7 +203,7 @@ def convert_entry_to_path(path):
|
||||
|
||||
|
||||
def is_installable_file(path):
|
||||
# type: (Union[Text, Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]]) -> bool
|
||||
# type: (PipfileType) -> bool
|
||||
"""Determine if a path can potentially be installed"""
|
||||
from packaging import specifiers
|
||||
|
||||
@@ -196,7 +222,11 @@ def is_installable_file(path):
|
||||
return False
|
||||
|
||||
parsed = urlparse(path)
|
||||
is_local = (not parsed.scheme or parsed.scheme == "file" or (len(parsed.scheme) == 1 and os.name == "nt"))
|
||||
is_local = (
|
||||
not parsed.scheme
|
||||
or parsed.scheme == "file"
|
||||
or (len(parsed.scheme) == 1 and os.name == "nt")
|
||||
)
|
||||
if parsed.scheme and parsed.scheme == "file":
|
||||
path = vistir.compat.fs_decode(vistir.path.url_to_path(path))
|
||||
normalized_path = vistir.path.normalize_path(path)
|
||||
@@ -204,7 +234,9 @@ def is_installable_file(path):
|
||||
return False
|
||||
|
||||
is_archive = pip_shims.shims.is_archive_file(normalized_path)
|
||||
is_local_project = os.path.isdir(normalized_path) and is_installable_dir(normalized_path)
|
||||
is_local_project = os.path.isdir(normalized_path) and is_installable_dir(
|
||||
normalized_path
|
||||
)
|
||||
if is_local and is_local_project or is_archive:
|
||||
return True
|
||||
|
||||
@@ -217,11 +249,13 @@ def is_installable_file(path):
|
||||
def get_dist_metadata(dist):
|
||||
import pkg_resources
|
||||
from email.parser import FeedParser
|
||||
if (isinstance(dist, pkg_resources.DistInfoDistribution) and
|
||||
dist.has_metadata('METADATA')):
|
||||
metadata = dist.get_metadata('METADATA')
|
||||
elif dist.has_metadata('PKG-INFO'):
|
||||
metadata = dist.get_metadata('PKG-INFO')
|
||||
|
||||
if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata(
|
||||
"METADATA"
|
||||
):
|
||||
metadata = dist.get_metadata("METADATA")
|
||||
elif dist.has_metadata("PKG-INFO"):
|
||||
metadata = dist.get_metadata("PKG-INFO")
|
||||
else:
|
||||
metadata = ""
|
||||
|
||||
@@ -231,7 +265,7 @@ def get_dist_metadata(dist):
|
||||
|
||||
|
||||
def get_setup_paths(base_path, subdirectory=None):
|
||||
# type: (Text, Optional[Text]) -> Dict[Text, Optional[Text]]
|
||||
# type: (S, Optional[S]) -> Dict[S, Optional[S]]
|
||||
if base_path is None:
|
||||
raise TypeError("must provide a path to derive setup paths from")
|
||||
setup_py = os.path.join(base_path, "setup.py")
|
||||
@@ -251,12 +285,12 @@ def get_setup_paths(base_path, subdirectory=None):
|
||||
return {
|
||||
"setup_py": setup_py if os.path.exists(setup_py) else None,
|
||||
"setup_cfg": setup_cfg if os.path.exists(setup_cfg) else None,
|
||||
"pyproject_toml": pyproject_toml if os.path.exists(pyproject_toml) else None
|
||||
"pyproject_toml": pyproject_toml if os.path.exists(pyproject_toml) else None,
|
||||
}
|
||||
|
||||
|
||||
def prepare_pip_source_args(sources, pip_args=None):
|
||||
# type: (List[Dict[Text, Union[Text, bool]]], Optional[List[Text]]) -> List[Text]
|
||||
# type: (List[Dict[S, Union[S, bool]]], Optional[List[S]]) -> List[S]
|
||||
if pip_args is None:
|
||||
pip_args = []
|
||||
if sources:
|
||||
@@ -264,7 +298,9 @@ def prepare_pip_source_args(sources, pip_args=None):
|
||||
pip_args.extend(["-i", sources[0]["url"]]) # type: ignore
|
||||
# Trust the host if it's not verified.
|
||||
if not sources[0].get("verify_ssl", True):
|
||||
pip_args.extend(["--trusted-host", urlparse(sources[0]["url"]).hostname]) # type: ignore
|
||||
pip_args.extend(
|
||||
["--trusted-host", urlparse(sources[0]["url"]).hostname]
|
||||
) # type: ignore
|
||||
# Add additional sources as extra indexes.
|
||||
if len(sources) > 1:
|
||||
for source in sources[1:]:
|
||||
@@ -284,7 +320,7 @@ def _ensure_dir(path):
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ensure_setup_py(base):
|
||||
# type: (Text) -> Generator[None, None, None]
|
||||
# type: (STRING_TYPE) -> Generator[None, None, None]
|
||||
if not base:
|
||||
base = create_tracked_tempdir(prefix="requirementslib-setup")
|
||||
base_dir = Path(base)
|
||||
@@ -413,9 +449,7 @@ def get_path(root, path, default=_UNSET):
|
||||
cur = cur[seg]
|
||||
except (ValueError, KeyError, IndexError, TypeError):
|
||||
if not getattr(cur, "__iter__", None):
|
||||
exc = TypeError(
|
||||
"%r object is not indexable" % type(cur).__name__
|
||||
)
|
||||
exc = TypeError("%r object is not indexable" % type(cur).__name__)
|
||||
raise PathAccessError(exc, seg, path)
|
||||
except PathAccessError:
|
||||
if default is _UNSET:
|
||||
|
||||
Vendored
+10
-10
@@ -13,22 +13,22 @@ python-dotenv==0.10.1
|
||||
first==2.0.1
|
||||
iso8601==0.1.12
|
||||
jinja2==2.10
|
||||
markupsafe==1.0
|
||||
parse==1.9.0
|
||||
markupsafe==1.1.1
|
||||
parse==1.11.1
|
||||
pathlib2==2.3.3
|
||||
scandir==1.9
|
||||
pipdeptree==0.13.1
|
||||
pipdeptree==0.13.2
|
||||
pipreqs==0.4.9
|
||||
docopt==0.6.2
|
||||
yarg==0.1.9
|
||||
pythonfinder==1.1.10
|
||||
pythonfinder==1.2.0
|
||||
requests==2.21.0
|
||||
chardet==3.0.4
|
||||
idna==2.8
|
||||
urllib3==1.24.1
|
||||
certifi==2018.11.29
|
||||
requirementslib==1.4.0
|
||||
attrs==18.2.0
|
||||
requirementslib==1.4.2
|
||||
attrs==19.1.0
|
||||
distlib==0.2.8
|
||||
packaging==19.0
|
||||
pyparsing==2.3.1
|
||||
@@ -40,14 +40,14 @@ semver==2.8.1
|
||||
shutilwhich==1.1.0
|
||||
toml==0.10.0
|
||||
cached-property==1.5.1
|
||||
vistir==0.3.0
|
||||
vistir==0.3.1
|
||||
pip-shims==0.3.2
|
||||
enum34==1.1.6
|
||||
yaspin==0.14.0
|
||||
yaspin==0.14.1
|
||||
cerberus==1.2
|
||||
git+https://github.com/sarugaku/passa.git@master#egg=passa
|
||||
cursor==1.2.0
|
||||
resolvelib==0.2.2
|
||||
backports.functools_lru_cache==1.5
|
||||
pep517==0.5.0
|
||||
pytoml==0.1.20
|
||||
git+https://github.com/sarugaku/passa.git@master#egg=passa
|
||||
orderedmultidict==1.0
|
||||
|
||||
Vendored
+16
-14
@@ -3,39 +3,39 @@ from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .compat import (
|
||||
NamedTemporaryFile,
|
||||
StringIO,
|
||||
TemporaryDirectory,
|
||||
partialmethod,
|
||||
to_native_string,
|
||||
StringIO,
|
||||
)
|
||||
from .contextmanagers import (
|
||||
atomic_open_for_write,
|
||||
cd,
|
||||
open_file,
|
||||
replaced_stream,
|
||||
spinner,
|
||||
temp_environ,
|
||||
temp_path,
|
||||
spinner,
|
||||
replaced_stream
|
||||
)
|
||||
from .cursor import hide_cursor, show_cursor
|
||||
from .misc import (
|
||||
StreamWrapper,
|
||||
chunked,
|
||||
decode_for_output,
|
||||
divide,
|
||||
get_wrapped_stream,
|
||||
load_path,
|
||||
partialclass,
|
||||
run,
|
||||
shell_escape,
|
||||
decode_for_output,
|
||||
to_text,
|
||||
to_bytes,
|
||||
take,
|
||||
chunked,
|
||||
divide,
|
||||
get_wrapped_stream,
|
||||
StreamWrapper
|
||||
to_bytes,
|
||||
to_text,
|
||||
)
|
||||
from .path import mkdir_p, rmtree, create_tracked_tempdir, create_tracked_tempfile
|
||||
from .path import create_tracked_tempdir, create_tracked_tempfile, mkdir_p, rmtree
|
||||
from .spin import create_spinner
|
||||
|
||||
|
||||
__version__ = '0.3.0'
|
||||
__version__ = "0.3.1"
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -67,5 +67,7 @@ __all__ = [
|
||||
"StringIO",
|
||||
"get_wrapped_stream",
|
||||
"StreamWrapper",
|
||||
"replaced_stream"
|
||||
"replaced_stream",
|
||||
"show_cursor",
|
||||
"hide_cursor",
|
||||
]
|
||||
|
||||
+1
-5
@@ -4,8 +4,4 @@ from __future__ import absolute_import, unicode_literals
|
||||
from .functools import partialmethod
|
||||
from .tempfile import NamedTemporaryFile
|
||||
|
||||
|
||||
__all__ = [
|
||||
"NamedTemporaryFile",
|
||||
"partialmethod"
|
||||
]
|
||||
__all__ = ["NamedTemporaryFile", "partialmethod"]
|
||||
|
||||
+12
-12
@@ -3,8 +3,7 @@ from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from functools import partial
|
||||
|
||||
|
||||
__all__ = ["partialmethod",]
|
||||
__all__ = ["partialmethod"]
|
||||
|
||||
|
||||
class partialmethod(object):
|
||||
@@ -16,8 +15,7 @@ class partialmethod(object):
|
||||
|
||||
def __init__(self, func, *args, **keywords):
|
||||
if not callable(func) and not hasattr(func, "__get__"):
|
||||
raise TypeError("{!r} is not callable or a descriptor"
|
||||
.format(func))
|
||||
raise TypeError("{!r} is not callable or a descriptor".format(func))
|
||||
|
||||
# func could be a descriptor like classmethod which isn't callable,
|
||||
# so we can't inherit from partial (it verifies func is callable)
|
||||
@@ -36,26 +34,28 @@ class partialmethod(object):
|
||||
|
||||
def __repr__(self):
|
||||
args = ", ".join(map(repr, self.args))
|
||||
keywords = ", ".join("{}={!r}".format(k, v)
|
||||
for k, v in self.keywords.items())
|
||||
keywords = ", ".join("{}={!r}".format(k, v) for k, v in self.keywords.items())
|
||||
format_string = "{module}.{cls}({func}, {args}, {keywords})"
|
||||
return format_string.format(module=self.__class__.__module__,
|
||||
cls=self.__class__.__qualname__,
|
||||
func=self.func,
|
||||
args=args,
|
||||
keywords=keywords)
|
||||
return format_string.format(
|
||||
module=self.__class__.__module__,
|
||||
cls=self.__class__.__qualname__,
|
||||
func=self.func,
|
||||
args=args,
|
||||
keywords=keywords,
|
||||
)
|
||||
|
||||
def _make_unbound_method(self):
|
||||
def _method(*args, **keywords):
|
||||
call_keywords = self.keywords.copy()
|
||||
call_keywords.update(keywords)
|
||||
if len(args) > 1:
|
||||
cls_or_self, rest = args[0], tuple(args[1:],)
|
||||
cls_or_self, rest = args[0], tuple(args[1:])
|
||||
else:
|
||||
cls_or_self = args[0]
|
||||
rest = tuple()
|
||||
call_args = (cls_or_self,) + self.args + tuple(rest)
|
||||
return self.func(*call_args, **call_keywords)
|
||||
|
||||
_method.__isabstractmethod__ = self.__isabstractmethod__
|
||||
_method._partialmethod = self
|
||||
return _method
|
||||
|
||||
+5
-8
@@ -5,7 +5,6 @@ import functools
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
from tempfile import _bin_openflags, _mkstemp_inner, gettempdir
|
||||
|
||||
import six
|
||||
@@ -175,7 +174,7 @@ def NamedTemporaryFile(
|
||||
prefix=None,
|
||||
dir=None,
|
||||
delete=True,
|
||||
wrapper_class_override=None
|
||||
wrapper_class_override=None,
|
||||
):
|
||||
"""Create and return a temporary file.
|
||||
Arguments:
|
||||
@@ -203,13 +202,11 @@ def NamedTemporaryFile(
|
||||
else:
|
||||
(fd, name) = _mkstemp_inner(dir, prefix, suffix, flags, output_type)
|
||||
try:
|
||||
file = io.open(
|
||||
fd, mode, buffering=buffering, newline=newline, encoding=encoding
|
||||
)
|
||||
file = io.open(fd, mode, buffering=buffering, newline=newline, encoding=encoding)
|
||||
if wrapper_class_override is not None:
|
||||
return type(
|
||||
str("_TempFileWrapper"), (wrapper_class_override, object), {}
|
||||
)(file, name, delete)
|
||||
return type(str("_TempFileWrapper"), (wrapper_class_override, object), {})(
|
||||
file, name, delete
|
||||
)
|
||||
else:
|
||||
return _TemporaryFileWrapper(file, name, delete)
|
||||
|
||||
|
||||
Vendored
+11
-4
@@ -1,12 +1,12 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import itertools
|
||||
import re
|
||||
import shlex
|
||||
|
||||
import six
|
||||
|
||||
|
||||
__all__ = ["ScriptEmptyError", "Script"]
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@ class ScriptEmptyError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def _quote_if_contains(value, pattern):
|
||||
if next(re.finditer(pattern, value), None):
|
||||
return '"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', value))
|
||||
return value
|
||||
|
||||
|
||||
class Script(object):
|
||||
"""Parse a script line (in Pipfile's [scripts] section).
|
||||
|
||||
@@ -72,7 +78,8 @@ class Script(object):
|
||||
See also: https://docs.python.org/3/library/subprocess.html#converting-argument-sequence
|
||||
"""
|
||||
return " ".join(
|
||||
arg if not next(re.finditer(r'\s', arg), None)
|
||||
else '"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', arg))
|
||||
for arg in self._parts
|
||||
itertools.chain(
|
||||
[_quote_if_contains(self.command, r"[\s^()]")],
|
||||
(_quote_if_contains(arg, r"[\s^]") for arg in self.args),
|
||||
)
|
||||
)
|
||||
|
||||
Vendored
+30
-11
@@ -6,11 +6,11 @@ import errno
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from tempfile import mkdtemp
|
||||
|
||||
import six
|
||||
|
||||
from .backports.tempfile import NamedTemporaryFile as _NamedTemporaryFile
|
||||
|
||||
__all__ = [
|
||||
"Path",
|
||||
@@ -35,19 +35,20 @@ __all__ = [
|
||||
"fs_encode",
|
||||
"fs_decode",
|
||||
"_fs_encode_errors",
|
||||
"_fs_decode_errors"
|
||||
"_fs_decode_errors",
|
||||
]
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
from pathlib import Path
|
||||
from functools import lru_cache
|
||||
else:
|
||||
from pathlib2 import Path
|
||||
from pipenv.vendor.pathlib2 import Path
|
||||
from pipenv.vendor.backports.functools_lru_cache import lru_cache
|
||||
|
||||
from .backports.tempfile import NamedTemporaryFile as _NamedTemporaryFile
|
||||
|
||||
if sys.version_info < (3, 3):
|
||||
from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size
|
||||
|
||||
NamedTemporaryFile = _NamedTemporaryFile
|
||||
else:
|
||||
from tempfile import NamedTemporaryFile
|
||||
@@ -89,6 +90,7 @@ if six.PY2:
|
||||
|
||||
class IsADirectoryError(OSError):
|
||||
"""The command does not work on directories"""
|
||||
|
||||
pass
|
||||
|
||||
class FileExistsError(OSError):
|
||||
@@ -96,19 +98,35 @@ if six.PY2:
|
||||
self.errno = errno.EEXIST
|
||||
super(FileExistsError, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
else:
|
||||
from builtins import (
|
||||
ResourceWarning, FileNotFoundError, PermissionError, IsADirectoryError,
|
||||
FileExistsError
|
||||
ResourceWarning,
|
||||
FileNotFoundError,
|
||||
PermissionError,
|
||||
IsADirectoryError,
|
||||
FileExistsError,
|
||||
)
|
||||
from io import StringIO
|
||||
|
||||
six.add_move(six.MovedAttribute("Iterable", "collections", "collections.abc")) # type: ignore
|
||||
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc")) # type: ignore
|
||||
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc")) # type: ignore
|
||||
six.add_move(
|
||||
six.MovedAttribute("Iterable", "collections", "collections.abc")
|
||||
) # type: ignore
|
||||
six.add_move(
|
||||
six.MovedAttribute("Mapping", "collections", "collections.abc")
|
||||
) # type: ignore
|
||||
six.add_move(
|
||||
six.MovedAttribute("Sequence", "collections", "collections.abc")
|
||||
) # type: ignore
|
||||
six.add_move(six.MovedAttribute("Set", "collections", "collections.abc")) # type: ignore
|
||||
six.add_move(six.MovedAttribute("ItemsView", "collections", "collections.abc")) # type: ignore
|
||||
from six.moves import Iterable, Mapping, Sequence, Set, ItemsView # type: ignore # noqa
|
||||
six.add_move(
|
||||
six.MovedAttribute("ItemsView", "collections", "collections.abc")
|
||||
) # type: ignore
|
||||
|
||||
# fmt: off
|
||||
from six.moves import ItemsView, Iterable, Mapping, Sequence, Set # type: ignore # noqa # isort:skip
|
||||
# fmt: on
|
||||
|
||||
|
||||
if not sys.warnoptions:
|
||||
warnings.simplefilter("default", ResourceWarning)
|
||||
@@ -282,6 +300,7 @@ else:
|
||||
|
||||
def to_native_string(string):
|
||||
from .misc import to_text, to_bytes
|
||||
|
||||
if six.PY2:
|
||||
return to_bytes(string)
|
||||
return to_text(string)
|
||||
|
||||
+20
-9
@@ -1,11 +1,10 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import io
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import six
|
||||
@@ -13,10 +12,15 @@ import six
|
||||
from .compat import NamedTemporaryFile, Path
|
||||
from .path import is_file_url, is_valid_url, path_to_url, url_to_path
|
||||
|
||||
|
||||
__all__ = [
|
||||
"temp_environ", "temp_path", "cd", "atomic_open_for_write", "open_file", "spinner",
|
||||
"dummy_spinner", "replaced_stream"
|
||||
"temp_environ",
|
||||
"temp_path",
|
||||
"cd",
|
||||
"atomic_open_for_write",
|
||||
"open_file",
|
||||
"spinner",
|
||||
"dummy_spinner",
|
||||
"replaced_stream",
|
||||
]
|
||||
|
||||
|
||||
@@ -104,7 +108,13 @@ def dummy_spinner(spin_type, text, **kwargs):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def spinner(spinner_name=None, start_text=None, handler_map=None, nospin=False, write_to_stdout=True):
|
||||
def spinner(
|
||||
spinner_name=None,
|
||||
start_text=None,
|
||||
handler_map=None,
|
||||
nospin=False,
|
||||
write_to_stdout=True,
|
||||
):
|
||||
"""Get a spinner object or a dummy spinner to wrap a context.
|
||||
|
||||
:param str spinner_name: A spinner type e.g. "dots" or "bouncingBar" (default: {"bouncingBar"})
|
||||
@@ -120,6 +130,7 @@ def spinner(spinner_name=None, start_text=None, handler_map=None, nospin=False,
|
||||
"""
|
||||
|
||||
from .spin import create_spinner
|
||||
|
||||
has_yaspin = None
|
||||
try:
|
||||
import yaspin
|
||||
@@ -146,7 +157,7 @@ def spinner(spinner_name=None, start_text=None, handler_map=None, nospin=False,
|
||||
handler_map=handler_map,
|
||||
nospin=nospin,
|
||||
use_yaspin=use_yaspin,
|
||||
write_to_stdout=write_to_stdout
|
||||
write_to_stdout=write_to_stdout,
|
||||
) as _spinner:
|
||||
yield _spinner
|
||||
|
||||
@@ -267,8 +278,8 @@ def open_file(link, session=None, stream=True):
|
||||
if os.path.isdir(local_path):
|
||||
raise ValueError("Cannot open directory for read: {}".format(link))
|
||||
else:
|
||||
with io.open(local_path, "rb") as local_file:
|
||||
yield local_file
|
||||
with io.open(local_path, "rb") as local_file:
|
||||
yield local_file
|
||||
else:
|
||||
# Remote URL
|
||||
headers = {"Accept-Encoding": "identity"}
|
||||
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
|
||||
__all__ = ["hide_cursor", "show_cursor"]
|
||||
|
||||
|
||||
class CONSOLE_CURSOR_INFO(ctypes.Structure):
|
||||
_fields_ = [("dwSize", ctypes.c_int), ("bVisible", ctypes.c_int)]
|
||||
|
||||
|
||||
WIN_STDERR_HANDLE_ID = ctypes.c_ulong(-12)
|
||||
WIN_STDOUT_HANDLE_ID = ctypes.c_ulong(-11)
|
||||
|
||||
|
||||
def get_stream_handle(stream=sys.stdout):
|
||||
"""
|
||||
Get the OS appropriate handle for the corresponding output stream.
|
||||
|
||||
:param str stream: The the stream to get the handle for
|
||||
:return: A handle to the appropriate stream, either a ctypes buffer
|
||||
or **sys.stdout** or **sys.stderr**.
|
||||
"""
|
||||
handle = stream
|
||||
if os.name == "nt":
|
||||
from ctypes import windll
|
||||
|
||||
handle_id = WIN_STDOUT_HANDLE_ID
|
||||
handle = windll.kernel32.GetStdHandle(handle_id)
|
||||
return handle
|
||||
|
||||
|
||||
def hide_cursor(stream=sys.stdout):
|
||||
"""
|
||||
Hide the console cursor on the given stream
|
||||
|
||||
:param stream: The name of the stream to get the handle for
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
handle = get_stream_handle(stream=stream)
|
||||
if os.name == "nt":
|
||||
from ctypes import windll
|
||||
|
||||
cursor_info = CONSOLE_CURSOR_INFO()
|
||||
windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(cursor_info))
|
||||
cursor_info.visible = False
|
||||
windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(cursor_info))
|
||||
else:
|
||||
handle.write("\033[?25l")
|
||||
handle.flush()
|
||||
|
||||
|
||||
def show_cursor(stream=sys.stdout):
|
||||
"""
|
||||
Show the console cursor on the given stream
|
||||
|
||||
:param stream: The name of the stream to get the handle for
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
handle = get_stream_handle(stream=stream)
|
||||
if os.name == "nt":
|
||||
from ctypes import windll
|
||||
|
||||
cursor_info = CONSOLE_CURSOR_INFO()
|
||||
windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(cursor_info))
|
||||
cursor_info.visible = True
|
||||
windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(cursor_info))
|
||||
else:
|
||||
handle.write("\033[?25h")
|
||||
handle.flush()
|
||||
Vendored
+94
-74
@@ -1,14 +1,13 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from collections import OrderedDict
|
||||
from functools import partial
|
||||
from itertools import islice, tee
|
||||
@@ -16,10 +15,11 @@ from itertools import islice, tee
|
||||
import six
|
||||
|
||||
from .cmdparse import Script
|
||||
from .compat import Path, fs_str, partialmethod, to_native_string, Iterable, StringIO
|
||||
from .compat import Iterable, Path, StringIO, fs_str, partialmethod, to_native_string
|
||||
from .contextmanagers import spinner as spinner
|
||||
|
||||
if os.name != "nt":
|
||||
|
||||
class WindowsError(OSError):
|
||||
pass
|
||||
|
||||
@@ -144,6 +144,55 @@ def _spawn_subprocess(script, env=None, block=True, cwd=None, combine_stderr=Tru
|
||||
return subprocess.Popen(script.cmdify(), **options)
|
||||
|
||||
|
||||
def _read_streams(stream_dict):
|
||||
results = {}
|
||||
for outstream in stream_dict.keys():
|
||||
stream = stream_dict[outstream]
|
||||
if not stream:
|
||||
results[outstream] = None
|
||||
continue
|
||||
line = to_text(stream.readline())
|
||||
if not line:
|
||||
results[outstream] = None
|
||||
continue
|
||||
line = to_text("{0}".format(line.rstrip()))
|
||||
results[outstream] = line
|
||||
return results
|
||||
|
||||
|
||||
def get_stream_results(cmd_instance, verbose, maxlen, spinner=None, stdout_allowed=False):
|
||||
stream_results = {"stdout": [], "stderr": []}
|
||||
streams = {"stderr": cmd_instance.stderr, "stdout": cmd_instance.stdout}
|
||||
while True:
|
||||
stream_contents = _read_streams(streams)
|
||||
stdout_line = stream_contents["stdout"]
|
||||
stderr_line = stream_contents["stderr"]
|
||||
if not (stdout_line or stderr_line):
|
||||
break
|
||||
for stream_name in stream_contents.keys():
|
||||
if stream_contents[stream_name] and stream_name in stream_results:
|
||||
line = stream_contents[stream_name]
|
||||
stream_results[stream_name].append(line)
|
||||
display_line = fs_str("{0}".format(line))
|
||||
if len(display_line) > maxlen:
|
||||
display_line = "{0}...".format(display_line[:maxlen])
|
||||
if verbose:
|
||||
use_stderr = not stdout_allowed or stream_name != "stdout"
|
||||
if spinner:
|
||||
target = spinner.stderr if use_stderr else spinner.stdout
|
||||
spinner.hide_and_write(display_line, target=target)
|
||||
else:
|
||||
target = sys.stderr if use_stderr else sys.stdout
|
||||
target.write(display_line)
|
||||
target.flush()
|
||||
if spinner:
|
||||
spinner.text = to_native_string(
|
||||
"{0} {1}".format(spinner.text, display_line)
|
||||
)
|
||||
continue
|
||||
return stream_results
|
||||
|
||||
|
||||
def _create_subprocess(
|
||||
cmd,
|
||||
env=None,
|
||||
@@ -155,74 +204,35 @@ def _create_subprocess(
|
||||
combine_stderr=False,
|
||||
display_limit=200,
|
||||
start_text="",
|
||||
write_to_stdout=True
|
||||
write_to_stdout=True,
|
||||
):
|
||||
if not env:
|
||||
env = os.environ.copy()
|
||||
try:
|
||||
c = _spawn_subprocess(cmd, env=env, block=block, cwd=cwd,
|
||||
combine_stderr=combine_stderr)
|
||||
except Exception as exc:
|
||||
c = _spawn_subprocess(
|
||||
cmd, env=env, block=block, cwd=cwd, combine_stderr=combine_stderr
|
||||
)
|
||||
except Exception:
|
||||
import traceback
|
||||
|
||||
formatted_tb = "".join(traceback.format_exception(*sys.exc_info()))
|
||||
sys.stderr.write("Error while executing command %s:" % " ".join(cmd._parts))
|
||||
sys.stderr.write(formatted_tb)
|
||||
raise
|
||||
if not block:
|
||||
c.stdin.close()
|
||||
output = []
|
||||
err = []
|
||||
spinner_orig_text = None
|
||||
if spinner:
|
||||
spinner_orig_text = getattr(spinner, "text", None)
|
||||
if spinner_orig_text is None:
|
||||
spinner_orig_text = start_text if start_text is not None else ""
|
||||
streams = {
|
||||
"stdout": c.stdout,
|
||||
"stderr": c.stderr
|
||||
}
|
||||
while True:
|
||||
stdout_line = None
|
||||
stderr_line = None
|
||||
for outstream in streams.keys():
|
||||
stream = streams[outstream]
|
||||
if not stream:
|
||||
continue
|
||||
line = to_text(stream.readline())
|
||||
if not line:
|
||||
continue
|
||||
line = to_text("{0}".format(line.rstrip()))
|
||||
if outstream == "stderr":
|
||||
stderr_line = line
|
||||
else:
|
||||
stdout_line = line
|
||||
if not (stdout_line or stderr_line):
|
||||
break
|
||||
if stderr_line is not None:
|
||||
err.append(stderr_line)
|
||||
err_line = fs_str("{0}".format(stderr_line))
|
||||
if verbose and err_line is not None:
|
||||
if spinner:
|
||||
spinner.hide_and_write(err_line, target=spinner.stderr)
|
||||
else:
|
||||
sys.stderr.write(err_line)
|
||||
sys.stderr.flush()
|
||||
if stdout_line is not None:
|
||||
output.append(stdout_line)
|
||||
display_line = fs_str("{0}".format(stdout_line))
|
||||
if len(stdout_line) > display_limit:
|
||||
display_line = "{0}...".format(stdout_line[:display_limit])
|
||||
if verbose and display_line is not None:
|
||||
if spinner:
|
||||
target = spinner.stdout if write_to_stdout else spinner.stderr
|
||||
spinner.hide_and_write(display_line, target=target)
|
||||
else:
|
||||
target = sys.stdout if write_to_stdout else sys.stderr
|
||||
target.write(display_line)
|
||||
target.flush()
|
||||
if spinner:
|
||||
spinner.text = to_native_string("{0} {1}".format(spinner_orig_text, display_line))
|
||||
continue
|
||||
spinner_orig_text = ""
|
||||
if spinner and getattr(spinner, "text", None) is not None:
|
||||
spinner_orig_text = spinner.text
|
||||
if not spinner_orig_text and start_text is not None:
|
||||
spinner_orig_text = start_text
|
||||
stream_results = get_stream_results(
|
||||
c,
|
||||
verbose=verbose,
|
||||
maxlen=display_limit,
|
||||
spinner=spinner,
|
||||
stdout_allowed=write_to_stdout,
|
||||
)
|
||||
try:
|
||||
c.wait()
|
||||
finally:
|
||||
@@ -237,6 +247,8 @@ def _create_subprocess(
|
||||
spinner.ok(to_native_string("✔ Complete"))
|
||||
else:
|
||||
spinner.ok(to_native_string("Complete"))
|
||||
output = stream_results["stdout"]
|
||||
err = stream_results["stderr"]
|
||||
c.out = "\n".join(output) if output else ""
|
||||
c.err = "\n".join(err) if err else ""
|
||||
else:
|
||||
@@ -261,7 +273,7 @@ def run(
|
||||
spinner_name=None,
|
||||
combine_stderr=True,
|
||||
display_limit=200,
|
||||
write_to_stdout=True
|
||||
write_to_stdout=True,
|
||||
):
|
||||
"""Use `subprocess.Popen` to get the output of a command and decode it.
|
||||
|
||||
@@ -303,8 +315,12 @@ def run(
|
||||
if block or not return_object:
|
||||
combine_stderr = False
|
||||
start_text = ""
|
||||
with spinner(spinner_name=spinner_name, start_text=start_text, nospin=nospin,
|
||||
write_to_stdout=write_to_stdout) as sp:
|
||||
with spinner(
|
||||
spinner_name=spinner_name,
|
||||
start_text=start_text,
|
||||
nospin=nospin,
|
||||
write_to_stdout=write_to_stdout,
|
||||
) as sp:
|
||||
return _create_subprocess(
|
||||
cmd,
|
||||
env=_env,
|
||||
@@ -315,7 +331,7 @@ def run(
|
||||
spinner=sp,
|
||||
combine_stderr=combine_stderr,
|
||||
start_text=start_text,
|
||||
write_to_stdout=True
|
||||
write_to_stdout=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -331,8 +347,9 @@ def load_path(python):
|
||||
"""
|
||||
|
||||
python = Path(python).as_posix()
|
||||
out, err = run([python, "-c", "import json, sys; print(json.dumps(sys.path))"],
|
||||
nospin=True)
|
||||
out, err = run(
|
||||
[python, "-c", "import json, sys; print(json.dumps(sys.path))"], nospin=True
|
||||
)
|
||||
if out:
|
||||
return json.loads(out)
|
||||
else:
|
||||
@@ -445,7 +462,7 @@ def to_text(string, encoding="utf-8", errors=None):
|
||||
string = six.text_type(bytes(string), encoding, errors)
|
||||
else:
|
||||
string = string.decode(encoding, errors)
|
||||
except UnicodeDecodeError as e:
|
||||
except UnicodeDecodeError:
|
||||
string = " ".join(to_text(arg, encoding, errors) for arg in string)
|
||||
return string
|
||||
|
||||
@@ -528,8 +545,8 @@ def get_output_encoding(source_encoding):
|
||||
"""
|
||||
|
||||
if source_encoding is not None:
|
||||
if get_canonical_encoding_name(source_encoding) == 'ascii':
|
||||
return 'utf-8'
|
||||
if get_canonical_encoding_name(source_encoding) == "ascii":
|
||||
return "utf-8"
|
||||
return get_canonical_encoding_name(source_encoding)
|
||||
return get_canonical_encoding_name(PREFERRED_ENCODING)
|
||||
|
||||
@@ -542,7 +559,7 @@ def _encode(output, encoding=None, errors=None, translation_map=None):
|
||||
except (UnicodeDecodeError, UnicodeEncodeError):
|
||||
if translation_map is not None:
|
||||
if six.PY2:
|
||||
output = unicode.translate(
|
||||
output = unicode.translate( # noqa: F821
|
||||
to_text(output, encoding=encoding, errors=errors), translation_map
|
||||
)
|
||||
else:
|
||||
@@ -573,8 +590,9 @@ def decode_for_output(output, target_stream=None, translation_map=None):
|
||||
try:
|
||||
output = _encode(output, encoding=encoding, translation_map=translation_map)
|
||||
except (UnicodeDecodeError, UnicodeEncodeError):
|
||||
output = _encode(output, encoding=encoding, errors="replace",
|
||||
translation_map=translation_map)
|
||||
output = _encode(
|
||||
output, encoding=encoding, errors="replace", translation_map=translation_map
|
||||
)
|
||||
return to_text(output, encoding=encoding, errors="replace")
|
||||
|
||||
|
||||
@@ -589,6 +607,7 @@ def get_canonical_encoding_name(name):
|
||||
"""
|
||||
|
||||
import codecs
|
||||
|
||||
try:
|
||||
codec = codecs.lookup(name)
|
||||
except LookupError:
|
||||
@@ -629,8 +648,9 @@ class StreamWrapper(io.TextIOWrapper):
|
||||
# borrowed from click's implementation of stream wrappers, see
|
||||
# https://github.com/pallets/click/blob/6cafd32/click/_compat.py#L64
|
||||
if six.PY2:
|
||||
|
||||
def write(self, x):
|
||||
if isinstance(x, (str, buffer, bytearray)):
|
||||
if isinstance(x, (str, buffer, bytearray)): # noqa: F821
|
||||
try:
|
||||
self.flush()
|
||||
except Exception:
|
||||
|
||||
Vendored
+20
-22
@@ -1,5 +1,5 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import atexit
|
||||
import errno
|
||||
@@ -11,23 +11,21 @@ import stat
|
||||
import warnings
|
||||
|
||||
import six
|
||||
|
||||
from six.moves import urllib_parse
|
||||
from six.moves.urllib import request as urllib_request
|
||||
|
||||
from .backports.tempfile import _TemporaryFileWrapper
|
||||
from .compat import (
|
||||
_NamedTemporaryFile,
|
||||
Path,
|
||||
ResourceWarning,
|
||||
TemporaryDirectory,
|
||||
_fs_encoding,
|
||||
_NamedTemporaryFile,
|
||||
finalize,
|
||||
fs_decode,
|
||||
fs_encode
|
||||
fs_encode,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"check_for_unc_path",
|
||||
"get_converted_relative_path",
|
||||
@@ -51,7 +49,11 @@ __all__ = [
|
||||
|
||||
|
||||
if os.name == "nt":
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning, message="The Windows bytes API has been deprecated.*")
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
category=DeprecationWarning,
|
||||
message="The Windows bytes API has been deprecated.*",
|
||||
)
|
||||
|
||||
|
||||
def unicode_path(path):
|
||||
@@ -93,9 +95,11 @@ def normalize_path(path):
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return os.path.normpath(os.path.normcase(
|
||||
os.path.abspath(os.path.expandvars(os.path.expanduser(str(path))))
|
||||
))
|
||||
return os.path.normpath(
|
||||
os.path.normcase(
|
||||
os.path.abspath(os.path.expandvars(os.path.expanduser(str(path))))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def is_in_path(path, parent):
|
||||
@@ -233,7 +237,7 @@ def mkdir_p(newdir, mode=0o777):
|
||||
target = os.path.join(head, tail)
|
||||
if os.path.exists(target) and os.path.isfile(target):
|
||||
raise OSError(
|
||||
"A file with the same name as the desired dir, '{0}', already exists.".format(
|
||||
"A file with the same name as the desired dir, '{0}', already exists.".format(
|
||||
fs_decode(newdir)
|
||||
)
|
||||
)
|
||||
@@ -307,7 +311,7 @@ def set_write_bit(fn):
|
||||
except AttributeError:
|
||||
pass
|
||||
for root, dirs, files in os.walk(fn, topdown=False):
|
||||
for dir_ in [os.path.join(root,d) for d in dirs]:
|
||||
for dir_ in [os.path.join(root, d) for d in dirs]:
|
||||
set_write_bit(dir_)
|
||||
for file_ in [os.path.join(root, f) for f in files]:
|
||||
set_write_bit(file_)
|
||||
@@ -332,9 +336,7 @@ def rmtree(directory, ignore_errors=False, onerror=None):
|
||||
if onerror is None:
|
||||
onerror = handle_remove_readonly
|
||||
try:
|
||||
shutil.rmtree(
|
||||
directory, ignore_errors=ignore_errors, onerror=onerror
|
||||
)
|
||||
shutil.rmtree(directory, ignore_errors=ignore_errors, onerror=onerror)
|
||||
except (IOError, OSError, FileNotFoundError) as exc:
|
||||
# Ignore removal failures where the file doesn't exist
|
||||
if exc.errno != errno.ENOENT:
|
||||
@@ -355,14 +357,10 @@ def handle_remove_readonly(func, path, exc):
|
||||
:func:`set_write_bit` on the target path and try again.
|
||||
"""
|
||||
# Check for read-only attribute
|
||||
from .compat import (
|
||||
ResourceWarning, FileNotFoundError, PermissionError
|
||||
)
|
||||
from .compat import ResourceWarning, FileNotFoundError, PermissionError
|
||||
|
||||
PERM_ERRORS = (errno.EACCES, errno.EPERM, errno.ENOENT)
|
||||
default_warning_message = (
|
||||
"Unable to remove file due to permissions restriction: {!r}"
|
||||
)
|
||||
default_warning_message = "Unable to remove file due to permissions restriction: {!r}"
|
||||
# split the initial exception out into its type, exception, and traceback
|
||||
exc_type, exc_exception, exc_tb = exc
|
||||
if is_readonly_path(path):
|
||||
@@ -488,8 +486,8 @@ def get_converted_relative_path(path, relative_to=None):
|
||||
raise ValueError("The path argument does not currently accept UNC paths")
|
||||
|
||||
relpath_s = to_text(posixpath.normpath(path.as_posix()))
|
||||
if not (relpath_s == u"." or relpath_s.startswith(u"./")):
|
||||
relpath_s = posixpath.join(u".", relpath_s)
|
||||
if not (relpath_s == "." or relpath_s.startswith("./")):
|
||||
relpath_s = posixpath.join(".", relpath_s)
|
||||
return relpath_s
|
||||
|
||||
|
||||
|
||||
Vendored
+14
-18
@@ -7,30 +7,31 @@ import signal
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from io import StringIO
|
||||
|
||||
import colorama
|
||||
import six
|
||||
|
||||
from .compat import to_native_string
|
||||
from .termcolors import COLOR_MAP, COLORS, colored, DISABLE_COLORS
|
||||
from .cursor import hide_cursor, show_cursor
|
||||
from .misc import decode_for_output
|
||||
from io import StringIO
|
||||
from .termcolors import COLOR_MAP, COLORS, DISABLE_COLORS, colored
|
||||
|
||||
try:
|
||||
import yaspin
|
||||
import cursor
|
||||
except ImportError:
|
||||
yaspin = None
|
||||
Spinners = None
|
||||
SpinBase = None
|
||||
cursor = None
|
||||
else:
|
||||
import yaspin.spinners
|
||||
import yaspin.core
|
||||
|
||||
Spinners = yaspin.spinners.Spinners
|
||||
SpinBase = yaspin.core.Yaspin
|
||||
|
||||
if os.name == "nt":
|
||||
|
||||
def handler(signum, frame, spinner):
|
||||
"""Signal handler, used to gracefully shut down the ``spinner`` instance
|
||||
when specified signal is received by the process running the ``spinner``.
|
||||
@@ -42,7 +43,9 @@ if os.name == "nt":
|
||||
spinner.stop()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
def handler(signum, frame, spinner):
|
||||
"""Signal handler, used to gracefully shut down the ``spinner`` instance
|
||||
when specified signal is received by the process running the ``spinner``.
|
||||
@@ -54,12 +57,10 @@ else:
|
||||
spinner.stop()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
CLEAR_LINE = chr(27) + "[K"
|
||||
|
||||
TRANSLATION_MAP = {
|
||||
10004: u"OK",
|
||||
10008: u"x",
|
||||
}
|
||||
TRANSLATION_MAP = {10004: u"OK", 10008: u"x"}
|
||||
|
||||
|
||||
decode_output = functools.partial(decode_for_output, translation_map=TRANSLATION_MAP)
|
||||
@@ -85,6 +86,7 @@ class DummySpinner(object):
|
||||
def __exit__(self, exc_type, exc_val, tb):
|
||||
if exc_type:
|
||||
import traceback
|
||||
|
||||
formatted_tb = traceback.format_exception(exc_type, exc_val, tb)
|
||||
self.write_err("".join(formatted_tb))
|
||||
self._close_output_buffer()
|
||||
@@ -198,10 +200,7 @@ class VistirSpinner(SpinBase):
|
||||
colorama.init()
|
||||
sigmap = {}
|
||||
if handler:
|
||||
sigmap.update({
|
||||
signal.SIGINT: handler,
|
||||
signal.SIGTERM: handler
|
||||
})
|
||||
sigmap.update({signal.SIGINT: handler, signal.SIGTERM: handler})
|
||||
handler_map = kwargs.pop("handler_map", {})
|
||||
if os.name == "nt":
|
||||
sigmap[signal.SIGBREAK] = handler
|
||||
@@ -333,10 +332,7 @@ class VistirSpinner(SpinBase):
|
||||
|
||||
def _compose_color_func(self):
|
||||
fn = functools.partial(
|
||||
colored,
|
||||
color=self._color,
|
||||
on_color=self._on_color,
|
||||
attrs=list(self._attrs),
|
||||
colored, color=self._color, on_color=self._on_color, attrs=list(self._attrs)
|
||||
)
|
||||
return fn
|
||||
|
||||
@@ -421,13 +417,13 @@ class VistirSpinner(SpinBase):
|
||||
def _hide_cursor(target=None):
|
||||
if not target:
|
||||
target = sys.stdout
|
||||
cursor.hide(stream=target)
|
||||
hide_cursor(stream=target)
|
||||
|
||||
@staticmethod
|
||||
def _show_cursor(target=None):
|
||||
if not target:
|
||||
target = sys.stdout
|
||||
cursor.show(stream=target)
|
||||
show_cursor(stream=target)
|
||||
|
||||
@staticmethod
|
||||
def _clear_err():
|
||||
|
||||
Vendored
+37
-47
@@ -1,62 +1,55 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
import colorama
|
||||
|
||||
import os
|
||||
|
||||
import colorama
|
||||
|
||||
from .compat import to_native_string
|
||||
|
||||
|
||||
DISABLE_COLORS = os.getenv("CI", False) or os.getenv("ANSI_COLORS_DISABLED",
|
||||
os.getenv("VISTIR_DISABLE_COLORS", False)
|
||||
DISABLE_COLORS = os.getenv("CI", False) or os.getenv(
|
||||
"ANSI_COLORS_DISABLED", os.getenv("VISTIR_DISABLE_COLORS", False)
|
||||
)
|
||||
|
||||
|
||||
ATTRIBUTES = dict(
|
||||
list(zip([
|
||||
'bold',
|
||||
'dark',
|
||||
'',
|
||||
'underline',
|
||||
'blink',
|
||||
'',
|
||||
'reverse',
|
||||
'concealed'
|
||||
],
|
||||
list(range(1, 9))
|
||||
))
|
||||
list(
|
||||
zip(
|
||||
["bold", "dark", "", "underline", "blink", "", "reverse", "concealed"],
|
||||
list(range(1, 9)),
|
||||
)
|
||||
del ATTRIBUTES['']
|
||||
)
|
||||
)
|
||||
del ATTRIBUTES[""]
|
||||
|
||||
|
||||
HIGHLIGHTS = dict(
|
||||
list(zip([
|
||||
'on_grey',
|
||||
'on_red',
|
||||
'on_green',
|
||||
'on_yellow',
|
||||
'on_blue',
|
||||
'on_magenta',
|
||||
'on_cyan',
|
||||
'on_white'
|
||||
list(
|
||||
zip(
|
||||
[
|
||||
"on_grey",
|
||||
"on_red",
|
||||
"on_green",
|
||||
"on_yellow",
|
||||
"on_blue",
|
||||
"on_magenta",
|
||||
"on_cyan",
|
||||
"on_white",
|
||||
],
|
||||
list(range(40, 48))
|
||||
))
|
||||
list(range(40, 48)),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
COLORS = dict(
|
||||
list(zip([
|
||||
'grey',
|
||||
'red',
|
||||
'green',
|
||||
'yellow',
|
||||
'blue',
|
||||
'magenta',
|
||||
'cyan',
|
||||
'white',
|
||||
],
|
||||
list(range(30, 38))
|
||||
))
|
||||
list(
|
||||
zip(
|
||||
["grey", "red", "green", "yellow", "blue", "magenta", "cyan", "white"],
|
||||
list(range(30, 38)),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
COLOR_MAP = {
|
||||
@@ -106,11 +99,11 @@ def colored(text, color=None, on_color=None, attrs=None):
|
||||
colored('Hello, World!', 'red', 'on_grey', ['blue', 'blink'])
|
||||
colored('Hello, World!', 'green')
|
||||
"""
|
||||
if os.getenv('ANSI_COLORS_DISABLED') is None:
|
||||
if os.getenv("ANSI_COLORS_DISABLED") is None:
|
||||
style = "NORMAL"
|
||||
if 'bold' in attrs:
|
||||
if "bold" in attrs:
|
||||
style = "BRIGHT"
|
||||
attrs.remove('bold')
|
||||
attrs.remove("bold")
|
||||
if color is not None:
|
||||
color = color.upper()
|
||||
text = to_native_string("%s%s%s%s%s") % (
|
||||
@@ -131,10 +124,7 @@ def colored(text, color=None, on_color=None, attrs=None):
|
||||
)
|
||||
|
||||
if attrs is not None:
|
||||
fmt_str = to_native_string("%s[%%dm%%s%s[9m") % (
|
||||
chr(27),
|
||||
chr(27)
|
||||
)
|
||||
fmt_str = to_native_string("%s[%%dm%%s%s[9m") % (chr(27), chr(27))
|
||||
for attr in attrs:
|
||||
text = fmt_str % (ATTRIBUTES[attr], text)
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.14.0"
|
||||
__version__ = "0.14.1"
|
||||
|
||||
Vendored
+4
-2
@@ -84,5 +84,7 @@ def kbi_safe_yaspin(*args, **kwargs):
|
||||
return Yaspin(*args, **kwargs)
|
||||
|
||||
|
||||
_kbi_safe_doc = yaspin.__doc__.replace("yaspin", "kbi_safe_yaspin")
|
||||
kbi_safe_yaspin.__doc__ = _kbi_safe_doc
|
||||
# Handle PYTHONOPTIMIZE=2 case, when docstrings are set to None.
|
||||
if yaspin.__doc__:
|
||||
_kbi_safe_doc = yaspin.__doc__.replace("yaspin", "kbi_safe_yaspin")
|
||||
kbi_safe_yaspin.__doc__ = _kbi_safe_doc
|
||||
|
||||
Vendored
+3
-3
@@ -17,7 +17,7 @@ import threading
|
||||
import time
|
||||
|
||||
import colorama
|
||||
import cursor
|
||||
from pipenv.vendor.vistir import cursor
|
||||
|
||||
from .base_spinner import default_spinner
|
||||
from .compat import PY2, basestring, builtin_str, bytes, iteritems, str
|
||||
@@ -530,11 +530,11 @@ class Yaspin(object):
|
||||
|
||||
@staticmethod
|
||||
def _hide_cursor():
|
||||
cursor.hide()
|
||||
cursor.hide_cursor()
|
||||
|
||||
@staticmethod
|
||||
def _show_cursor():
|
||||
cursor.show()
|
||||
cursor.show_cursor()
|
||||
|
||||
@staticmethod
|
||||
def _clear_line():
|
||||
|
||||
@@ -128,10 +128,11 @@ setup(
|
||||
],
|
||||
},
|
||||
python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
|
||||
setup_requires=["invoke", "parver"],
|
||||
setup_requires=["invoke", "parver", ],
|
||||
install_requires=required,
|
||||
extras_require={
|
||||
"test": ["pytest<4.0", "pytest-tap", "pytest-xdist", "flaky", "mock"]
|
||||
"test": ["pytest<4.0", "pytest-tap", "pytest-xdist", "flaky", "mock"],
|
||||
"dev": ["towncrier", "bs4"],
|
||||
},
|
||||
include_package_data=True,
|
||||
license="MIT",
|
||||
|
||||
+6
-4
@@ -15,21 +15,23 @@ diff --git a/pipenv/vendor/vistir/compat.py b/pipenv/vendor/vistir/compat.py
|
||||
index 9ae33fdc..ec3b65cb 100644
|
||||
--- a/pipenv/vendor/vistir/compat.py
|
||||
+++ b/pipenv/vendor/vistir/compat.py
|
||||
@@ -43,11 +43,11 @@ if sys.version_info >= (3, 5):
|
||||
@@ -43,12 +43,12 @@ if sys.version_info >= (3, 5):
|
||||
from functools import lru_cache
|
||||
else:
|
||||
from pathlib2 import Path
|
||||
- from pathlib2 import Path
|
||||
- from backports.functools_lru_cache import lru_cache
|
||||
+ from pipenv.vendor.pathlib2 import Path
|
||||
+ from pipenv.vendor.backports.functools_lru_cache import lru_cache
|
||||
|
||||
from .backports.tempfile import NamedTemporaryFile as _NamedTemporaryFile
|
||||
|
||||
if sys.version_info < (3, 3):
|
||||
- from backports.shutil_get_terminal_size import get_terminal_size
|
||||
+ from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size
|
||||
|
||||
NamedTemporaryFile = _NamedTemporaryFile
|
||||
else:
|
||||
from tempfile import NamedTemporaryFile
|
||||
@@ -56,7 +56,7 @@ else:
|
||||
@@ -57,7 +57,7 @@ else:
|
||||
try:
|
||||
from weakref import finalize
|
||||
except ImportError:
|
||||
|
||||
@@ -7,7 +7,7 @@ index d01fb98e..06b8b621 100644
|
||||
import time
|
||||
|
||||
+import colorama
|
||||
+import cursor
|
||||
+from pipenv.vendor.vistir import cursor
|
||||
+
|
||||
from .base_spinner import default_spinner
|
||||
from .compat import PY2, basestring, builtin_str, bytes, iteritems, str
|
||||
@@ -48,13 +48,13 @@ index d01fb98e..06b8b621 100644
|
||||
def _hide_cursor():
|
||||
- sys.stdout.write("\033[?25l")
|
||||
- sys.stdout.flush()
|
||||
+ cursor.hide()
|
||||
+ cursor.hide_cursor()
|
||||
|
||||
@staticmethod
|
||||
def _show_cursor():
|
||||
- sys.stdout.write("\033[?25h")
|
||||
- sys.stdout.flush()
|
||||
+ cursor.show()
|
||||
+ cursor.show_cursor()
|
||||
|
||||
@staticmethod
|
||||
def _clear_line():
|
||||
|
||||
Reference in New Issue
Block a user