Fix deps and testing infrastructure

- Fix requirement parsing
- Add appveyor config
- cutover from pathlib to pathlib2 if needed
- Pin pathlib2==2.1.0 to avoid scandir
- Windows script runner fix
- Backport `shlex.quote()` for use in `pipenv run`
- Update tests and appveyor

Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
Dan Ryan
2018-03-19 01:06:29 -04:00
parent 8a67a21d61
commit 31301b1536
29 changed files with 3458 additions and 478 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ pytest-xdist = "*"
click = "*"
pytest-pypy = {path = "./tests/pytest-pypi", editable = true}
pytest-tap = "*"
stdeb = {version="*", sys_platform="== 'linux'"}
stdeb = {version="*", markers="sys_platform == 'linux'"}
white = {version="*", markers="python_version >= '3.6'"}
[packages]
@@ -23,4 +23,4 @@ tests = "bash ./run-tests.sh"
[pipenv]
allow_prereleases = true
allow_prereleases = true
Generated
+132 -10
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "8acf60476c4efca4f1be83dc747f18d4a663c3479e664e9e6865469f7ab9b958"
"sha256": "eab749ae861993f9e4b71bea88782c806a02fa82e3f45c82c4550ee459d8b886"
},
"pipfile-spec": 6,
"requires": {},
@@ -29,6 +29,13 @@
],
"version": "==1.4"
},
"asn1crypto": {
"hashes": [
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
],
"version": "==0.24.0"
},
"attrs": {
"hashes": [
"sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9",
@@ -43,6 +50,14 @@
],
"version": "==2.5.3"
},
"black": {
"hashes": [
"sha256:0461c7a52b5beb378936bf642753dec7a45305c96c6129d540b9c53227121a5a",
"sha256:7183263650ba3071034e90b40a1ea74abccbd32cf525cef6d7914479dbe7f2fb"
],
"markers": "python_version > '3.5'",
"version": "==18.3a0"
},
"certifi": {
"hashes": [
"sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
@@ -50,6 +65,39 @@
],
"version": "==2018.1.18"
},
"cffi": {
"hashes": [
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
],
"markers": "platform_python_implementation != 'pypy'",
"version": "==1.11.5"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
@@ -73,6 +121,35 @@
"markers": "sys_platform == 'win32'",
"version": "==0.3.9"
},
"configparser": {
"hashes": [
"sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a"
],
"markers": "python_version < '3.2'",
"version": "==3.5.0"
},
"cryptography": {
"hashes": [
"sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd",
"sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04",
"sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f",
"sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd",
"sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb",
"sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2",
"sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037",
"sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd",
"sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531",
"sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63",
"sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e",
"sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351",
"sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a",
"sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563",
"sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab",
"sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471",
"sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887"
],
"version": "==2.2.2"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
@@ -81,6 +158,16 @@
],
"version": "==0.14"
},
"enum34": {
"hashes": [
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
"sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
],
"markers": "python_version < '3'",
"version": "==1.1.6"
},
"execnet": {
"hashes": [
"sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a",
@@ -103,6 +190,14 @@
],
"version": "==0.12.2"
},
"funcsigs": {
"hashes": [
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
],
"markers": "python_version < '3.0'",
"version": "==1.0.2"
},
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
@@ -117,6 +212,13 @@
],
"version": "==1.0.0"
},
"ipaddress": {
"hashes": [
"sha256:200d8686011d470b5e4de207d803445deee427455cd0cb7c982b68cf82524f81"
],
"markers": "python_version < '3'",
"version": "==1.0.19"
},
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
@@ -159,19 +261,26 @@
],
"version": "==4.1.0"
},
"pathlib": {
"ordereddict": {
"hashes": [
"sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f"
"sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f"
],
"version": "==1.1"
},
"pathlib2": {
"hashes": [
"sha256:24e0b33e1333b55e73c9d1e9a8342417d519f7789a9d3b440f4acd00ea45157e",
"sha256:deb3a960c1d55868dfbcac98432358b92ba89d95029cddd4040db1f27405055c"
],
"markers": "python_version < '3.4'",
"version": "==1.0.1"
"version": "==2.1.0"
},
"pbr": {
"hashes": [
"sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1",
"sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac"
"sha256:8b9a7c3704657cb0831b3ded0a6b61377947c8235b76649bb7de4bfe2e6cfa56",
"sha256:cf66675e22ae91a4f20e4b8354f117d3e3d1de651513051d109cc39645fb3672"
],
"version": "==3.1.1"
"version": "==4.0.0"
},
"pipenv": {
"editable": true,
@@ -204,6 +313,12 @@
],
"version": "==2.3.1"
},
"pycparser": {
"hashes": [
"sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226"
],
"version": "==2.18"
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
@@ -218,6 +333,13 @@
],
"version": "==2.2.0"
},
"pyopenssl": {
"hashes": [
"sha256:07a2de1a54de07448732a81e38a55df7da109b2f47f599f8bb35b0cbec69d4bd",
"sha256:2c10cfba46a52c0b0950118981d61e72c1e5b1aac451ca1bc77de1a679456773"
],
"version": "==17.5.0"
},
"pytest": {
"hashes": [
"sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c",
@@ -328,10 +450,10 @@
},
"tqdm": {
"hashes": [
"sha256:05e991ecb0f874046ddcb374396a626afd046fb4d31f73633ea752b844458a7a",
"sha256:2aea9f81fdf127048667e0ba22f5fc10ebc879fb838dc52dcf055242037ec1f7"
"sha256:782aa84b61a5246c4f9e5b938875009e0b759d9a5c9d16b12e4f8deefdff7892",
"sha256:eaf9c32a2cf7b9623eb272134b934eb354ef6304299611aa71e3cf47cf83c22b"
],
"version": "==4.19.8"
"version": "==4.19.9"
},
"twine": {
"hashes": [
+77
View File
@@ -0,0 +1,77 @@
build: off
version: 1.0.{build}
skip_branch_with_pr: true
init:
- git config --global core.sharedRepository true
- git config --global core.longpaths true
- git config --global core.autocrlf input
environment:
PYPI_VENDOR_DIR: ".\\tests\\pypi\\"
GIT_ASK_YESNO: "false"
SHELL: "windows"
PYTHON_ARCH: "64"
PYTHONIOENCODING: "utf-8"
matrix:
- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x"
TEST_SUITE: "cli"
- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x"
TEST_SUITE: '"dotvenv or check or unused or requirements"'
- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x"
TEST_SUITE: "complex"
- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x"
TEST_SUITE: '"markers or run or project or utils"'
- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x"
TEST_SUITE: "install"
- PYTHON: "C:\\Python36-x64"
PYTHON_VERSION: "3.6.x"
TEST_SUITE: "cli"
- PYTHON: "C:\\Python36-x64"
PYTHON_VERSION: "3.6.x"
TEST_SUITE: '"dotvenv or check or unused or requirements"'
- PYTHON: "C:\\Python36-x64"
PYTHON_VERSION: "3.6.x"
TEST_SUITE: "complex"
- PYTHON: "C:\\Python36-x64"
PYTHON_VERSION: "3.6.x"
TEST_SUITE: '"markers or run or project or utils"'
- PYTHON: "C:\\Python36-x64"
PYTHON_VERSION: "2.7.x"
TEST_SUITE: "install"
install:
- set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%
- "%PYTHON%\\python.exe -m pip install --upgrade pip"
- "%PYTHON%\\python.exe -m pip install -e ."
- "%PYTHON%\\python.exe -m pipenv install -e ."
- "%PYTHON%\\python.exe -m pipenv install --dev"
- echo "Running pipenv at:"
- "%PYTHON%\\python.exe -m pipenv --venv"
- echo "Using python:"
- "%PYTHON%\\python.exe -m pipenv --py"
cache:
- "%LocalAppData%\\pip\\cache"
test_script:
- cmd: set PYPI_VENDOR_DIR=".\tests\pypi\" && %PYTHON%\\python.exe -m pipenv run pytest -v -n auto -m %TEST_SUITE% tests
+14 -14
View File
@@ -11,7 +11,7 @@ from click_didyoumean import DYMCommandCollection
from .__version__ import __version__
from .import environments
from . import environments
from .environments import *
# Enable shell completion.
@@ -23,7 +23,7 @@ class PipenvGroup(click.Group):
"""Custom Group class provides formatted main help"""
def get_help_option(self, ctx):
from .import core
from . import core
"""Override for showing formatted main help via --help and -h options"""
help_options = self.get_help_option_names(ctx)
@@ -148,7 +148,7 @@ def cli(
)
sys.exit(1)
sys.exit(0)
from .import core
from . import core
if man:
if core.system_which('man'):
path = os.sep.join([os.path.dirname(__file__), 'pipenv.1'])
@@ -344,7 +344,7 @@ def install(
keep_outdated=False,
selective_upgrade=False,
):
from .import core
from . import core
core.do_install(
package_name=package_name,
@@ -426,7 +426,7 @@ def uninstall(
verbose=False,
keep_outdated=False,
):
from .import core
from . import core
core.do_uninstall(
package_name=package_name,
@@ -499,7 +499,7 @@ def lock(
pre=False,
keep_outdated=False,
):
from .import core
from . import core
# Ensure that virtualenv is available.
core.ensure_project(three=three, python=python)
@@ -542,7 +542,7 @@ def lock(
def shell(
three=None, python=False, fancy=False, shell_args=None, anyway=False
):
from .import core
from . import core
# Prevent user from activating nested environments.
if 'PIPENV_ACTIVE' in os.environ:
@@ -594,7 +594,7 @@ def shell(
help="Specify which version of Python virtualenv should use.",
)
def run(command, args, three=None, python=False):
from .import core
from . import core
core.do_run(command=command, args=args, three=three, python=python)
@@ -633,7 +633,7 @@ def check(
style=False,
args=None,
):
from .import core
from . import core
core.do_check(
three=three, python=python, system=system, unused=unused, args=args
@@ -719,7 +719,7 @@ def update(
outdated=False,
more_packages=None,
):
from .import core
from . import core
core.ensure_project(three=three, python=python, warn=True)
if not outdated:
@@ -797,7 +797,7 @@ def update(
'--reverse', is_flag=True, default=False, help="Reversed dependency graph."
)
def graph(bare=False, json=False, reverse=False):
from .import core
from . import core
core.do_graph(bare=bare, json=json, reverse=reverse)
@@ -817,7 +817,7 @@ def graph(bare=False, json=False, reverse=False):
)
@click.argument('module', nargs=1)
def run_open(module, three=None, python=None):
from .import core
from . import core
# Ensure that virtualenv is available.
core.ensure_project(three=three, python=python, validate=False)
@@ -895,7 +895,7 @@ def sync(
package_name=None,
sequential=False,
):
from .import core
from . import core
core.do_sync(
ctx=ctx,
@@ -952,7 +952,7 @@ def clean(
user=False,
verbose=False,
):
from .import core
from . import core
core.do_clean(
ctx=ctx, three=three, python=python, dry_run=dry_run, verbose=verbose
+12 -7
View File
@@ -25,7 +25,8 @@ from blindspin import spinner
from requests.packages import urllib3
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from .project import Project
from.project import Project
from .requirements import PipenvRequirement
from .utils import (
convert_deps_from_pip,
convert_deps_to_pip,
@@ -1399,9 +1400,9 @@ def pip_install(
f.write(package_name)
# Install dependencies when a package is a VCS dependency.
try:
req = get_requirement(
req = PipenvRequirement.from_line(
package_name.split('--hash')[0].split('--trusted-host')[0]
).vcs
).requirement.vcs
except (pip9._vendor.pyparsing.ParseException, ValueError) as e:
click.echo('{0}: {1}'.format(crayons.red('WARNING'), e), err=True)
click.echo(
@@ -2198,13 +2199,16 @@ def inline_activate_virtualenv():
def do_run_nt(command, args):
"""Run command by appending space-joined args to it!"""
import subprocess
try:
from shlex import quote as shellquote
except ImportError:
from pipenv.vendor.backports.shlex import quote as shellquote
command = project.scripts.get(command, command)
command = [command] + [shellquote(a) for a in args]
# if you've passed something with crazy quoting...
# ...just don't. (or put it in a script!)
p = subprocess.Popen(
' '.join([command] + list(args)), shell=True, universal_newlines=True
)
p = subprocess.Popen(command, shell=True, universal_newlines=True)
p.communicate()
sys.exit(p.returncode)
@@ -2486,7 +2490,8 @@ def do_clean(
)
installed_package_names = []
for installed in installed_packages:
r = get_requirement(installed)
pipenvreq = PipenvRequirement.from_line(installed)
r = pipenvreq.requirement
# Ignore editable installations.
if not r.editable:
installed_package_names.append(r.name.lower())
+4 -1
View File
@@ -6,7 +6,10 @@ from contextlib import contextmanager
from subprocess import check_call, Popen, PIPE
from collections import namedtuple
from functools import partial, wraps
from pathlib import Path
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from tempfile import NamedTemporaryFile as _ntf
try:
from shutil import which
+4 -1
View File
@@ -8,7 +8,10 @@ import random
import textwrap
from functools import partial
from subprocess import CalledProcessError
from pathlib import Path
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
try:
from shutil import get_terminal_size
+609
View File
@@ -0,0 +1,609 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import
import sys
from pipenv import PIPENV_VENDOR, PIPENV_PATCHED
sys.path.insert(0, PIPENV_VENDOR)
sys.path.insert(0, PIPENV_PATCHED)
import hashlib
import os
import requirements
import six
from attr import attrs, attrib, Factory, validators
from collections import defaultdict
from pip9.index import Link
from pip9.download import path_to_url, url_to_path
from pip9.req.req_install import _strip_extras
from pip9._vendor.distlib.markers import Evaluator
from pipenv.utils import SCHEME_LIST, VCS_LIST, is_installable_file, is_vcs, multi_split, get_converted_relative_path, is_star, is_pinned, is_valid_url
from first import first
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
HASH_STRING = ' --hash={0}'
def _validate_vcs(instance, attr, value):
if value not in VCS_LIST:
raise ValueError('Invalid vcs {0}'.format(value))
_optional_instance_of = lambda cls: validators.optional(validators.instance_of(cls))
@attrs
class Source(object):
#: URL to PyPI instance
url = attrib(default='')
#: If False, skip SSL checks
verify_ssl = attrib(default=True, validator=validators.optional(validators.instance_of(bool)))
#: human name to refer to this source (can be referenced in packages or dev-packages)
name = attrib(default='')
@attrs
class Requires(object):
"""System-level requirements - see PEP508 for more detail"""
os_name = attrib(default=None)
sys_platform = attrib(default=None)
platform_machine = attrib(default=None)
platform_python_implementation = attrib(default=None)
platform_release = attrib(default=None)
platform_system = attrib(default=None)
platform_version = attrib(default=None)
python_version = attrib(default=None)
python_full_version = attrib(default=None)
implementation_name = attrib(default=None)
implementation_version = attrib(default=None)
@attrs
class VCSRequirement(object):
#: vcs reference name (branch / commit / tag)
ref = attrib(default=None)
#: path to hit - without any of the VCS prefixes (like git+ / http+ / etc)
uri = attrib(default=None)
subdirectory = attrib(default=None)
vcs = attrib(validator=validators.optional(_validate_vcs), default=None)
@attrs
class PipfileRequirement(object):
path = attrib(default=None)
uri = attrib(default=None)
name = attrib(default=None)
extras = attrib(default=Factory(list))
markers = attrib(default='')
editable = attrib(default=False)
vcs = attrib(validator=validators.optional(_validate_vcs), default=None)
version = attrib(default='')
index = attrib(default=None)
_hash = attrib(default=None)
hashes = attrib(default=Factory(list))
ref = attrib(default=None)
subdirectory = attrib(default=None)
_link = attrib()
@_link.default
def _init_link(self):
if not self.vcs:
return None
uri = self.uri if self.uri else path_to_url(os.path.abspath(self.path))
return build_vcs_link(
self.vcs,
uri,
name=self.name,
ref=self.ref,
subdirectory=self.subdirectory,
)
def __attrs_post_init__(self):
if self._hash and not self.hashes:
self.hashes = [self._hash]
if self.vcs and self.uri and self._link:
self.uri = _strip_ssh_from_git_uri(self._link.url)
@classmethod
def create(cls, name, pipfile):
_pipfile = {}
if hasattr(pipfile, 'keys'):
_pipfile = dict(pipfile).copy()
_pipfile['name'] = name
_pipfile['version'] = cls._get_version(pipfile)
editable = _pipfile.pop(
'editable'
) if 'editable' in _pipfile else False
vcs_type = first([vcs for vcs in VCS_LIST if vcs in _pipfile])
vcs = _pipfile.pop(vcs_type) if vcs_type else None
_pipfile_vcs_key = None
if vcs:
_pipfile_vcs_key = 'uri' if is_valid_url(vcs) else 'path'
_pipfile['editable'] = editable
_pipfile['vcs'] = vcs_type
if _pipfile_vcs_key and not _pipfile.get(_pipfile_vcs_key):
_pipfile[_pipfile_vcs_key] = vcs
markers = _pipfile.get('markers')
_extra_markers = [k for k in _pipfile.keys() if k in Evaluator.allowed_values.keys()]
if _extra_markers:
markers = list(markers) if markers else []
for marker in _extra_markers:
markers.append(_pipfile.pop(marker))
_pipfile['markers'] = ' and '.join(markers)
return cls(**_pipfile)
@staticmethod
def _get_version(pipfile_entry):
if str(pipfile_entry) == '{}' or is_star(pipfile_entry):
return ''
elif isinstance(pipfile_entry, six.string_types):
return pipfile_entry
return pipfile_entry.get('version', '')
@property
def pip_version(self):
if is_star(self.version):
return self.name
return '{0}{1}'.format(self.name, self.version)
@property
def requirement(self):
req_uri = self.uri
if self.editable and self.path and not self.uri:
req_uri = path_to_url(os.path.abspath(self.path))
return PipenvRequirement._create_requirement(
name=self.pip_version,
path=self.path,
uri=req_uri,
markers=self.markers,
extras=self.extras,
index=self.index,
hashes=self.hashes,
vcs=self.vcs,
editable=self.editable,
link=self._link,
line=None,
)
@attrs
class PipenvRequirement(object):
"""Requirement for Pipenv Use
Provides the following methods:
- as_pipfile
- as_lockfile
- as_requirement
- from_line
- from_pipfile
- resolve
"""
_editable_prefix = '-e '
path = attrib(default=None)
uri = attrib(default=None)
name = attrib(default=None)
extras = attrib(default=Factory(list))
markers = attrib(default='')
editable = attrib(default=False)
vcs = attrib(validator=validators.optional(_validate_vcs), default=None)
link = attrib(default=None)
line = attrib(default=None)
requirement = attrib(default=None)
index = attrib(default=Factory(list))
specs = attrib(default='')
hashes = attrib(default=Factory(list))
@classmethod
def create(cls, req):
creation_attrs = {'requirement': req}
for prop in [
'name',
'extras',
'markers',
'line',
'link',
'vcs',
'editable',
'uri',
'path',
'hashes',
'index',
]:
creation_attrs[prop] = getattr(req, prop, None)
return cls(**creation_attrs)
@property
def original_line(self):
_editable = ''
if self.editable:
_editable += self._editable_prefix
if self.line and (self.path or self.uri):
# original_line adds in -e if necessary
if self.line.startswith(self._editable_prefix):
return self.line
return '{0}{1}'.format(_editable, self.line)
return self.constructed_line
@property
def constructed_line(self):
_editable = ''
if self.editable:
_editable += self._editable_prefix
line = ''
if self.link:
line = '{0}{1}'.format(_editable, self.link.url)
elif self.path or self.uri:
line = '{0}{1}'.format(_editable, self.path or self.uri)
else:
line += self.name
if not self.vcs:
line = '{0}{1}{2}{3}{4}'.format(
line,
self.extras_as_pip,
self.specifiers_as_pip,
self.markers_as_pip,
self.hashes_as_pip,
)
else:
line = self.line
if _editable == self._editable_prefix and not self.line.startswith(
_editable
):
line = '{0}{1}'.format(_editable, self.line)
line = '{0}{1}{2}{3}'.format(
line,
self.extras_as_pip,
self.markers_as_pip,
self.hashes_as_pip,
)
return line
@property
def extras_as_pip(self):
if self.extras:
return '[{0}]'.format(','.join(self.extras))
return ''
@property
def markers_as_pip(self):
if self.markers:
return '; {0}'.format(self.markers)
return ''
@property
def specifiers_as_pip(self):
if hasattr(self.requirement, 'specs'):
return ','.join([''.join(spec) for spec in self.requirement.specs])
return ''
@property
def hashes_as_pip(self):
if self.hashes:
if isinstance(self.hashes, six.string_types):
return HASH_STRING.format(self.hashes)
return ''.join([HASH_STRING.format(h) for h in self.hashes])
return ''
@classmethod
def from_pipfile(cls, name, indexes, pipfile_entry):
pipfile = PipfileRequirement.create(name, pipfile_entry)
return cls.create(pipfile.requirement)
@classmethod
def from_line(cls, line):
"""Pre-clean requirement strings passed to the requirements parser.
Ensures that we can accept both local and relative paths, file and VCS URIs,
remote URIs, and package names, and that we pass only valid requirement strings
to the requirements parser. Performs necessary modifications to requirements
object if the user input was a local relative path.
:param str dep: A requirement line
:returns: :class:`requirements.Requirement` object
"""
hashes = None
if '--hash=' in line:
hashes = line.split(' --hash=')
line, hashes = hashes[0], hashes[1:]
editable = False
original_line = line
_editable = ''
if line.startswith('-e '):
editable = True
_editable += cls._editable_prefix
line = line.split(' ', 1)[1]
line, markers = cls._split_markers(line)
line, extras = _strip_extras(line)
req_dict = defaultdict(None)
vcs = None
if is_installable_file(line):
req_dict = cls._prep_path(line)
req_dict['original_line'] = '{0}{1}'.format(
_editable, req_dict['original_line']
)
elif is_vcs(line):
req_dict = cls._prep_vcs(line)
vcs = first(
_split_vcs_method(
req_dict.get('uri', req_dict.get('path', line))
)
)
req_dict['original_line'] = '{0}{1}'.format(
_editable, req_dict['original_line']
)
else:
req_dict = {
'line': line,
'original_line': original_line,
'name': multi_split(line, '!=<>~')[0],
}
return cls.create(
cls._create_requirement(
line=req_dict['original_line'],
name=req_dict.get('name'),
path=req_dict.get('path'),
uri=req_dict.get('uri'),
link=req_dict.get('link'),
hashes=hashes,
markers=markers,
extras=extras,
editable=editable,
vcs=vcs,
)
)
def as_pipfile(self):
""""Converts a requirement to a Pipfile-formatted one."""
req_dict = {}
req = self.requirement
req_dict = {}
if req.local_file:
hashable_path = req.uri or req.path
dict_key = 'file' if req.uri else 'path'
hashed_path = hashlib.sha256(
hashable_path.encode('utf-8')
).hexdigest(
)
req_dict[dict_key] = hashable_path
req_dict['name'] = hashed_path[
len(hashed_path) - 7:
] if not req.vcs else req.name
elif req.vcs:
if req.name is None:
raise ValueError(
'pipenv requires an #egg fragment for version controlled '
'dependencies. Please install remote dependency '
'in the form {0}#egg=<package-name>.'.format(req.uri)
)
if req.uri and req.uri.startswith('{0}+'.format(req.vcs)):
if req_dict.get('uri'):
# req_dict['uri'] = req.uri[len(req.vcs) + 1:]
del req_dict['uri']
req_dict.update(
{
req.vcs: req.uri[
len(req.vcs) + 1:
] if req.uri else req.path
}
)
if req.subdirectory:
req_dict.update({'subdirectory': req.subdirectory})
if req.revision:
req_dict.update({'ref': req.revision})
elif req.specs:
# Comparison operators: e.g. Django>1.10
specs = ','.join([''.join(spec) for spec in req.specs])
req_dict.update({'version': specs})
else:
req_dict.update({'version': '*'})
if self.extras:
req_dict.update({'extras': self.extras})
if req.editable:
req_dict.update({'editable': req.editable})
if self.hashes:
hash_key = 'hashes'
hashes = self.hashes
if isinstance(hashes, six.string_types) or len(hashes) == 1:
hash_key = 'hash'
if len(hashes) == 1:
hashes = first(hashes)
req_dict.update({hash_key: hashes})
if len(req_dict.keys()) == 1 and req_dict.get('version'):
return {req.name: req_dict.get('version')}
return {req.name: req_dict}
def as_requirement(self, project=None, include_index=False):
"""Creates a requirements.txt compatible output of the current dependency.
:param project: Pipenv Project, defaults to None
:param project: :class:`pipenv.project.Project`, optional
:param include_index: Whether to include the resolved index, defaults to False
:param include_index: bool, optional
"""
line = self.constructed_line
if include_index and not (self.requirement.local_file or self.vcs):
from .utils import prepare_pip_source_args
if self.index:
pip_src_args = [project.get_source(self.index)]
else:
pip_src_args = project.sources
index_string = ' '.join(prepare_pip_source_args(pip_src_args))
line = '{0} {1}'.format(line, index_string)
return line
@staticmethod
def _split_markers(line):
"""Split markers from a dependency"""
if not any(line.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
marker_sep = ';'
else:
marker_sep = '; '
markers = None
if marker_sep in line:
line, markers = line.split(marker_sep, 1)
markers = markers.strip() if markers else None
return line, markers
@staticmethod
def _prep_path(line):
_path = Path(line)
link = Link(_path.absolute().as_uri())
if _path.is_absolute() or _path.as_posix() == '.':
path = _path.as_posix()
else:
path = get_converted_relative_path(line)
name_or_url = link.egg_fragment if link.egg_fragment else link.url_without_fragment
name = link.egg_fragment or link.show_url or link.filename
return {
'link': link,
'path': path,
'line': name_or_url,
'original_line': line,
'name': name,
}
@staticmethod
def _prep_vcs(line):
# Generate a Link object for parsing egg fragments
link = Link(line)
# Save the original path to store in the pipfile
original_uri = link.url
# Construct the requirement using proper git+ssh:// replaced uris or names if available
formatted_line = _clean_git_uri(line)
return {
'link': link,
'uri': formatted_line,
'line': original_uri,
'original_line': line,
'name': link.egg_fragment,
}
@classmethod
def _create_requirement(
cls,
line=None,
name=None,
path=None,
uri=None,
extras=None,
markers=None,
editable=False,
vcs=None,
link=None,
hashes=None,
index=None,
):
_editable = cls._editable_prefix if editable else ''
_line = line or uri or path or name
# We don't want to only use the name on properly
# formatted VCS inputs
if vcs or is_vcs(_line):
_line = uri or path or line
_line = '{0}{1}'.format(_editable, _line)
elif link and path and not uri:
_line = link.url
req = first(requirements.parse(_line))
req.line = _line
if editable:
req.editable = True
if req.name and not any(
getattr(req, prop) for prop in ['uri', 'path']
):
if link and link.scheme.startswith('file') and path:
req.path = path
req.local_file = True
elif link and uri:
req.uri = link.url_without_fragment
elif req.local_file and path and not req.vcs:
req.uri = None
req.path = path
elif req.vcs and not req.local_file and uri != link.url:
req.uri = _strip_ssh_from_git_uri(req.uri)
req.line = line or _strip_ssh_from_git_uri(req.line)
if markers:
req.markers = markers
if extras:
# Bizarrely this is also what pip does...
req.extras = first(
requirements.parse(
'fakepkg{0}'.format(_extras_to_string(extras))
)
).extras
req.link = link
if hashes:
req.hashes = hashes
if index:
req.index = index
return req
def _strip_ssh_from_git_uri(uri):
"""Return git+ssh:// formatted URI to git+git@ format"""
if isinstance(uri, six.string_types):
uri = uri.replace('git+ssh://', 'git+')
return uri
def _clean_git_uri(uri):
"""Cleans VCS uris from pip9 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:
uri = uri.replace('git+', 'git+ssh://')
return uri
def _split_vcs_method(uri):
"""Split a vcs+uri formatted uri into (vcs, uri)"""
vcs_start = '{0}+'
vcs = first(
[vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))]
)
if vcs:
vcs, uri = uri.split('+', 1)
return vcs, uri
def _extras_to_string(extras):
"""Turn a list of extras into a string"""
if isinstance(extras, six.string_types):
if extras.startswith('['):
return extras
else:
extras = [extras]
return '[{0}]'.format(','.join(extras))
def build_vcs_link(
vcs, uri, name=None, ref=None, subdirectory=None, extras= []
):
vcs_start = '{0}+'.format(vcs)
if not uri.startswith(vcs_start):
uri = '{0}{1}'.format(vcs_start, uri)
uri = _clean_git_uri(uri)
if ref:
uri = '{0}@{1}'.format(uri, ref)
if name:
uri = '{0}#egg={1}'.format(uri, name)
if extras:
extras = _extras_to_string(extras)
uri = '{0}{1}'.format(uri, extras)
if subdirectory:
uri = '{0}&subdirectory={1}'.format(uri, subdirectory)
return Link(uri)
+20 -231
View File
@@ -65,8 +65,6 @@ requests = requests.Session()
def get_requirement(dep):
from pip9.req.req_install import _strip_extras
import requirements
"""Pre-clean requirement strings passed to the requirements parser.
@@ -78,79 +76,8 @@ def get_requirement(dep):
:param str dep: A requirement line
:returns: :class:`requirements.Requirement` object
"""
path = None
uri = None
cleaned_uri = None
editable = False
dep_link = None
# check for editable dep / vcs dep
if dep.startswith('-e '):
editable = True
# Use the user supplied path as the written dependency
dep = dep.split(' ', 1)[1]
# Split out markers if they are present - similar to how pip does it
# See pip9.req.req_install.InstallRequirement.from_line
if not any(dep.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
marker_sep = ';'
else:
marker_sep = '; '
if marker_sep in dep:
dep, markers = dep.split(marker_sep, 1)
markers = markers.strip()
if not markers:
markers = None
else:
markers = None
# Strip extras from the requirement so we can make a properly parseable req
dep, extras = _strip_extras(dep)
# Only operate on local, existing, non-URI formatted paths which are installable
if is_installable_file(dep):
dep_path = Path(dep)
dep_link = Link(dep_path.absolute().as_uri())
if dep_path.is_absolute() or dep_path.as_posix() == '.':
path = dep_path.as_posix()
else:
path = get_converted_relative_path(dep)
dep = dep_link.egg_fragment if dep_link.egg_fragment else dep_link.url_without_fragment
elif is_vcs(dep):
# Generate a Link object for parsing egg fragments
dep_link = Link(dep)
# Save the original path to store in the pipfile
uri = dep_link.url
# Construct the requirement using proper git+ssh:// replaced uris or names if available
cleaned_uri = clean_git_uri(dep)
dep = cleaned_uri
if editable:
dep = '-e {0}'.format(dep)
req = [r for r in requirements.parse(dep)][0]
# if all we built was the requirement name and still need everything else
if req.name and not any([req.uri, req.path]):
if dep_link:
if dep_link.scheme.startswith('file') and path and not req.path:
req.path = path
req.local_file = True
req.uri = None
else:
req.uri = dep_link.url_without_fragment
# If the result is a local file with a URI and we have a local path, unset the URI
# and set the path instead -- note that local files may have 'path' set by accident
elif req.local_file and path and not req.vcs:
req.path = path
req.uri = None
elif req.vcs and req.uri and cleaned_uri and cleaned_uri != uri:
req.uri = strip_ssh_from_git_uri(req.uri)
req.line = strip_ssh_from_git_uri(req.line)
req.editable = editable
if markers:
req.markers = markers
if extras:
# Bizarrely this is also what pip does...
req.extras = [
r for r in requirements.parse('fakepkg{0}'.format(extras))
][
0
].extras
return req
from .requirements import PipenvRequirement
return PipenvRequirement.from_line(dep).requirement
def cleanup_toml(tml):
@@ -359,7 +286,7 @@ def actually_resolve_reps(
def venv_resolve_deps(
deps, which, project, pre=False, verbose=False, clear=False
):
from .import resolver
from . import resolver
import json
resolver = escape_grouped_arguments(resolver.__file__.rstrip('co'))
@@ -514,73 +441,10 @@ def multi_split(s, split):
def convert_deps_from_pip(dep):
""""Converts a pip-formatted dependency to a Pipfile-formatted one."""
from .requirements import PipenvRequirement
dependency = {}
req = get_requirement(dep)
extras = {'extras': req.extras}
# File installs.
if (req.uri or req.path or is_installable_file(req.name)) and not req.vcs:
# Assign a package name to the file, last 7 of it's sha256 hex digest.
if not req.uri and not req.path:
req.path = os.path.abspath(req.name)
hashable_path = req.uri if req.uri else req.path
req.name = hashlib.sha256(hashable_path.encode('utf-8')).hexdigest()
req.name = req.name[len(req.name) - 7:]
# {path: uri} TOML (spec 4 I guess...)
if req.uri:
dependency[req.name] = {'file': hashable_path}
else:
dependency[req.name] = {'path': hashable_path}
if req.extras:
dependency[req.name].update(extras)
# Add --editable if applicable
if req.editable:
dependency[req.name].update({'editable': True})
# VCS Installs.
elif req.vcs:
if req.name is None:
raise ValueError(
'pipenv requires an #egg fragment for version controlled '
'dependencies. Please install remote dependency '
'in the form {0}#egg=<package-name>.'.format(req.uri)
)
# Crop off the git+, etc part.
if req.uri.startswith('{0}+'.format(req.vcs)):
req.uri = req.uri[len(req.vcs) + 1:]
dependency.setdefault(req.name, {}).update({req.vcs: req.uri})
# Add --editable, if it's there.
if req.editable:
dependency[req.name].update({'editable': True})
# Add subdirectory, if it's there
if req.subdirectory:
dependency[req.name].update({'subdirectory': req.subdirectory})
# Add the specifier, if it was provided.
if req.revision:
dependency[req.name].update({'ref': req.revision})
# Extras: e.g. #egg=requests[security]
if req.extras:
dependency[req.name].update({'extras': req.extras})
elif req.extras or req.specs:
specs = None
# Comparison operators: e.g. Django>1.10
if req.specs:
r = multi_split(dep, '!=<>~')
specs = dep[len(r[0]):]
dependency[req.name] = specs
# Extras: e.g. requests[socks]
if req.extras:
dependency[req.name] = extras
if specs:
dependency[req.name].update({'version': specs})
# Bare dependencies: e.g. requests
else:
dependency[dep] = '*'
# Cleanup when there's multiple values, e.g. -e.
if len(dependency) > 1:
for key in dependency.copy():
if not hasattr(dependency[key], 'keys'):
del dependency[key]
return dependency
req = PipenvRequirement.from_line(dep)
return req.as_pipfile()
def is_star(val):
@@ -593,95 +457,16 @@ def is_pinned(val):
def convert_deps_to_pip(deps, project=None, r=True, include_index=False):
""""Converts a Pipfile-formatted dependency to a pip-formatted one."""
from .requirements import PipenvRequirement
dependencies = []
for dep in deps.keys():
# Default (e.g. '>1.10').
extra = deps[dep] if isinstance(deps[dep], six.string_types) else ''
version = ''
index = ''
# Get rid of '*'.
if is_star(deps[dep]) or str(extra) == '{}':
extra = ''
hash = ''
# Support for single hash (spec 1).
if 'hash' in deps[dep]:
hash = ' --hash={0}'.format(deps[dep]['hash'])
# Support for multiple hashes (spec 2).
if 'hashes' in deps[dep]:
hash = '{0} '.format(
''.join(
[' --hash={0} '.format(h) for h in deps[dep]['hashes']]
)
)
# Support for extras (e.g. requests[socks])
if 'extras' in deps[dep]:
extra = '[{0}]'.format(','.join(deps[dep]['extras']))
if 'version' in deps[dep]:
if not is_star(deps[dep]['version']):
version = deps[dep]['version']
# For lockfile format.
if 'markers' in deps[dep]:
specs = '; {0}'.format(deps[dep]['markers'])
else:
# For pipfile format.
specs = []
for specifier in specifiers:
if specifier in deps[dep]:
if not is_star(deps[dep][specifier]):
specs.append(
'{0} {1}'.format(specifier, deps[dep][specifier])
)
if specs:
specs = '; {0}'.format(' and '.join(specs))
else:
specs = ''
if include_index and not is_file(deps[dep]) and not is_vcs(deps[dep]):
pip_src_args = []
if 'index' in deps[dep]:
pip_src_args = [project.get_source(deps[dep]['index'])]
else:
pip_src_args = project.sources
pip_args = prepare_pip_source_args(pip_src_args)
index = ' '.join(pip_args)
# Support for version control
maybe_vcs = [vcs for vcs in VCS_LIST if vcs in deps[dep]]
vcs = maybe_vcs[0] if maybe_vcs else None
# Support for files.
if 'file' in deps[dep]:
extra = '{1}{0}'.format(extra, deps[dep]['file']).strip()
# Flag the file as editable if it is a local relative path
if 'editable' in deps[dep]:
dep = '-e '
else:
dep = ''
# Support for paths.
elif 'path' in deps[dep]:
extra = '{1}{0}'.format(extra, deps[dep]['path']).strip()
# Flag the file as editable if it is a local relative path
if 'editable' in deps[dep]:
dep = '-e '
else:
dep = ''
if vcs:
extra = '{0}+{1}'.format(vcs, deps[dep][vcs])
# Support for @refs.
if 'ref' in deps[dep]:
extra += '@{0}'.format(deps[dep]['ref'])
extra += '#egg={0}'.format(dep)
# Support for subdirectory
if 'subdirectory' in deps[dep]:
extra += '&subdirectory={0}'.format(deps[dep]['subdirectory'])
# Support for editable.
if 'editable' in deps[dep]:
# Support for --egg.
dep = '-e '
else:
dep = ''
s = '{0}{1}{2}{3}{4} {5}'.format(
dep, extra, version, specs, hash, index
).strip(
)
dependencies.append(s)
for dep_name, dep in deps.items():
indexes = project.sources if hasattr(project, 'sources') else None
if hasattr(dep, 'keys') and dep.get('index'):
indexes = project.get_source(dep['index'])
new_dep = PipenvRequirement.from_pipfile(dep_name, indexes, dep)
req = new_dep.as_requirement(project=project, include_index=include_index)
req = req.strip()
dependencies.append(req)
if not r:
return dependencies
@@ -754,7 +539,7 @@ def is_editable(pipfile_entry):
def is_vcs(pipfile_entry):
import requirements
from pipenv.vendor import requirements
"""Determine if dictionary entry from Pipfile is for a vcs dependency."""
if hasattr(pipfile_entry, 'keys'):
@@ -1019,6 +804,10 @@ def find_windows_executable(bin_path, exe_name):
return find_executable(exe_name)
def path_to_url(path):
return Path(normalize_drive(os.path.abspath(path))).as_uri()
def get_converted_relative_path(path, relative_to=os.curdir):
"""Given a vague relative path, return the path relative to the given location"""
return os.path.join('.', os.path.relpath(path, start=relative_to))
+57
View File
@@ -0,0 +1,57 @@
from __future__ import absolute_import, division, print_function
from functools import partial
from . import converters, exceptions, filters, validators
from ._config import get_run_validators, set_run_validators
from ._funcs import asdict, assoc, astuple, evolve, has
from ._make import (
NOTHING, Attribute, Factory, attrib, attrs, fields, fields_dict,
make_class, validate
)
__version__ = "18.1.0.dev0"
__title__ = "attrs"
__description__ = "Classes Without Boilerplate"
__uri__ = "http://www.attrs.org/"
__doc__ = __description__ + " <" + __uri__ + ">"
__author__ = "Hynek Schlawack"
__email__ = "hs@ox.cx"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
s = attributes = attrs
ib = attr = attrib
dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
__all__ = [
"Attribute",
"Factory",
"NOTHING",
"asdict",
"assoc",
"astuple",
"attr",
"attrib",
"attributes",
"attrs",
"converters",
"evolve",
"exceptions",
"fields",
"fields_dict",
"filters",
"get_run_validators",
"has",
"ib",
"make_class",
"s",
"set_run_validators",
"validate",
"validators",
]
+146
View File
@@ -0,0 +1,146 @@
from __future__ import absolute_import, division, print_function
import platform
import sys
import types
import warnings
PY2 = sys.version_info[0] == 2
PYPY = platform.python_implementation() == "PyPy"
if PYPY or sys.version_info[:2] >= (3, 6):
ordered_dict = dict
else:
from collections import OrderedDict
ordered_dict = OrderedDict
if PY2:
from UserDict import IterableUserDict
# We 'bundle' isclass instead of using inspect as importing inspect is
# fairly expensive (order of 10-15 ms for a modern machine in 2016)
def isclass(klass):
return isinstance(klass, (type, types.ClassType))
# TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
TYPE = "type"
def iteritems(d):
return d.iteritems()
# Python 2 is bereft of a read-only dict proxy, so we make one!
class ReadOnlyDict(IterableUserDict):
"""
Best-effort read-only dict wrapper.
"""
def __setitem__(self, key, val):
# We gently pretend we're a Python 3 mappingproxy.
raise TypeError("'mappingproxy' object does not support item "
"assignment")
def update(self, _):
# We gently pretend we're a Python 3 mappingproxy.
raise AttributeError("'mappingproxy' object has no attribute "
"'update'")
def __delitem__(self, _):
# We gently pretend we're a Python 3 mappingproxy.
raise TypeError("'mappingproxy' object does not support item "
"deletion")
def clear(self):
# We gently pretend we're a Python 3 mappingproxy.
raise AttributeError("'mappingproxy' object has no attribute "
"'clear'")
def pop(self, key, default=None):
# We gently pretend we're a Python 3 mappingproxy.
raise AttributeError("'mappingproxy' object has no attribute "
"'pop'")
def popitem(self):
# We gently pretend we're a Python 3 mappingproxy.
raise AttributeError("'mappingproxy' object has no attribute "
"'popitem'")
def setdefault(self, key, default=None):
# We gently pretend we're a Python 3 mappingproxy.
raise AttributeError("'mappingproxy' object has no attribute "
"'setdefault'")
def __repr__(self):
# Override to be identical to the Python 3 version.
return "mappingproxy(" + repr(self.data) + ")"
def metadata_proxy(d):
res = ReadOnlyDict()
res.data.update(d) # We blocked update, so we have to do it like this.
return res
else:
def isclass(klass):
return isinstance(klass, type)
TYPE = "class"
def iteritems(d):
return d.items()
def metadata_proxy(d):
return types.MappingProxyType(dict(d))
def import_ctypes():
"""
Moved into a function for testability.
"""
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.
"""
if PYPY: # pragma: no cover
def set_closure_cell(cell, value):
cell.__setstate__((value,))
else:
try:
ctypes = import_ctypes()
set_closure_cell = ctypes.pythonapi.PyCell_Set
set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
set_closure_cell.restype = ctypes.c_int
except Exception:
# We try best effort to set the cell, but sometimes it's not
# possible. For example on Jython or on GAE.
set_closure_cell = just_warn
return set_closure_cell
set_closure_cell = make_set_closure_cell()
+23
View File
@@ -0,0 +1,23 @@
from __future__ import absolute_import, division, print_function
__all__ = ["set_run_validators", "get_run_validators"]
_run_validators = True
def set_run_validators(run):
"""
Set whether or not validators are run. By default, they are run.
"""
if not isinstance(run, bool):
raise TypeError("'run' must be bool.")
global _run_validators
_run_validators = run
def get_run_validators():
"""
Return whether or not validators are run.
"""
return _run_validators
+212
View File
@@ -0,0 +1,212 @@
from __future__ import absolute_import, division, print_function
import copy
from ._compat import iteritems
from ._make import NOTHING, _obj_setattr, fields
from .exceptions import AttrsAttributeNotFoundError
def asdict(inst, recurse=True, filter=None, dict_factory=dict,
retain_collection_types=False):
"""
Return the ``attrs`` attribute values of *inst* as a dict.
Optionally recurse into other ``attrs``-decorated classes.
:param inst: Instance of an ``attrs``-decorated class.
:param bool recurse: Recurse into classes that are also
``attrs``-decorated.
:param callable filter: A callable whose return code determines whether an
attribute or element is included (``True``) or dropped (``False``). Is
called with the :class:`attr.Attribute` as the first argument and the
value as the second argument.
:param callable dict_factory: A callable to produce dictionaries from. For
example, to produce ordered dictionaries instead of normal Python
dictionaries, pass in ``collections.OrderedDict``.
:param bool retain_collection_types: Do not convert to ``list`` when
encountering an attribute whose type is ``tuple`` or ``set``. Only
meaningful if ``recurse`` is ``True``.
:rtype: return type of *dict_factory*
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
class.
.. versionadded:: 16.0.0 *dict_factory*
.. versionadded:: 16.1.0 *retain_collection_types*
"""
attrs = fields(inst.__class__)
rv = dict_factory()
for a in attrs:
v = getattr(inst, a.name)
if filter is not None and not filter(a, v):
continue
if recurse is True:
if has(v.__class__):
rv[a.name] = asdict(v, recurse=True, filter=filter,
dict_factory=dict_factory)
elif isinstance(v, (tuple, list, set)):
cf = v.__class__ if retain_collection_types is True else list
rv[a.name] = cf([
asdict(i, recurse=True, filter=filter,
dict_factory=dict_factory)
if has(i.__class__) else i
for i in v
])
elif isinstance(v, dict):
df = dict_factory
rv[a.name] = df((
asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
for kk, vv in iteritems(v))
else:
rv[a.name] = v
else:
rv[a.name] = v
return rv
def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
retain_collection_types=False):
"""
Return the ``attrs`` attribute values of *inst* as a tuple.
Optionally recurse into other ``attrs``-decorated classes.
:param inst: Instance of an ``attrs``-decorated class.
:param bool recurse: Recurse into classes that are also
``attrs``-decorated.
:param callable filter: A callable whose return code determines whether an
attribute or element is included (``True``) or dropped (``False``). Is
called with the :class:`attr.Attribute` as the first argument and the
value as the second argument.
:param callable tuple_factory: A callable to produce tuples from. For
example, to produce lists instead of tuples.
:param bool retain_collection_types: Do not convert to ``list``
or ``dict`` when encountering an attribute which type is
``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is
``True``.
:rtype: return type of *tuple_factory*
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
class.
.. versionadded:: 16.2.0
"""
attrs = fields(inst.__class__)
rv = []
retain = retain_collection_types # Very long. :/
for a in attrs:
v = getattr(inst, a.name)
if filter is not None and not filter(a, v):
continue
if recurse is True:
if has(v.__class__):
rv.append(astuple(v, recurse=True, filter=filter,
tuple_factory=tuple_factory,
retain_collection_types=retain))
elif isinstance(v, (tuple, list, set)):
cf = v.__class__ if retain is True else list
rv.append(cf([
astuple(j, recurse=True, filter=filter,
tuple_factory=tuple_factory,
retain_collection_types=retain)
if has(j.__class__) else j
for j in v
]))
elif isinstance(v, dict):
df = v.__class__ if retain is True else dict
rv.append(df(
(
astuple(
kk,
tuple_factory=tuple_factory,
retain_collection_types=retain
) if has(kk.__class__) else kk,
astuple(
vv,
tuple_factory=tuple_factory,
retain_collection_types=retain
) if has(vv.__class__) else vv
)
for kk, vv in iteritems(v)))
else:
rv.append(v)
else:
rv.append(v)
return rv if tuple_factory is list else tuple_factory(rv)
def has(cls):
"""
Check whether *cls* is a class with ``attrs`` attributes.
:param type cls: Class to introspect.
:raise TypeError: If *cls* is not a class.
:rtype: :class:`bool`
"""
return getattr(cls, "__attrs_attrs__", None) is not None
def assoc(inst, **changes):
"""
Copy *inst* and apply *changes*.
:param inst: Instance of a class with ``attrs`` attributes.
:param changes: Keyword changes in the new copy.
:return: A copy of inst with *changes* incorporated.
:raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
be found on *cls*.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
class.
.. deprecated:: 17.1.0
Use :func:`evolve` instead.
"""
import warnings
warnings.warn("assoc is deprecated and will be removed after 2018/01.",
DeprecationWarning, stacklevel=2)
new = copy.copy(inst)
attrs = fields(inst.__class__)
for k, v in iteritems(changes):
a = getattr(attrs, k, NOTHING)
if a is NOTHING:
raise AttrsAttributeNotFoundError(
"{k} is not an attrs attribute on {cl}."
.format(k=k, cl=new.__class__)
)
_obj_setattr(new, k, v)
return new
def evolve(inst, **changes):
"""
Create a new instance, based on *inst* with *changes* applied.
:param inst: Instance of a class with ``attrs`` attributes.
:param changes: Keyword changes in the new copy.
:return: A copy of inst with *changes* incorporated.
:raise TypeError: If *attr_name* couldn't be found in the class
``__init__``.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
class.
.. versionadded:: 17.1.0
"""
cls = inst.__class__
attrs = fields(cls)
for a in attrs:
if not a.init:
continue
attr_name = a.name # To deal with private attributes.
init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
if init_name not in changes:
changes[init_name] = getattr(inst, attr_name)
return cls(**changes)
+1680
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
"""
Commonly useful converters.
"""
from __future__ import absolute_import, division, print_function
def optional(converter):
"""
A converter that allows an attribute to be optional. An optional attribute
is one which can be set to ``None``.
:param callable converter: the converter that is used for non-``None``
values.
.. versionadded:: 17.1.0
"""
def optional_converter(val):
if val is None:
return None
return converter(val)
return optional_converter
+48
View File
@@ -0,0 +1,48 @@
from __future__ import absolute_import, division, print_function
class FrozenInstanceError(AttributeError):
"""
A frozen/immutable instance has been attempted to be modified.
It mirrors the behavior of ``namedtuples`` by using the same error message
and subclassing :exc:`AttributeError`.
.. versionadded:: 16.1.0
"""
msg = "can't set attribute"
args = [msg]
class AttrsAttributeNotFoundError(ValueError):
"""
An ``attrs`` function couldn't find an attribute that the user asked for.
.. versionadded:: 16.2.0
"""
class NotAnAttrsClassError(ValueError):
"""
A non-``attrs`` class has been passed into an ``attrs`` function.
.. versionadded:: 16.2.0
"""
class DefaultAlreadySetError(RuntimeError):
"""
A default has been set using ``attr.ib()`` and is attempted to be reset
using the decorator.
.. versionadded:: 17.1.0
"""
class UnannotatedAttributeError(RuntimeError):
"""
A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type
annotation.
.. versionadded:: 17.3.0
"""
+52
View File
@@ -0,0 +1,52 @@
"""
Commonly useful filters for :func:`attr.asdict`.
"""
from __future__ import absolute_import, division, print_function
from ._compat import isclass
from ._make import Attribute
def _split_what(what):
"""
Returns a tuple of `frozenset`s of classes and attributes.
"""
return (
frozenset(cls for cls in what if isclass(cls)),
frozenset(cls for cls in what if isinstance(cls, Attribute)),
)
def include(*what):
"""
Whitelist *what*.
:param what: What to whitelist.
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
:rtype: :class:`callable`
"""
cls, attrs = _split_what(what)
def include_(attribute, value):
return value.__class__ in cls or attribute in attrs
return include_
def exclude(*what):
"""
Blacklist *what*.
:param what: What to blacklist.
:type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
:rtype: :class:`callable`
"""
cls, attrs = _split_what(what)
def exclude_(attribute, value):
return value.__class__ not in cls and attribute not in attrs
return exclude_
+166
View File
@@ -0,0 +1,166 @@
"""
Commonly useful validators.
"""
from __future__ import absolute_import, division, print_function
from ._make import _AndValidator, and_, attrib, attrs
__all__ = [
"and_",
"in_",
"instance_of",
"optional",
"provides",
]
@attrs(repr=False, slots=True, hash=True)
class _InstanceOfValidator(object):
type = attrib()
def __call__(self, inst, attr, value):
"""
We use a callable class to be able to change the ``__repr__``.
"""
if not isinstance(value, self.type):
raise TypeError(
"'{name}' must be {type!r} (got {value!r} that is a "
"{actual!r})."
.format(name=attr.name, type=self.type,
actual=value.__class__, value=value),
attr, self.type, value,
)
def __repr__(self):
return (
"<instance_of validator for type {type!r}>"
.format(type=self.type)
)
def instance_of(type):
"""
A validator that raises a :exc:`TypeError` if the initializer is called
with a wrong type for this particular attribute (checks are performed using
:func:`isinstance` therefore it's also valid to pass a tuple of types).
:param type: The type to check for.
:type type: type or tuple of types
:raises TypeError: With a human readable error message, the attribute
(of type :class:`attr.Attribute`), the expected type, and the value it
got.
"""
return _InstanceOfValidator(type)
@attrs(repr=False, slots=True, hash=True)
class _ProvidesValidator(object):
interface = attrib()
def __call__(self, inst, attr, value):
"""
We use a callable class to be able to change the ``__repr__``.
"""
if not self.interface.providedBy(value):
raise TypeError(
"'{name}' must provide {interface!r} which {value!r} "
"doesn't."
.format(name=attr.name, interface=self.interface, value=value),
attr, self.interface, value,
)
def __repr__(self):
return (
"<provides validator for interface {interface!r}>"
.format(interface=self.interface)
)
def provides(interface):
"""
A validator that raises a :exc:`TypeError` if the initializer is called
with an object that does not provide the requested *interface* (checks are
performed using ``interface.providedBy(value)`` (see `zope.interface
<https://zopeinterface.readthedocs.io/en/latest/>`_).
:param zope.interface.Interface interface: The interface to check for.
:raises TypeError: With a human readable error message, the attribute
(of type :class:`attr.Attribute`), the expected interface, and the
value it got.
"""
return _ProvidesValidator(interface)
@attrs(repr=False, slots=True, hash=True)
class _OptionalValidator(object):
validator = attrib()
def __call__(self, inst, attr, value):
if value is None:
return
self.validator(inst, attr, value)
def __repr__(self):
return (
"<optional validator for {what} or None>"
.format(what=repr(self.validator))
)
def optional(validator):
"""
A validator that makes an attribute optional. An optional attribute is one
which can be set to ``None`` in addition to satisfying the requirements of
the sub-validator.
:param validator: A validator (or a list of validators) that is used for
non-``None`` values.
:type validator: callable or :class:`list` of callables.
.. versionadded:: 15.1.0
.. versionchanged:: 17.1.0 *validator* can be a list of validators.
"""
if isinstance(validator, list):
return _OptionalValidator(_AndValidator(validator))
return _OptionalValidator(validator)
@attrs(repr=False, slots=True, hash=True)
class _InValidator(object):
options = attrib()
def __call__(self, inst, attr, value):
if value not in self.options:
raise ValueError(
"'{name}' must be in {options!r} (got {value!r})"
.format(name=attr.name, options=self.options, value=value)
)
def __repr__(self):
return (
"<in_ validator with options {options!r}>"
.format(options=self.options)
)
def in_(options):
"""
A validator that raises a :exc:`ValueError` if the initializer is called
with a value that does not belong in the options provided. The check is
performed using ``value in options``.
:param options: Allowed options.
:type options: list, tuple, :class:`enum.Enum`, ...
:raises ValueError: With a human readable error message, the attribute (of
type :class:`attr.Attribute`), the expected options, and the value it
got.
.. versionadded:: 17.1.0
"""
return _InValidator(options)
+2 -1
View File
@@ -2,4 +2,5 @@ from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
from . import shutil_get_terminal_size
from . import weakref
from . import weakref
from . import shlex
+9
View File
@@ -0,0 +1,9 @@
"""
Partial backport of python 3's shlex module.
Include's only `shlex.quote()` for backwards compatible functionality.
"""
__all__ = ['quote']
from .shlex import quote
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
import sys
__all__ = ['quote']
if sys.version_info >= (3, 1):
_find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII).search
else:
_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
def quote(s):
"""Return a shell-escaped version of the string *s*."""
if not s:
return "''"
if _find_unsafe(s) is None:
return s
# use single quotes, and put single quotes into double quotes
# the string $'b is then quoted as '$'"'"'b'
return "'" + s.replace("'", "'\"'\"'") + "'"
+105 -208
View File
@@ -1,7 +1,3 @@
# Copyright (c) 2014-2017 Matthias C. M. Troffaes
# Copyright (c) 2012-2014 Antoine Pitrou and contributors
# Distributed under the terms of the MIT License.
import ctypes
import fnmatch
import functools
@@ -13,7 +9,8 @@ import re
import six
import sys
from collections import Sequence
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST, EPERM, EACCES
from contextlib import contextmanager
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST
from operator import attrgetter
from stat import (
S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO)
@@ -27,22 +24,23 @@ try:
intern = intern
except NameError:
intern = sys.intern
try:
basestring = basestring
except NameError:
basestring = str
supports_symlinks = True
if os.name == 'nt':
try:
import nt
except ImportError:
nt = None
else:
if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
from nt import _getfinalpathname
else:
supports_symlinks = False
_getfinalpathname = None
else:
nt = None
try:
from os import scandir as os_scandir
except ImportError:
from scandir import scandir as os_scandir
__all__ = [
"PurePath", "PurePosixPath", "PureWindowsPath",
@@ -61,15 +59,12 @@ def _py2_fsencode(parts):
else part for part in parts]
def _try_except_fileexistserror(try_func, except_func, else_func=None):
def _try_except_fileexistserror(try_func, except_func):
if sys.version_info >= (3, 3):
try:
try_func()
except FileExistsError as exc:
except_func(exc)
else:
if else_func is not None:
else_func()
else:
try:
try_func()
@@ -78,45 +73,6 @@ def _try_except_fileexistserror(try_func, except_func, else_func=None):
raise
else:
except_func(exc)
else:
if else_func is not None:
else_func()
def _try_except_filenotfounderror(try_func, except_func):
if sys.version_info >= (3, 3):
try:
try_func()
except FileNotFoundError as exc:
except_func(exc)
else:
try:
try_func()
except EnvironmentError as exc:
if exc.errno != ENOENT:
raise
else:
except_func(exc)
def _try_except_permissionerror_iter(try_iter, except_iter):
if sys.version_info >= (3, 3):
try:
for x in try_iter():
yield x
except PermissionError as exc:
for x in except_iter(exc):
yield x
else:
try:
for x in try_iter():
yield x
except EnvironmentError as exc:
if exc.errno not in (EPERM, EACCES):
raise
else:
for x in except_iter(exc):
yield x
def _win32_get_unique_path_id(path):
@@ -264,7 +220,10 @@ class _WindowsFlavour(_Flavour):
is_supported = (os.name == 'nt')
drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
drive_letters = (
set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
set(chr(x) for x in range(ord('A'), ord('Z') + 1))
)
ext_namespace_prefix = '\\\\?\\'
reserved_names = (
@@ -324,28 +283,12 @@ class _WindowsFlavour(_Flavour):
def casefold_parts(self, parts):
return [p.lower() for p in parts]
def resolve(self, path, strict=False):
def resolve(self, path):
s = str(path)
if not s:
return os.getcwd()
previous_s = None
if _getfinalpathname is not None:
if strict:
return self._ext_to_normal(_getfinalpathname(s))
else:
# End of the path after the first one not found
tail_parts = []
while True:
try:
s = self._ext_to_normal(_getfinalpathname(s))
except FileNotFoundError:
previous_s = s
s, tail = os.path.split(s)
tail_parts.append(tail)
if previous_s == s:
return path
else:
return os.path.join(s, *reversed(tail_parts))
return self._ext_to_normal(_getfinalpathname(s))
# Means fallback on absolute
return None
@@ -450,7 +393,7 @@ class _PosixFlavour(_Flavour):
def casefold_parts(self, parts):
return parts
def resolve(self, path, strict=False):
def resolve(self, path):
sep = self.sep
accessor = path._accessor
seen = {}
@@ -481,10 +424,9 @@ class _PosixFlavour(_Flavour):
try:
target = accessor.readlink(newpath)
except OSError as e:
if e.errno != EINVAL and strict:
if e.errno != EINVAL:
raise
# Not a symlink, or non-strict mode. We just leave the path
# untouched.
# Not a symlink
path = newpath
else:
seen[newpath] = None # not resolved symlink
@@ -521,7 +463,6 @@ class _PosixFlavour(_Flavour):
raise RuntimeError("Can't determine home directory "
"for %r" % username)
_windows_flavour = _WindowsFlavour()
_posix_flavour = _PosixFlavour()
@@ -554,8 +495,6 @@ class _NormalAccessor(_Accessor):
listdir = _wrap_strfunc(os.listdir)
scandir = _wrap_strfunc(os_scandir)
chmod = _wrap_strfunc(os.chmod)
if hasattr(os, "lchmod"):
@@ -602,6 +541,27 @@ _normal_accessor = _NormalAccessor()
# Globbing helpers
#
@contextmanager
def _cached(func):
try:
func.__cached__
yield func
except AttributeError:
cache = {}
def wrapper(*args):
try:
return cache[args]
except KeyError:
value = cache[args] = func(*args)
return value
wrapper.__cached__ = True
try:
yield wrapper
finally:
cache.clear()
def _make_selector(pattern_parts):
pat = pattern_parts[0]
child_parts = pattern_parts[1:]
@@ -616,7 +576,6 @@ def _make_selector(pattern_parts):
cls = _PreciseSelector
return cls(pat, child_parts)
if hasattr(functools, "lru_cache"):
_make_selector = functools.lru_cache()(_make_selector)
@@ -630,10 +589,8 @@ class _Selector:
self.child_parts = child_parts
if child_parts:
self.successor = _make_selector(child_parts)
self.dironly = True
else:
self.successor = _TerminatingSelector()
self.dironly = False
def select_from(self, parent_path):
"""Iterate over all child paths of `parent_path` matched by this
@@ -641,15 +598,13 @@ class _Selector:
path_cls = type(parent_path)
is_dir = path_cls.is_dir
exists = path_cls.exists
scandir = parent_path._accessor.scandir
if not is_dir(parent_path):
return iter([])
return self._select_from(parent_path, is_dir, exists, scandir)
listdir = parent_path._accessor.listdir
return self._select_from(parent_path, is_dir, exists, listdir)
class _TerminatingSelector:
def _select_from(self, parent_path, is_dir, exists, scandir):
def _select_from(self, parent_path, is_dir, exists, listdir):
yield parent_path
@@ -659,20 +614,14 @@ class _PreciseSelector(_Selector):
self.name = name
_Selector.__init__(self, child_parts)
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
path = parent_path._make_child_relpath(self.name)
if (is_dir if self.dironly else exists)(path):
for p in self.successor._select_from(
path, is_dir, exists, scandir):
yield p
def except_iter(exc):
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
path = parent_path._make_child_relpath(self.name)
if exists(path):
for p in self.successor._select_from(
path, is_dir, exists, listdir):
yield p
class _WildcardSelector(_Selector):
@@ -681,26 +630,17 @@ class _WildcardSelector(_Selector):
self.pat = re.compile(fnmatch.translate(pat))
_Selector.__init__(self, child_parts)
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
cf = parent_path._flavour.casefold
entries = list(scandir(parent_path))
for entry in entries:
if not self.dironly or entry.is_dir():
name = entry.name
casefolded = cf(name)
if self.pat.match(casefolded):
path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(
path, is_dir, exists, scandir):
yield p
def except_iter(exc):
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
cf = parent_path._flavour.casefold
for name in listdir(parent_path):
casefolded = cf(name)
if self.pat.match(casefolded):
path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(
path, is_dir, exists, listdir):
yield p
class _RecursiveWildcardSelector(_Selector):
@@ -708,46 +648,31 @@ class _RecursiveWildcardSelector(_Selector):
def __init__(self, pat, child_parts):
_Selector.__init__(self, child_parts)
def _iterate_directories(self, parent_path, is_dir, scandir):
def _iterate_directories(self, parent_path, is_dir, listdir):
yield parent_path
for name in listdir(parent_path):
path = parent_path._make_child_relpath(name)
if is_dir(path):
for p in self._iterate_directories(path, is_dir, listdir):
yield p
def try_iter():
entries = list(scandir(parent_path))
for entry in entries:
if entry.is_dir() and not entry.is_symlink():
path = parent_path._make_child_relpath(entry.name)
for p in self._iterate_directories(path, is_dir, scandir):
yield p
def except_iter(exc):
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
with _cached(listdir) as listdir:
yielded = set()
try:
successor_select = self.successor._select_from
for starting_point in self._iterate_directories(
parent_path, is_dir, scandir):
parent_path, is_dir, listdir):
for p in successor_select(
starting_point, is_dir, exists, scandir):
starting_point, is_dir, exists, listdir):
if p not in yielded:
yield p
yielded.add(p)
finally:
yielded.clear()
def except_iter(exc):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
#
# Public API
@@ -818,25 +743,13 @@ class PurePath(object):
for a in args:
if isinstance(a, PurePath):
parts += a._parts
elif isinstance(a, basestring):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
else:
if sys.version_info >= (3, 6):
a = os.fspath(a)
else:
# duck typing for older Python versions
if hasattr(a, "__fspath__"):
a = a.__fspath__()
if isinstance(a, str):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
# also handle unicode for PY2 (six.text_type = unicode)
elif six.PY2 and isinstance(a, six.text_type):
# cast to str using filesystem encoding
parts.append(a.encode(sys.getfilesystemencoding()))
else:
raise TypeError(
"argument should be a str object or an os.PathLike "
"object returning str, not %r"
% type(a))
raise TypeError(
"argument should be a path or str object, not %r"
% type(a))
return cls._flavour.parse_parts(parts)
@classmethod
@@ -870,7 +783,7 @@ class PurePath(object):
return cls._flavour.join(parts)
def _init(self):
# Overridden in concrete Path
# Overriden in concrete Path
pass
def _make_child(self, args):
@@ -889,9 +802,6 @@ class PurePath(object):
self._parts) or '.'
return self._str
def __fspath__(self):
return str(self)
def as_posix(self):
"""Return the string representation of the path with forward (/)
slashes."""
@@ -1072,7 +982,7 @@ class PurePath(object):
cf = self._flavour.casefold_parts
if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
raise ValueError("{0!r} does not start with {1!r}"
raise ValueError("{!r} does not start with {!r}"
.format(str(self), str(formatted)))
return self._from_parsed_parts('', root if n == 1 else '',
abs_parts[n:])
@@ -1160,12 +1070,6 @@ class PurePath(object):
return True
# Can't subclass os.PathLike from PurePath and keep the constructor
# optimizations in PurePath._parse_args().
if sys.version_info >= (3, 6):
os.PathLike.register(PurePath)
class PurePosixPath(PurePath):
_flavour = _posix_flavour
__slots__ = ()
@@ -1252,8 +1156,8 @@ class Path(PurePath):
return cls(cls()._flavour.gethomedir(None))
def samefile(self, other_path):
"""Return whether other_path is the same or not as this file
(as returned by os.path.samefile()).
"""Return whether `other_file` is the same or not as this file.
(as returned by os.path.samefile(file, other_file)).
"""
if hasattr(os.path, "samestat"):
st = self.stat()
@@ -1287,8 +1191,6 @@ class Path(PurePath):
"""Iterate over this subtree and yield all existing files (of any
kind, including directories) matching the given pattern.
"""
if not pattern:
raise ValueError("Unacceptable pattern: {0!r}".format(pattern))
pattern = self._flavour.casefold(pattern)
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
if drv or root:
@@ -1327,7 +1229,7 @@ class Path(PurePath):
obj._init(template=self)
return obj
def resolve(self, strict=False):
def resolve(self):
"""
Make the path absolute, resolving all symlinks on the way and also
normalizing it (for example turning slashes into backslashes under
@@ -1335,7 +1237,7 @@ class Path(PurePath):
"""
if self._closed:
self._raise_closed()
s = self._flavour.resolve(self, strict=strict)
s = self._flavour.resolve(self)
if s is None:
# No symlink resolution => for consistency, raise an error if
# the path doesn't exist or is forbidden
@@ -1405,7 +1307,7 @@ class Path(PurePath):
if not isinstance(data, six.binary_type):
raise TypeError(
'data must be %s, not %s' %
(six.binary_type.__name__, data.__class__.__name__))
(six.binary_type.__class__.__name__, data.__class__.__name__))
with self.open(mode='wb') as f:
return f.write(data)
@@ -1416,7 +1318,7 @@ class Path(PurePath):
if not isinstance(data, six.text_type):
raise TypeError(
'data must be %s, not %s' %
(six.text_type.__name__, data.__class__.__name__))
(six.text_type.__class__.__name__, data.__class__.__name__))
with self.open(mode='w', encoding=encoding, errors=errors) as f:
return f.write(data)
@@ -1444,26 +1346,27 @@ class Path(PurePath):
os.close(fd)
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
Create a new directory at this given path.
"""
def helper(exc):
if not exist_ok or not self.is_dir():
raise exc
if self._closed:
self._raise_closed()
def _try_func():
self._accessor.mkdir(self, mode)
def _exc_func(exc):
if not parents or self.parent == self:
raise exc
self.parent.mkdir(parents=True, exist_ok=True)
self.mkdir(mode, parents=False, exist_ok=exist_ok)
try:
_try_except_filenotfounderror(_try_func, _exc_func)
except OSError:
if not exist_ok or not self.is_dir():
raise
if not parents:
_try_except_fileexistserror(
lambda: self._accessor.mkdir(self, mode),
helper)
else:
try:
_try_except_fileexistserror(
lambda: self._accessor.mkdir(self, mode),
helper)
except OSError as e:
if e.errno != ENOENT:
raise
self.parent.mkdir(parents=True)
self._accessor.mkdir(self, mode)
def chmod(self, mode):
"""
@@ -1661,9 +1564,3 @@ class PosixPath(Path, PurePosixPath):
class WindowsPath(Path, PureWindowsPath):
__slots__ = ()
def owner(self):
raise NotImplementedError("Path.owner() is unsupported on this system")
def group(self):
raise NotImplementedError("Path.group() is unsupported on this system")
+1 -1
View File
@@ -22,7 +22,7 @@ required = [
'setuptools>=36.2.1',
'virtualenv-clone>=0.2.5',
'virtualenv',
'pathlib;python_version<"3.4"',
'pathlib2==2.1.0;python_version<"3.4"',
'requests[security];python_version<"3.0"',
'ordereddict;python_version<"3.0"',
]
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -964,7 +964,7 @@ flask = "==0.12.2"
click = "==6.7"
[dev-packages]
requests = {git = "https://github.com/requests/requests", egg = "requests"}
requests = {git = "https://github.com/requests/requests.git"}
""".strip()
f.write(contents)
+34
View File
@@ -0,0 +1,34 @@
# -*- coding=utf-8 -*-
import os
import pytest
from first import first
from pipenv import requirements
from pipenv.utils import get_requirement, convert_deps_from_pip, convert_deps_to_pip
class TestRequirements:
@pytest.mark.requirement
@pytest.mark.parametrize(
'line, pipfile',
[
['requests', {'requests': '*'}],
['requests[socks]', {'requests': {'extras': ['socks'], 'version': '*'}}],
['django>1.10', {'django': '>1.10'}],
['requests[socks]>1.10', {'requests': {'extras': ['socks'], 'version': '>1.10'}}],
['-e git+git://github.com/pinax/pinax.git@1.4#egg=pinax', {'pinax': {'git': 'git://github.com/pinax/pinax.git', 'ref': '1.4', 'editable': True}}],
['git+git://github.com/pinax/pinax.git@1.4#egg=pinax', {'pinax': {'git': 'git://github.com/pinax/pinax.git', 'ref': '1.4'}}],
['FooProject==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', {'FooProject': {'version': '==1.2', 'hash': 'sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'}}],
['FooProject[stuff]==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', {'FooProject': {'version': '==1.2', 'extras': ['stuff'], 'hash': 'sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'}}],
['git+https://github.com/requests/requests.git@master#egg=requests[security]', {'requests': {'git': 'https://github.com/requests/requests.git', 'ref': 'master', 'extras': ['security']}}],
['-e svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject', {u'MyProject': {u'svn': u'svn://svn.myproject.org/svn/MyProject', 'editable': True}}],
['hg+http://hg.myproject.org/MyProject@da39a3ee5e6b#egg=MyProject', {'MyProject': {'hg': 'http://hg.myproject.org/MyProject', 'ref': 'da39a3ee5e6b'}}]
]
)
def test_pip_requirements(self, line, pipfile):
from_line = requirements.PipenvRequirement.from_line(line)
pipfile_pkgname = first([k for k in pipfile.keys()])
pipfile_entry = pipfile[pipfile_pkgname]
from_pipfile = requirements.PipenvRequirement.from_pipfile(pipfile_pkgname, [], pipfile_entry)
assert from_line.as_pipfile() == pipfile
assert from_pipfile.as_requirement() == line
+1 -1
View File
@@ -86,7 +86,7 @@ class TestUtils:
# requests[socks]
dep = 'requests[socks]'
dep = pipenv.utils.convert_deps_from_pip(dep)
assert dep == {'requests': {'extras': ['socks']}}
assert dep == {'requests': {'extras': ['socks'], 'version': '*'}}
# requests[socks] w/ version
dep = 'requests[socks]==1.10'
dep = pipenv.utils.convert_deps_from_pip(dep)