Update all vendored dependencies

- Clean up test config and environment variable handling
- Unset env var changes performend by `pipenv run`
- Make `environments.is_in_virtualenv()` more dynamic -- read
  environment on the fly
- Split up tests on `pipenv run` to reduce complexity -- one test for
  global run (no virtualenv creation), one test for virtualenv creation
- Add `warn_in_virtualenv` call to `run` command, why doesn't click
  invoke this automatically?

Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
Dan Ryan
2019-01-21 19:05:25 -05:00
parent ed3f206e64
commit 574fe7308d
81 changed files with 5540 additions and 1963 deletions
+3 -2
View File
@@ -300,6 +300,7 @@ def uninstall(
if retcode:
sys.exit(retcode)
@cli.command(short_help="Generates Pipfile.lock.", context_settings=CONTEXT_SETTINGS)
@lock_options
@pass_state
@@ -399,8 +400,8 @@ def shell(
@pass_state
def run(state, command, args):
"""Spawns a command installed into the virtualenv."""
from ..core import do_run
from ..core import do_run, warn_in_virtualenv
warn_in_virtualenv()
do_run(
command=command, args=args, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror
)
+18 -5
View File
@@ -2158,7 +2158,7 @@ def do_shell(three=None, python=False, fancy=False, shell_args=None, pypi_mirror
# Only set PIPENV_ACTIVE after finishing reading virtualenv_location
# otherwise its value will be changed
os.environ["PIPENV_ACTIVE"] = vistir.misc.fs_str("1")
os.environ.pop("PIP_SHIMS_BASE_MODULE", None)
if fancy:
@@ -2297,6 +2297,8 @@ def do_run(command, args, three=None, python=False, pypi_mirror=None):
load_dot_env()
previous_pip_shims_module = os.environ.pop("PIP_SHIMS_BASE_MODULE", None)
# Activate virtualenv under the current interpreter's environment
inline_activate_virtual_environment()
@@ -2304,10 +2306,9 @@ def do_run(command, args, three=None, python=False, pypi_mirror=None):
# Only set PIPENV_ACTIVE after finishing reading virtualenv_location
# such as in inline_activate_virtual_environment
# otherwise its value will be changed
previous_pipenv_active_value = os.environ.get("PIPENV_ACTIVE")
os.environ["PIPENV_ACTIVE"] = vistir.misc.fs_str("1")
os.environ.pop("PIP_SHIMS_BASE_MODULE", None)
try:
script = project.build_script(command, args)
cmd_string = ' '.join([script.command] + script.args)
@@ -2315,10 +2316,22 @@ def do_run(command, args, three=None, python=False, pypi_mirror=None):
click.echo(crayons.normal("$ {0}".format(cmd_string)), err=True)
except ScriptEmptyError:
click.echo("Can't run script {0!r}-it's empty?", err=True)
run_args = [script]
run_kwargs = {}
if os.name == "nt":
do_run_nt(script)
run_fn = do_run_nt
else:
do_run_posix(script, command=command)
run_fn = do_run_posix
run_kwargs = {"command": command}
try:
run_fn(*run_args, **run_kwargs)
finally:
os.environ.pop("PIPENV_ACTIVE", None)
if previous_pipenv_active_value is not None:
os.environ["PIPENV_ACTIVE"] = previous_pipenv_active_value
if previous_pip_shims_module is not None:
os.environ["PIP_SHIMS_BASE_MODULE"] = previous_pip_shims_module
def do_check(
+17 -4
View File
@@ -280,10 +280,23 @@ def is_quiet(threshold=-1):
def is_in_virtualenv():
pipenv_active = os.environ.get("PIPENV_ACTIVE")
virtual_env = os.environ.get("VIRTUAL_ENV")
return (PIPENV_USE_SYSTEM or virtual_env) and not (
pipenv_active or PIPENV_IGNORE_VIRTUALENVS
"""
Check virtualenv membership dynamically
:return: True or false depending on whether we are in a regular virtualenv or not
:rtype: bool
"""
pipenv_active = os.environ.get("PIPENV_ACTIVE", False)
virtual_env = None
use_system = False
ignore_virtualenvs = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS", False))
if not pipenv_active and not ignore_virtualenvs:
virtual_env = os.environ.get("VIRTUAL_ENV")
use_system = bool(virtual_env)
return (use_system or virtual_env) and not (
pipenv_active or ignore_virtualenvs
)
+1 -1
View File
@@ -2,7 +2,7 @@
__author__ = "Daniel Greenfeld"
__email__ = "pydanny@gmail.com"
__version__ = "1.4.3"
__version__ = "1.5.1"
__license__ = "BSD"
from time import time
+2 -2
View File
@@ -1,3 +1,3 @@
from .core import where, old_where
from .core import where
__version__ = "2018.10.15"
__version__ = "2018.11.29"
+242
View File
@@ -4268,3 +4268,245 @@ rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
# Subject: CN=GTS Root R1 O=Google Trust Services LLC
# Label: "GTS Root R1"
# Serial: 146587175971765017618439757810265552097
# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85
# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8
# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
# Subject: CN=GTS Root R2 O=Google Trust Services LLC
# Label: "GTS Root R2"
# Serial: 146587176055767053814479386953112547951
# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b
# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d
# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg
GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu
XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd
re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu
PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1
mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K
8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj
x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR
nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0
kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok
twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp
8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT
z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA
pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb
pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB
R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R
RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk
0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC
5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF
izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn
yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
# Subject: CN=GTS Root R3 O=Google Trust Services LLC
# Label: "GTS Root R3"
# Serial: 146587176140553309517047991083707763997
# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25
# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5
# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5
-----BEGIN CERTIFICATE-----
MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A
DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk
fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA
njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
# Subject: CN=GTS Root R4 O=Google Trust Services LLC
# Label: "GTS Root R4"
# Serial: 146587176229350439916519468929765261721
# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26
# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb
# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd
-----BEGIN CERTIFICATE-----
MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l
xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0
CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx
sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==
-----END CERTIFICATE-----
# Issuer: CN=UCA Global G2 Root O=UniTrust
# Subject: CN=UCA Global G2 Root O=UniTrust
# Label: "UCA Global G2 Root"
# Serial: 124779693093741543919145257850076631279
# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8
# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a
# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH
bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x
CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds
b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr
b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9
kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm
VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R
VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc
C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj
tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY
D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv
j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl
NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6
iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP
O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV
ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj
L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl
1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU
b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV
PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj
y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb
EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg
DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI
+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy
YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX
UB+K+wb1whnw0A==
-----END CERTIFICATE-----
# Issuer: CN=UCA Extended Validation Root O=UniTrust
# Subject: CN=UCA Extended Validation Root O=UniTrust
# Label: "UCA Extended Validation Root"
# Serial: 106100277556486529736699587978573607008
# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2
# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a
# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF
eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx
MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV
BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog
D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS
sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop
O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk
sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi
c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj
VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz
KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/
TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G
sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs
1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD
fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN
l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ
VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5
c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp
4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s
t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj
2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO
vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C
xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx
cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM
fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax
-----END CERTIFICATE-----
# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
# Label: "Certigna Root CA"
# Serial: 269714418870597844693661054334862075617
# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77
# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43
# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD
VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX
BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO
ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M
CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu
I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm
TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh
C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf
ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz
IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT
Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k
JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5
hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB
GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov
L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo
dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr
aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq
hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L
6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG
HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6
0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB
lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi
o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1
gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v
faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63
Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----
-17
View File
@@ -8,14 +8,6 @@ certifi.py
This module returns the installation location of cacert.pem.
"""
import os
import warnings
class DeprecatedBundleWarning(DeprecationWarning):
"""
The weak security bundle is being deprecated. Please bother your service
provider to get them to stop using cross-signed roots.
"""
def where():
@@ -24,14 +16,5 @@ def where():
return os.path.join(f, 'cacert.pem')
def old_where():
warnings.warn(
"The weak security bundle has been removed. certifi.old_where() is now an alias "
"of certifi.where(). Please update your code to use certifi.where() instead. "
"certifi.old_where() will be removed in 2018.",
DeprecatedBundleWarning
)
return where()
if __name__ == '__main__':
print(where())
-1
View File
@@ -25,4 +25,3 @@ 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.
+1 -2
View File
@@ -3,5 +3,4 @@ from .initialise import init, deinit, reinit, colorama_text
from .ansi import Fore, Back, Style, Cursor
from .ansitowin32 import AnsiToWin32
__version__ = '0.3.9'
__version__ = '0.4.1'
+32 -11
View File
@@ -13,14 +13,6 @@ if windll is not None:
winterm = WinTerm()
def is_stream_closed(stream):
return not hasattr(stream, 'closed') or stream.closed
def is_a_tty(stream):
return hasattr(stream, 'isatty') and stream.isatty()
class StreamWrapper(object):
'''
Wraps a stream (such as stdout), acting as a transparent proxy for all
@@ -36,9 +28,38 @@ class StreamWrapper(object):
def __getattr__(self, name):
return getattr(self.__wrapped, name)
def __enter__(self, *args, **kwargs):
# special method lookup bypasses __getattr__/__getattribute__, see
# https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit
# thus, contextlib magic methods are not proxied via __getattr__
return self.__wrapped.__enter__(*args, **kwargs)
def __exit__(self, *args, **kwargs):
return self.__wrapped.__exit__(*args, **kwargs)
def write(self, text):
self.__convertor.write(text)
def isatty(self):
stream = self.__wrapped
if 'PYCHARM_HOSTED' in os.environ:
if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__):
return True
try:
stream_isatty = stream.isatty
except AttributeError:
return False
else:
return stream_isatty()
@property
def closed(self):
stream = self.__wrapped
try:
return stream.closed
except AttributeError:
return True
class AnsiToWin32(object):
'''
@@ -68,12 +89,12 @@ class AnsiToWin32(object):
# should we strip ANSI sequences from our output?
if strip is None:
strip = conversion_supported or (not is_stream_closed(wrapped) and not is_a_tty(wrapped))
strip = conversion_supported or (not self.stream.closed and not self.stream.isatty())
self.strip = strip
# should we should convert ANSI sequences into win32 calls?
if convert is None:
convert = conversion_supported and not is_stream_closed(wrapped) and is_a_tty(wrapped)
convert = conversion_supported and not self.stream.closed and self.stream.isatty()
self.convert = convert
# dict of ansi codes to win32 functions and parameters
@@ -149,7 +170,7 @@ class AnsiToWin32(object):
def reset_all(self):
if self.convert:
self.call_win32('m', (0,))
elif not self.strip and not is_stream_closed(self.wrapped):
elif not self.strip and not self.stream.closed:
self.wrapped.write(Style.RESET_ALL)
-2
View File
@@ -78,5 +78,3 @@ def wrap_stream(stream, convert, strip, autoreset, wrap):
if wrapper.should_wrap():
stream = wrapper.stream
return stream
+7 -11
View File
@@ -89,11 +89,6 @@ else:
]
_SetConsoleTitleW.restype = wintypes.BOOL
handles = {
STDOUT: _GetStdHandle(STDOUT),
STDERR: _GetStdHandle(STDERR),
}
def _winapi_test(handle):
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = _GetConsoleScreenBufferInfo(
@@ -101,17 +96,18 @@ else:
return bool(success)
def winapi_test():
return any(_winapi_test(h) for h in handles.values())
return any(_winapi_test(h) for h in
(_GetStdHandle(STDOUT), _GetStdHandle(STDERR)))
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = _GetConsoleScreenBufferInfo(
handle, byref(csbi))
return csbi
def SetConsoleTextAttribute(stream_id, attrs):
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
return _SetConsoleTextAttribute(handle, attrs)
def SetConsoleCursorPosition(stream_id, position, adjust=True):
@@ -129,11 +125,11 @@ else:
adjusted_position.Y += sr.Top
adjusted_position.X += sr.Left
# Resume normal processing
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
return _SetConsoleCursorPosition(handle, adjusted_position)
def FillConsoleOutputCharacter(stream_id, char, length, start):
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
char = c_char(char.encode())
length = wintypes.DWORD(length)
num_written = wintypes.DWORD(0)
@@ -144,7 +140,7 @@ else:
def FillConsoleOutputAttribute(stream_id, attr, length, start):
''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
attribute = wintypes.WORD(attr)
length = wintypes.DWORD(length)
num_written = wintypes.DWORD(0)
+9 -2
View File
@@ -44,6 +44,7 @@ class WinTerm(object):
def reset_all(self, on_stderr=None):
self.set_attrs(self._default)
self.set_console(attrs=self._default)
self._light = 0
def fore(self, fore=None, light=False, on_stderr=False):
if fore is None:
@@ -122,12 +123,15 @@ class WinTerm(object):
if mode == 0:
from_coord = csbi.dwCursorPosition
cells_to_erase = cells_in_screen - cells_before_cursor
if mode == 1:
elif mode == 1:
from_coord = win32.COORD(0, 0)
cells_to_erase = cells_before_cursor
elif mode == 2:
from_coord = win32.COORD(0, 0)
cells_to_erase = cells_in_screen
else:
# invalid mode
return
# fill the entire screen with blanks
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
# now set the buffer's attributes accordingly
@@ -147,12 +151,15 @@ class WinTerm(object):
if mode == 0:
from_coord = csbi.dwCursorPosition
cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
if mode == 1:
elif mode == 1:
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
cells_to_erase = csbi.dwCursorPosition.X
elif mode == 2:
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
cells_to_erase = csbi.dwSize.X
else:
# invalid mode
return
# fill the entire screen with blanks
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
# now set the buffer's attributes accordingly
+5
View File
@@ -1,4 +1,9 @@
import sys
try:
from StringIO import StringIO # noqa
except ImportError:
from io import StringIO # noqa
PY2 = sys.version_info[0] == 2
WIN = sys.platform.startswith('win')
text_type = unicode if PY2 else str # noqa
+54
View File
@@ -0,0 +1,54 @@
import os
class UndefinedValueError(Exception):
pass
class Undefined(object):
"""Class to represent undefined type. """
pass
# Reference instance to represent undefined values
undefined = Undefined()
def _cast_boolean(value):
"""
Helper to convert config values to boolean as ConfigParser do.
"""
_BOOLEANS = {'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False, '': False}
value = str(value)
if value.lower() not in _BOOLEANS:
raise ValueError('Not a boolean: %s' % value)
return _BOOLEANS[value.lower()]
def getenv(option, default=undefined, cast=undefined):
"""
Return the value for option or default if defined.
"""
# We can't avoid __contains__ because value may be empty.
if option in os.environ:
value = os.environ[option]
else:
if isinstance(default, Undefined):
raise UndefinedValueError('{} not found. Declare it as envvar or define a default value.'.format(option))
value = default
if isinstance(cast, Undefined):
return value
if cast is bool:
value = _cast_boolean(value)
elif cast is list:
value = [x for x in value.split(',') if x]
else:
value = cast(value)
return value
+126 -88
View File
@@ -2,47 +2,90 @@
from __future__ import absolute_import, print_function, unicode_literals
import codecs
import fileinput
import io
import os
import re
import shutil
import sys
from subprocess import Popen, PIPE, STDOUT
from subprocess import Popen
import tempfile
import warnings
from collections import OrderedDict
from collections import OrderedDict, namedtuple
from contextlib import contextmanager
from .compat import StringIO
from .compat import StringIO, PY2, WIN, text_type
__escape_decoder = codecs.getdecoder('unicode_escape')
__posix_variable = re.compile('\$\{[^\}]*\}')
__posix_variable = re.compile(r'\$\{[^\}]*\}')
_binding = re.compile(
r"""
(
\s* # leading whitespace
(?:export{0}+)? # export
( '[^']+' # single-quoted key
| [^=\#\s]+ # or unquoted key
)?
(?:
(?:{0}*={0}*) # equal sign
( '(?:\\'|[^'])*' # single-quoted value
| "(?:\\"|[^"])*" # or double-quoted value
| [^\#\r\n]* # or unquoted value
)
)?
\s* # trailing whitespace
(?:\#[^\r\n]*)? # comment
(?:\r|\n|\r\n)? # newline
)
""".format(r'[^\S\r\n]'),
re.MULTILINE | re.VERBOSE,
)
_escape_sequence = re.compile(r"\\[\\'\"abfnrtv]")
def decode_escaped(escaped):
return __escape_decoder(escaped)[0]
Binding = namedtuple('Binding', 'key value original')
def parse_line(line):
line = line.strip()
def decode_escapes(string):
def decode_match(match):
return codecs.decode(match.group(0), 'unicode-escape')
# Ignore lines with `#` or which doesn't have `=` in it.
if not line or line.startswith('#') or '=' not in line:
return None, None
return _escape_sequence.sub(decode_match, string)
k, v = line.split('=', 1)
if k.startswith('export '):
(_, _, k) = k.partition('export ')
def is_surrounded_by(string, char):
return (
len(string) > 1
and string[0] == string[-1] == char
)
# Remove any leading and trailing spaces in key, value
k, v = k.strip(), v.strip()
if v:
v = v.encode('unicode-escape').decode('ascii')
quoted = v[0] == v[-1] in ['"', "'"]
if quoted:
v = decode_escaped(v[1:-1])
def parse_binding(string, position):
match = _binding.match(string, position)
(matched, key, value) = match.groups()
if key is None or value is None:
key = None
value = None
else:
value_quoted = is_surrounded_by(value, "'") or is_surrounded_by(value, '"')
if value_quoted:
value = decode_escapes(value[1:-1])
else:
value = value.strip()
return (Binding(key=key, value=value, original=matched), match.end())
return k, v
def parse_stream(stream):
string = stream.read()
position = 0
length = len(string)
while position < length:
(binding, position) = parse_binding(string, position)
yield binding
class DotEnv():
@@ -52,19 +95,17 @@ class DotEnv():
self._dict = None
self.verbose = verbose
@contextmanager
def _get_stream(self):
self._is_file = False
if isinstance(self.dotenv_path, StringIO):
return self.dotenv_path
if os.path.exists(self.dotenv_path):
self._is_file = True
return io.open(self.dotenv_path)
if self.verbose:
warnings.warn("File doesn't exist {}".format(self.dotenv_path))
return StringIO('')
yield self.dotenv_path
elif os.path.isfile(self.dotenv_path):
with io.open(self.dotenv_path) as stream:
yield stream
else:
if self.verbose:
warnings.warn("File doesn't exist {}".format(self.dotenv_path))
yield StringIO('')
def dict(self):
"""Return dotenv as dict"""
@@ -76,17 +117,10 @@ class DotEnv():
return self._dict
def parse(self):
f = self._get_stream()
for line in f:
key, value = parse_line(line)
if not key:
continue
yield key, value
if self._is_file:
f.close()
with self._get_stream() as stream:
for mapping in parse_stream(stream):
if mapping.key is not None and mapping.value is not None:
yield mapping.key, mapping.value
def set_as_environment_variables(self, override=False):
"""
@@ -95,13 +129,12 @@ class DotEnv():
for k, v in self.dict().items():
if k in os.environ and not override:
continue
# With Python 2 on Windows, ensuree environment variables are
# system strings to avoid "TypeError: environment can only contain
# strings" in Python's subprocess module.
if sys.version_info.major < 3 and sys.platform == 'win32':
from pipenv.utils import fs_str
k = fs_str(k)
v = fs_str(v)
# With Python2 on Windows, force environment variables to str to avoid
# "TypeError: environment can only contain strings" in Python's subprocess.py.
if PY2 and WIN:
if isinstance(k, text_type) or isinstance(v, text_type):
k = k.encode('ascii')
v = v.encode('ascii')
os.environ[k] = v
return True
@@ -127,6 +160,20 @@ def get_key(dotenv_path, key_to_get):
return DotEnv(dotenv_path, verbose=True).get(key_to_get)
@contextmanager
def rewrite(path):
try:
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as dest:
with io.open(path) as source:
yield (source, dest)
except BaseException:
if os.path.isfile(dest.name):
os.unlink(dest.name)
raise
else:
shutil.move(dest.name, path)
def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
"""
Adds or Updates a key/value to the given .env
@@ -142,20 +189,19 @@ def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
if " " in value_to_set:
quote_mode = "always"
line_template = '{}="{}"' if quote_mode == "always" else '{}={}'
line_template = '{}="{}"\n' if quote_mode == "always" else '{}={}\n'
line_out = line_template.format(key_to_set, value_to_set)
replaced = False
for line in fileinput.input(dotenv_path, inplace=True):
k, v = parse_line(line)
if k == key_to_set:
replaced = True
line = line_out
print(line, end='')
if not replaced:
with io.open(dotenv_path, "a") as f:
f.write("{}\n".format(line_out))
with rewrite(dotenv_path) as (source, dest):
replaced = False
for mapping in parse_stream(source):
if mapping.key == key_to_set:
dest.write(line_out)
replaced = True
else:
dest.write(mapping.original)
if not replaced:
dest.write(line_out)
return True, key_to_set, value_to_set
@@ -167,18 +213,17 @@ def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
If the .env path given doesn't exist, fails
If the given key doesn't exist in the .env, fails
"""
removed = False
if not os.path.exists(dotenv_path):
warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path)
return None, key_to_unset
for line in fileinput.input(dotenv_path, inplace=True):
k, v = parse_line(line)
if k == key_to_unset:
removed = True
line = ''
print(line, end='')
removed = False
with rewrite(dotenv_path) as (source, dest):
for mapping in parse_stream(source):
if mapping.key == key_to_unset:
removed = True
else:
dest.write(mapping.original)
if not removed:
warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path))
@@ -194,7 +239,7 @@ def resolve_nested_variables(values):
first search in environ, if not found,
then look into the dotenv variables
"""
ret = os.getenv(name, values.get(name, ""))
ret = os.getenv(name, new_values.get(name, ""))
return ret
def _re_sub_callback(match_object):
@@ -204,10 +249,12 @@ def resolve_nested_variables(values):
"""
return _replacement(match_object.group()[2:-1])
for k, v in values.items():
values[k] = __posix_variable.sub(_re_sub_callback, v)
new_values = {}
return values
for k, v in values.items():
new_values[k] = __posix_variable.sub(_re_sub_callback, v)
return new_values
def _walk_to_root(path):
@@ -248,7 +295,7 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
for dirname in _walk_to_root(path):
check_path = os.path.join(dirname, filename)
if os.path.exists(check_path):
if os.path.isfile(check_path):
return check_path
if raise_error_if_not_found:
@@ -292,19 +339,10 @@ def run_command(command, env):
cmd_env.update(env)
p = Popen(command,
stdin=PIPE,
stdout=PIPE,
stderr=STDOUT,
universal_newlines=True,
bufsize=0,
shell=False,
env=cmd_env)
try:
out, _ = p.communicate()
print(out)
except Exception:
warnings.warn('An error occured, running the command:')
out, _ = p.communicate()
warnings.warn(out)
_, _ = p.communicate()
return p.returncode
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.9.1"
__version__ = "0.10.1"
Vendored Executable → Regular
View File
Vendored Executable → Regular
View File
Vendored Executable → Regular
View File
Vendored Executable → Regular
+1 -4
View File
@@ -267,10 +267,7 @@ def alabel(label):
try:
label = label.encode('ascii')
try:
ulabel(label)
except IDNAError:
raise IDNAError('The label {0} is not a valid A-label'.format(label))
ulabel(label)
if not valid_label_length(label):
raise IDNAError('Label too long')
return label
Vendored Executable → Regular
+104 -18
View File
@@ -1,6 +1,6 @@
# This file is automatically generated by tools/idna-data
__version__ = "10.0.0"
__version__ = "11.0.0"
scripts = {
'Greek': (
0x37000000374,
@@ -49,7 +49,7 @@ scripts = {
0x30210000302a,
0x30380000303c,
0x340000004db6,
0x4e0000009feb,
0x4e0000009ff0,
0xf9000000fa6e,
0xfa700000fada,
0x200000002a6d7,
@@ -62,7 +62,7 @@ scripts = {
'Hebrew': (
0x591000005c8,
0x5d0000005eb,
0x5f0000005f5,
0x5ef000005f5,
0xfb1d0000fb37,
0xfb380000fb3d,
0xfb3e0000fb3f,
@@ -248,6 +248,7 @@ joining_types = {
0x6fb: 68,
0x6fc: 68,
0x6ff: 68,
0x70f: 84,
0x710: 82,
0x712: 68,
0x713: 68,
@@ -522,6 +523,7 @@ joining_types = {
0x1875: 68,
0x1876: 68,
0x1877: 68,
0x1878: 68,
0x1880: 85,
0x1881: 85,
0x1882: 85,
@@ -690,6 +692,70 @@ joining_types = {
0x10bad: 68,
0x10bae: 68,
0x10baf: 85,
0x10d00: 76,
0x10d01: 68,
0x10d02: 68,
0x10d03: 68,
0x10d04: 68,
0x10d05: 68,
0x10d06: 68,
0x10d07: 68,
0x10d08: 68,
0x10d09: 68,
0x10d0a: 68,
0x10d0b: 68,
0x10d0c: 68,
0x10d0d: 68,
0x10d0e: 68,
0x10d0f: 68,
0x10d10: 68,
0x10d11: 68,
0x10d12: 68,
0x10d13: 68,
0x10d14: 68,
0x10d15: 68,
0x10d16: 68,
0x10d17: 68,
0x10d18: 68,
0x10d19: 68,
0x10d1a: 68,
0x10d1b: 68,
0x10d1c: 68,
0x10d1d: 68,
0x10d1e: 68,
0x10d1f: 68,
0x10d20: 68,
0x10d21: 68,
0x10d22: 82,
0x10d23: 68,
0x10f30: 68,
0x10f31: 68,
0x10f32: 68,
0x10f33: 82,
0x10f34: 68,
0x10f35: 68,
0x10f36: 68,
0x10f37: 68,
0x10f38: 68,
0x10f39: 68,
0x10f3a: 68,
0x10f3b: 68,
0x10f3c: 68,
0x10f3d: 68,
0x10f3e: 68,
0x10f3f: 68,
0x10f40: 68,
0x10f41: 68,
0x10f42: 68,
0x10f43: 68,
0x10f44: 68,
0x10f45: 85,
0x10f51: 68,
0x10f52: 68,
0x10f53: 68,
0x10f54: 82,
0x110bd: 85,
0x110cd: 85,
0x1e900: 68,
0x1e901: 68,
0x1e902: 68,
@@ -1034,14 +1100,15 @@ codepoint_classes = {
0x52d0000052e,
0x52f00000530,
0x5590000055a,
0x56100000587,
0x56000000587,
0x58800000589,
0x591000005be,
0x5bf000005c0,
0x5c1000005c3,
0x5c4000005c6,
0x5c7000005c8,
0x5d0000005eb,
0x5f0000005f3,
0x5ef000005f3,
0x6100000061b,
0x62000000640,
0x64100000660,
@@ -1054,12 +1121,13 @@ codepoint_classes = {
0x7100000074b,
0x74d000007b2,
0x7c0000007f6,
0x7fd000007fe,
0x8000000082e,
0x8400000085c,
0x8600000086b,
0x8a0000008b5,
0x8b6000008be,
0x8d4000008e2,
0x8d3000008e2,
0x8e300000958,
0x96000000964,
0x96600000970,
@@ -1077,6 +1145,7 @@ codepoint_classes = {
0x9e0000009e4,
0x9e6000009f2,
0x9fc000009fd,
0x9fe000009ff,
0xa0100000a04,
0xa0500000a0b,
0xa0f00000a11,
@@ -1136,8 +1205,7 @@ codepoint_classes = {
0xbd000000bd1,
0xbd700000bd8,
0xbe600000bf0,
0xc0000000c04,
0xc0500000c0d,
0xc0000000c0d,
0xc0e00000c11,
0xc1200000c29,
0xc2a00000c3a,
@@ -1276,7 +1344,7 @@ codepoint_classes = {
0x17dc000017de,
0x17e0000017ea,
0x18100000181a,
0x182000001878,
0x182000001879,
0x1880000018ab,
0x18b0000018f6,
0x19000000191f,
@@ -1544,11 +1612,11 @@ codepoint_classes = {
0x309d0000309f,
0x30a1000030fb,
0x30fc000030ff,
0x31050000312f,
0x310500003130,
0x31a0000031bb,
0x31f000003200,
0x340000004db6,
0x4e0000009feb,
0x4e0000009ff0,
0xa0000000a48d,
0xa4d00000a4fe,
0xa5000000a60d,
@@ -1655,8 +1723,10 @@ codepoint_classes = {
0xa7a50000a7a6,
0xa7a70000a7a8,
0xa7a90000a7aa,
0xa7af0000a7b0,
0xa7b50000a7b6,
0xa7b70000a7b8,
0xa7b90000a7ba,
0xa7f70000a7f8,
0xa7fa0000a828,
0xa8400000a874,
@@ -1664,8 +1734,7 @@ codepoint_classes = {
0xa8d00000a8da,
0xa8e00000a8f8,
0xa8fb0000a8fc,
0xa8fd0000a8fe,
0xa9000000a92e,
0xa8fd0000a92e,
0xa9300000a954,
0xa9800000a9c1,
0xa9cf0000a9da,
@@ -1743,7 +1812,7 @@ codepoint_classes = {
0x10a0500010a07,
0x10a0c00010a14,
0x10a1500010a18,
0x10a1900010a34,
0x10a1900010a36,
0x10a3800010a3b,
0x10a3f00010a40,
0x10a6000010a7d,
@@ -1756,6 +1825,11 @@ codepoint_classes = {
0x10b8000010b92,
0x10c0000010c49,
0x10cc000010cf3,
0x10d0000010d28,
0x10d3000010d3a,
0x10f0000010f1d,
0x10f2700010f28,
0x10f3000010f51,
0x1100000011047,
0x1106600011070,
0x1107f000110bb,
@@ -1763,10 +1837,11 @@ codepoint_classes = {
0x110f0000110fa,
0x1110000011135,
0x1113600011140,
0x1114400011147,
0x1115000011174,
0x1117600011177,
0x11180000111c5,
0x111ca000111cd,
0x111c9000111cd,
0x111d0000111db,
0x111dc000111dd,
0x1120000011212,
@@ -1786,7 +1861,7 @@ codepoint_classes = {
0x1132a00011331,
0x1133200011334,
0x113350001133a,
0x1133c00011345,
0x1133b00011345,
0x1134700011349,
0x1134b0001134e,
0x1135000011351,
@@ -1796,6 +1871,7 @@ codepoint_classes = {
0x1137000011375,
0x114000001144b,
0x114500001145a,
0x1145e0001145f,
0x11480000114c6,
0x114c7000114c8,
0x114d0000114da,
@@ -1807,15 +1883,17 @@ codepoint_classes = {
0x116500001165a,
0x11680000116b8,
0x116c0000116ca,
0x117000001171a,
0x117000001171b,
0x1171d0001172c,
0x117300001173a,
0x118000001183b,
0x118c0000118ea,
0x118ff00011900,
0x11a0000011a3f,
0x11a4700011a48,
0x11a5000011a84,
0x11a8600011a9a,
0x11a9d00011a9e,
0x11ac000011af9,
0x11c0000011c09,
0x11c0a00011c37,
@@ -1831,6 +1909,13 @@ codepoint_classes = {
0x11d3c00011d3e,
0x11d3f00011d48,
0x11d5000011d5a,
0x11d6000011d66,
0x11d6700011d69,
0x11d6a00011d8f,
0x11d9000011d92,
0x11d9300011d99,
0x11da000011daa,
0x11ee000011ef7,
0x120000001239a,
0x1248000012544,
0x130000001342f,
@@ -1845,11 +1930,12 @@ codepoint_classes = {
0x16b5000016b5a,
0x16b6300016b78,
0x16b7d00016b90,
0x16e6000016e80,
0x16f0000016f45,
0x16f5000016f7f,
0x16f8f00016fa0,
0x16fe000016fe2,
0x17000000187ed,
0x17000000187f2,
0x1880000018af3,
0x1b0000001b11f,
0x1b1700001b2fc,
Vendored Executable → Regular
View File
Vendored Executable → Regular
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = '2.7'
__version__ = '2.8'
Vendored Executable → Regular
+343 -317
View File
File diff suppressed because it is too large Load Diff
+10 -4
View File
@@ -4,18 +4,24 @@
from __future__ import absolute_import, division, print_function
__all__ = [
"__title__", "__summary__", "__uri__", "__version__", "__author__",
"__email__", "__license__", "__copyright__",
"__title__",
"__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
]
__title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
__version__ = "18.0"
__version__ = "19.0"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "BSD or Apache License, Version 2.0"
__copyright__ = "Copyright 2014-2018 %s" % __author__
__copyright__ = "Copyright 2014-2019 %s" % __author__
+16 -4
View File
@@ -4,11 +4,23 @@
from __future__ import absolute_import, division, print_function
from .__about__ import (
__author__, __copyright__, __email__, __license__, __summary__, __title__,
__uri__, __version__
__author__,
__copyright__,
__email__,
__license__,
__summary__,
__title__,
__uri__,
__version__,
)
__all__ = [
"__title__", "__summary__", "__uri__", "__version__", "__author__",
"__email__", "__license__", "__copyright__",
"__title__",
"__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
]
+4 -3
View File
@@ -12,9 +12,9 @@ PY3 = sys.version_info[0] == 3
# flake8: noqa
if PY3:
string_types = str,
string_types = (str,)
else:
string_types = basestring,
string_types = (basestring,)
def with_metaclass(meta, *bases):
@@ -27,4 +27,5 @@ def with_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
return type.__new__(metaclass, "temporary_class", (), {})
-2
View File
@@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function
class Infinity(object):
def __repr__(self):
return "Infinity"
@@ -38,7 +37,6 @@ Infinity = Infinity()
class NegativeInfinity(object):
def __repr__(self):
return "-Infinity"
+42 -47
View File
@@ -17,8 +17,11 @@ from .specifiers import Specifier, InvalidSpecifier
__all__ = [
"InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName",
"Marker", "default_environment",
"InvalidMarker",
"UndefinedComparison",
"UndefinedEnvironmentName",
"Marker",
"default_environment",
]
@@ -42,7 +45,6 @@ class UndefinedEnvironmentName(ValueError):
class Node(object):
def __init__(self, value):
self.value = value
@@ -57,62 +59,52 @@ class Node(object):
class Variable(Node):
def serialize(self):
return str(self)
class Value(Node):
def serialize(self):
return '"{0}"'.format(self)
class Op(Node):
def serialize(self):
return str(self)
VARIABLE = (
L("implementation_version") |
L("platform_python_implementation") |
L("implementation_name") |
L("python_full_version") |
L("platform_release") |
L("platform_version") |
L("platform_machine") |
L("platform_system") |
L("python_version") |
L("sys_platform") |
L("os_name") |
L("os.name") | # PEP-345
L("sys.platform") | # PEP-345
L("platform.version") | # PEP-345
L("platform.machine") | # PEP-345
L("platform.python_implementation") | # PEP-345
L("python_implementation") | # undocumented setuptools legacy
L("extra")
L("implementation_version")
| L("platform_python_implementation")
| L("implementation_name")
| L("python_full_version")
| L("platform_release")
| L("platform_version")
| L("platform_machine")
| L("platform_system")
| L("python_version")
| L("sys_platform")
| L("os_name")
| L("os.name")
| L("sys.platform") # PEP-345
| L("platform.version") # PEP-345
| L("platform.machine") # PEP-345
| L("platform.python_implementation") # PEP-345
| L("python_implementation") # PEP-345
| L("extra") # undocumented setuptools legacy
)
ALIASES = {
'os.name': 'os_name',
'sys.platform': 'sys_platform',
'platform.version': 'platform_version',
'platform.machine': 'platform_machine',
'platform.python_implementation': 'platform_python_implementation',
'python_implementation': 'platform_python_implementation'
"os.name": "os_name",
"sys.platform": "sys_platform",
"platform.version": "platform_version",
"platform.machine": "platform_machine",
"platform.python_implementation": "platform_python_implementation",
"python_implementation": "platform_python_implementation",
}
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
VERSION_CMP = (
L("===") |
L("==") |
L(">=") |
L("<=") |
L("!=") |
L("~=") |
L(">") |
L("<")
L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<")
)
MARKER_OP = VERSION_CMP | L("not in") | L("in")
@@ -152,8 +144,11 @@ def _format_marker(marker, first=True):
# where the single item is itself it's own list. In that case we want skip
# the rest of this function so that we don't get extraneous () on the
# outside.
if (isinstance(marker, list) and len(marker) == 1 and
isinstance(marker[0], (list, tuple))):
if (
isinstance(marker, list)
and len(marker) == 1
and isinstance(marker[0], (list, tuple))
):
return _format_marker(marker[0])
if isinstance(marker, list):
@@ -239,20 +234,20 @@ def _evaluate_markers(markers, environment):
def format_full_version(info):
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
version = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
if kind != 'final':
if kind != "final":
version += kind[0] + str(info.serial)
return version
def default_environment():
if hasattr(sys, 'implementation'):
if hasattr(sys, "implementation"):
iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name
else:
iver = '0'
implementation_name = ''
iver = "0"
implementation_name = ""
return {
"implementation_name": implementation_name,
@@ -270,13 +265,13 @@ def default_environment():
class Marker(object):
def __init__(self, marker):
try:
self._markers = _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e:
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
marker, marker[e.loc:e.loc + 8])
marker, marker[e.loc : e.loc + 8]
)
raise InvalidMarker(err_str)
def __str__(self):
+21 -13
View File
@@ -38,8 +38,8 @@ IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
NAME = IDENTIFIER("name")
EXTRA = IDENTIFIER
URI = Regex(r'[^ ]+')("url")
URL = (AT + URI)
URI = Regex(r"[^ ]+")("url")
URL = AT + URI
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
@@ -48,17 +48,18 @@ VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE),
joinString=",", adjacent=False)("_raw_spec")
VERSION_MANY = Combine(
VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
)("_raw_spec")
_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '')
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR.setParseAction(
lambda s, l, t: Marker(s[t._original_start:t._original_end])
lambda s, l, t: Marker(s[t._original_start : t._original_end])
)
MARKER_SEPARATOR = SEMICOLON
MARKER = MARKER_SEPARATOR + MARKER_EXPR
@@ -66,8 +67,7 @@ MARKER = MARKER_SEPARATOR + MARKER_EXPR
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
URL_AND_MARKER = URL + Optional(MARKER)
NAMED_REQUIREMENT = \
NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
# pyparsing isn't thread safe during initialization, so we do it eagerly, see
@@ -92,15 +92,21 @@ class Requirement(object):
try:
req = REQUIREMENT.parseString(requirement_string)
except ParseException as e:
raise InvalidRequirement("Parse error at \"{0!r}\": {1}".format(
requirement_string[e.loc:e.loc + 8], e.msg
))
raise InvalidRequirement(
'Parse error at "{0!r}": {1}'.format(
requirement_string[e.loc : e.loc + 8], e.msg
)
)
self.name = req.name
if req.url:
parsed_url = urlparse.urlparse(req.url)
if not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc):
if parsed_url.scheme == "file":
if urlparse.urlunparse(parsed_url) != req.url:
raise InvalidRequirement("Invalid URL given")
elif not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc
):
raise InvalidRequirement("Invalid URL: {0}".format(req.url))
self.url = req.url
else:
@@ -120,6 +126,8 @@ class Requirement(object):
if self.url:
parts.append("@ {0}".format(self.url))
if self.marker:
parts.append(" ")
if self.marker:
parts.append("; {0}".format(self.marker))
+21 -46
View File
@@ -19,7 +19,6 @@ class InvalidSpecifier(ValueError):
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)):
@abc.abstractmethod
def __str__(self):
"""
@@ -84,10 +83,7 @@ class _IndividualSpecifier(BaseSpecifier):
if not match:
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
self._spec = (
match.group("operator").strip(),
match.group("version").strip(),
)
self._spec = (match.group("operator").strip(), match.group("version").strip())
# Store whether or not this Specifier should accept prereleases
self._prereleases = prereleases
@@ -99,11 +95,7 @@ class _IndividualSpecifier(BaseSpecifier):
else ""
)
return "<{0}({1!r}{2})>".format(
self.__class__.__name__,
str(self),
pre,
)
return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
def __str__(self):
return "{0}{1}".format(*self._spec)
@@ -194,8 +186,9 @@ class _IndividualSpecifier(BaseSpecifier):
# If our version is a prerelease, and we were not set to allow
# prereleases, then we'll store it for later incase nothing
# else matches this specifier.
if (parsed_version.is_prerelease and not
(prereleases or self.prereleases)):
if parsed_version.is_prerelease and not (
prereleases or self.prereleases
):
found_prereleases.append(version)
# Either this is not a prerelease, or we should have been
# accepting prereleases from the beginning.
@@ -213,8 +206,7 @@ class _IndividualSpecifier(BaseSpecifier):
class LegacySpecifier(_IndividualSpecifier):
_regex_str = (
r"""
_regex_str = r"""
(?P<operator>(==|!=|<=|>=|<|>))
\s*
(?P<version>
@@ -225,10 +217,8 @@ class LegacySpecifier(_IndividualSpecifier):
# them, and a comma since it's a version separator.
)
"""
)
_regex = re.compile(
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"==": "equal",
@@ -269,13 +259,13 @@ def _require_version_compare(fn):
if not isinstance(prospective, Version):
return False
return fn(self, prospective, spec)
return wrapped
class Specifier(_IndividualSpecifier):
_regex_str = (
r"""
_regex_str = r"""
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
(?P<version>
(?:
@@ -367,10 +357,8 @@ class Specifier(_IndividualSpecifier):
)
)
"""
)
_regex = re.compile(
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"~=": "compatible",
@@ -397,8 +385,7 @@ class Specifier(_IndividualSpecifier):
prefix = ".".join(
list(
itertools.takewhile(
lambda x: (not x.startswith("post") and not
x.startswith("dev")),
lambda x: (not x.startswith("post") and not x.startswith("dev")),
_version_split(spec),
)
)[:-1]
@@ -407,8 +394,9 @@ class Specifier(_IndividualSpecifier):
# Add the prefix notation to the end of our string
prefix += ".*"
return (self._get_operator(">=")(prospective, spec) and
self._get_operator("==")(prospective, prefix))
return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
prospective, prefix
)
@_require_version_compare
def _compare_equal(self, prospective, spec):
@@ -428,7 +416,7 @@ class Specifier(_IndividualSpecifier):
# Shorten the prospective version to be the same length as the spec
# so that we can determine if the specifier is a prefix of the
# prospective version or not.
prospective = prospective[:len(spec)]
prospective = prospective[: len(spec)]
# Pad out our two sides with zeros so that they both equal the same
# length.
@@ -567,27 +555,17 @@ def _pad_version(left, right):
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
# Get the rest of our versions
left_split.append(left[len(left_split[0]):])
right_split.append(right[len(right_split[0]):])
left_split.append(left[len(left_split[0]) :])
right_split.append(right[len(right_split[0]) :])
# Insert our padding
left_split.insert(
1,
["0"] * max(0, len(right_split[0]) - len(left_split[0])),
)
right_split.insert(
1,
["0"] * max(0, len(left_split[0]) - len(right_split[0])),
)
left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
return (
list(itertools.chain(*left_split)),
list(itertools.chain(*right_split)),
)
return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
class SpecifierSet(BaseSpecifier):
def __init__(self, specifiers="", prereleases=None):
# Split on , to break each indidivual specifier into it's own item, and
# strip each item to remove leading/trailing whitespace.
@@ -721,10 +699,7 @@ class SpecifierSet(BaseSpecifier):
# given version is contained within all of them.
# Note: This use of all() here means that an empty set of specifiers
# will always return True, this is an explicit design decision.
return all(
s.contains(item, prereleases=prereleases)
for s in self._specs
)
return all(s.contains(item, prereleases=prereleases) for s in self._specs)
def filter(self, iterable, prereleases=None):
# Determine if we're forcing a prerelease or not, if we're not forcing
+1 -7
View File
@@ -36,13 +36,7 @@ def canonicalize_version(version):
# Release segment
# NB: This strips trailing '.0's to normalize
parts.append(
re.sub(
r'(\.0)+$',
'',
".".join(str(x) for x in version.release)
)
)
parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release)))
# Pre-release
if version.pre is not None:
+14 -35
View File
@@ -10,14 +10,11 @@ import re
from ._structures import Infinity
__all__ = [
"parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
]
__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
_Version = collections.namedtuple(
"_Version",
["epoch", "release", "dev", "pre", "post", "local"],
"_Version", ["epoch", "release", "dev", "pre", "post", "local"]
)
@@ -40,7 +37,6 @@ class InvalidVersion(ValueError):
class _BaseVersion(object):
def __hash__(self):
return hash(self._key)
@@ -70,7 +66,6 @@ class _BaseVersion(object):
class LegacyVersion(_BaseVersion):
def __init__(self, version):
self._version = str(version)
self._key = _legacy_cmpkey(self._version)
@@ -126,12 +121,14 @@ class LegacyVersion(_BaseVersion):
return False
_legacy_version_component_re = re.compile(
r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
)
_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
_legacy_version_replacement_map = {
"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
"pre": "c",
"preview": "c",
"-": "final-",
"rc": "c",
"dev": "@",
}
@@ -215,10 +212,7 @@ VERSION_PATTERN = r"""
class Version(_BaseVersion):
_regex = re.compile(
r"^\s*" + VERSION_PATTERN + r"\s*$",
re.VERBOSE | re.IGNORECASE,
)
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
def __init__(self, version):
# Validate the version and parse it into pieces
@@ -230,18 +224,11 @@ class Version(_BaseVersion):
self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")),
pre=_parse_letter_version(
match.group("pre_l"),
match.group("pre_n"),
),
pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
post=_parse_letter_version(
match.group("post_l"),
match.group("post_n1") or match.group("post_n2"),
),
dev=_parse_letter_version(
match.group("dev_l"),
match.group("dev_n"),
match.group("post_l"), match.group("post_n1") or match.group("post_n2")
),
dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
local=_parse_local_version(match.group("local")),
)
@@ -395,12 +382,7 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key.
release = tuple(
reversed(list(
itertools.dropwhile(
lambda x: x == 0,
reversed(release),
)
))
reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
)
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
@@ -433,9 +415,6 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# - Numeric segments sort numerically
# - Shorter versions sort before longer versions when the prefixes
# match exactly
local = tuple(
(i, "") if isinstance(i, int) else (-Infinity, i)
for i in local
)
local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local)
return epoch, release, pre, post, dev, local
+2 -2
View File
@@ -20,13 +20,13 @@ class Project(passa.models.projects.Project):
pipfile = root.joinpath("Pipfile")
if not pipfile.is_file():
raise argparse.ArgumentError(
"{0!r} is not a Pipfile project".format(root),
"project", "{0!r} is not a Pipfile project".format(root),
)
try:
super(Project, self).__init__(root.as_posix(), *args, **kwargs)
except tomlkit.exceptions.ParseError as e:
raise argparse.ArgumentError(
"failed to parse Pipfile: {0!r}".format(str(e)),
"project", "failed to parse Pipfile: {0!r}".format(str(e)),
)
def __name__(self):
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Thomas Kluyver
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, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
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.
+4
View File
@@ -0,0 +1,4 @@
"""Wrappers to build Python packages using PEP 517 hooks
"""
__version__ = '0.5.0'
+207
View File
@@ -0,0 +1,207 @@
"""This is invoked in a subprocess to call the build backend hooks.
It expects:
- Command line args: hook_name, control_dir
- Environment variable: PEP517_BUILD_BACKEND=entry.point:spec
- control_dir/input.json:
- {"kwargs": {...}}
Results:
- control_dir/output.json
- {"return_val": ...}
"""
from glob import glob
from importlib import import_module
import os
from os.path import join as pjoin
import re
import shutil
import sys
# This is run as a script, not a module, so it can't do a relative import
import compat
class BackendUnavailable(Exception):
"""Raised if we cannot import the backend"""
def _build_backend():
"""Find and load the build backend"""
ep = os.environ['PEP517_BUILD_BACKEND']
mod_path, _, obj_path = ep.partition(':')
try:
obj = import_module(mod_path)
except ImportError:
raise BackendUnavailable
if obj_path:
for path_part in obj_path.split('.'):
obj = getattr(obj, path_part)
return obj
def get_requires_for_build_wheel(config_settings):
"""Invoke the optional get_requires_for_build_wheel hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_wheel
except AttributeError:
return []
else:
return hook(config_settings)
def prepare_metadata_for_build_wheel(metadata_directory, config_settings):
"""Invoke optional prepare_metadata_for_build_wheel
Implements a fallback by building a wheel if the hook isn't defined.
"""
backend = _build_backend()
try:
hook = backend.prepare_metadata_for_build_wheel
except AttributeError:
return _get_wheel_metadata_from_wheel(backend, metadata_directory,
config_settings)
else:
return hook(metadata_directory, config_settings)
WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL'
def _dist_info_files(whl_zip):
"""Identify the .dist-info folder inside a wheel ZipFile."""
res = []
for path in whl_zip.namelist():
m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path)
if m:
res.append(path)
if res:
return res
raise Exception("No .dist-info folder found in wheel")
def _get_wheel_metadata_from_wheel(
backend, metadata_directory, config_settings):
"""Build a wheel and extract the metadata from it.
Fallback for when the build backend does not
define the 'get_wheel_metadata' hook.
"""
from zipfile import ZipFile
whl_basename = backend.build_wheel(metadata_directory, config_settings)
with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'):
pass # Touch marker file
whl_file = os.path.join(metadata_directory, whl_basename)
with ZipFile(whl_file) as zipf:
dist_info = _dist_info_files(zipf)
zipf.extractall(path=metadata_directory, members=dist_info)
return dist_info[0].split('/')[0]
def _find_already_built_wheel(metadata_directory):
"""Check for a wheel already built during the get_wheel_metadata hook.
"""
if not metadata_directory:
return None
metadata_parent = os.path.dirname(metadata_directory)
if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)):
return None
whl_files = glob(os.path.join(metadata_parent, '*.whl'))
if not whl_files:
print('Found wheel built marker, but no .whl files')
return None
if len(whl_files) > 1:
print('Found multiple .whl files; unspecified behaviour. '
'Will call build_wheel.')
return None
# Exactly one .whl file
return whl_files[0]
def build_wheel(wheel_directory, config_settings, metadata_directory=None):
"""Invoke the mandatory build_wheel hook.
If a wheel was already built in the
prepare_metadata_for_build_wheel fallback, this
will copy it rather than rebuilding the wheel.
"""
prebuilt_whl = _find_already_built_wheel(metadata_directory)
if prebuilt_whl:
shutil.copy2(prebuilt_whl, wheel_directory)
return os.path.basename(prebuilt_whl)
return _build_backend().build_wheel(wheel_directory, config_settings,
metadata_directory)
def get_requires_for_build_sdist(config_settings):
"""Invoke the optional get_requires_for_build_wheel hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_sdist
except AttributeError:
return []
else:
return hook(config_settings)
class _DummyException(Exception):
"""Nothing should ever raise this exception"""
class GotUnsupportedOperation(Exception):
"""For internal use when backend raises UnsupportedOperation"""
def build_sdist(sdist_directory, config_settings):
"""Invoke the mandatory build_sdist hook."""
backend = _build_backend()
try:
return backend.build_sdist(sdist_directory, config_settings)
except getattr(backend, 'UnsupportedOperation', _DummyException):
raise GotUnsupportedOperation
HOOK_NAMES = {
'get_requires_for_build_wheel',
'prepare_metadata_for_build_wheel',
'build_wheel',
'get_requires_for_build_sdist',
'build_sdist',
}
def main():
if len(sys.argv) < 3:
sys.exit("Needs args: hook_name, control_dir")
hook_name = sys.argv[1]
control_dir = sys.argv[2]
if hook_name not in HOOK_NAMES:
sys.exit("Unknown hook: %s" % hook_name)
hook = globals()[hook_name]
hook_input = compat.read_json(pjoin(control_dir, 'input.json'))
json_out = {'unsupported': False, 'return_val': None}
try:
json_out['return_val'] = hook(**hook_input['kwargs'])
except BackendUnavailable:
json_out['no_backend'] = True
except GotUnsupportedOperation:
json_out['unsupported'] = True
compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2)
if __name__ == '__main__':
main()
+108
View File
@@ -0,0 +1,108 @@
"""Build a project using PEP 517 hooks.
"""
import argparse
import logging
import os
import contextlib
import pytoml
import shutil
import errno
import tempfile
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
log = logging.getLogger(__name__)
@contextlib.contextmanager
def tempdir():
td = tempfile.mkdtemp()
try:
yield td
finally:
shutil.rmtree(td)
def _do_build(hooks, env, dist, dest):
get_requires_name = 'get_requires_for_build_{dist}'.format(**locals())
get_requires = getattr(hooks, get_requires_name)
reqs = get_requires({})
log.info('Got build requires: %s', reqs)
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
with tempdir() as td:
log.info('Trying to build %s in %s', dist, td)
build_name = 'build_{dist}'.format(**locals())
build = getattr(hooks, build_name)
filename = build(td, {})
source = os.path.join(td, filename)
shutil.move(source, os.path.join(dest, os.path.basename(filename)))
def mkdir_p(*args, **kwargs):
"""Like `mkdir`, but does not raise an exception if the
directory already exists.
"""
try:
return os.mkdir(*args, **kwargs)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
def build(source_dir, dist, dest=None):
pyproject = os.path.join(source_dir, 'pyproject.toml')
dest = os.path.join(source_dir, dest or 'dist')
mkdir_p(dest)
with open(pyproject) as f:
pyproject_data = pytoml.load(f)
# Ensure the mandatory data can be loaded
buildsys = pyproject_data['build-system']
requires = buildsys['requires']
backend = buildsys['build-backend']
hooks = Pep517HookCaller(source_dir, backend)
with BuildEnvironment() as env:
env.pip_install(requires)
_do_build(hooks, env, dist, dest)
parser = argparse.ArgumentParser()
parser.add_argument(
'source_dir',
help="A directory containing pyproject.toml",
)
parser.add_argument(
'--binary', '-b',
action='store_true',
default=False,
)
parser.add_argument(
'--source', '-s',
action='store_true',
default=False,
)
parser.add_argument(
'--out-dir', '-o',
help="Destination in which to save the builds relative to source dir",
)
def main(args):
# determine which dists to build
dists = list(filter(None, (
'sdist' if args.source or not args.binary else None,
'wheel' if args.binary or not args.source else None,
)))
for dist in dists:
build(args.source_dir, dist, args.out_dir)
if __name__ == '__main__':
main(parser.parse_args())
+202
View File
@@ -0,0 +1,202 @@
"""Check a project and backend by attempting to build using PEP 517 hooks.
"""
import argparse
import logging
import os
from os.path import isfile, join as pjoin
from pytoml import TomlError, load as toml_load
import shutil
from subprocess import CalledProcessError
import sys
import tarfile
from tempfile import mkdtemp
import zipfile
from .colorlog import enable_colourful_output
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
log = logging.getLogger(__name__)
def check_build_sdist(hooks, build_sys_requires):
with BuildEnvironment() as env:
try:
env.pip_install(build_sys_requires)
log.info('Installed static build dependencies')
except CalledProcessError:
log.error('Failed to install static build dependencies')
return False
try:
reqs = hooks.get_requires_for_build_sdist({})
log.info('Got build requires: %s', reqs)
except Exception:
log.error('Failure in get_requires_for_build_sdist', exc_info=True)
return False
try:
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
except CalledProcessError:
log.error('Failed to install dynamic build dependencies')
return False
td = mkdtemp()
log.info('Trying to build sdist in %s', td)
try:
try:
filename = hooks.build_sdist(td, {})
log.info('build_sdist returned %r', filename)
except Exception:
log.info('Failure in build_sdist', exc_info=True)
return False
if not filename.endswith('.tar.gz'):
log.error(
"Filename %s doesn't have .tar.gz extension", filename)
return False
path = pjoin(td, filename)
if isfile(path):
log.info("Output file %s exists", path)
else:
log.error("Output file %s does not exist", path)
return False
if tarfile.is_tarfile(path):
log.info("Output file is a tar file")
else:
log.error("Output file is not a tar file")
return False
finally:
shutil.rmtree(td)
return True
def check_build_wheel(hooks, build_sys_requires):
with BuildEnvironment() as env:
try:
env.pip_install(build_sys_requires)
log.info('Installed static build dependencies')
except CalledProcessError:
log.error('Failed to install static build dependencies')
return False
try:
reqs = hooks.get_requires_for_build_wheel({})
log.info('Got build requires: %s', reqs)
except Exception:
log.error('Failure in get_requires_for_build_sdist', exc_info=True)
return False
try:
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
except CalledProcessError:
log.error('Failed to install dynamic build dependencies')
return False
td = mkdtemp()
log.info('Trying to build wheel in %s', td)
try:
try:
filename = hooks.build_wheel(td, {})
log.info('build_wheel returned %r', filename)
except Exception:
log.info('Failure in build_wheel', exc_info=True)
return False
if not filename.endswith('.whl'):
log.error("Filename %s doesn't have .whl extension", filename)
return False
path = pjoin(td, filename)
if isfile(path):
log.info("Output file %s exists", path)
else:
log.error("Output file %s does not exist", path)
return False
if zipfile.is_zipfile(path):
log.info("Output file is a zip file")
else:
log.error("Output file is not a zip file")
return False
finally:
shutil.rmtree(td)
return True
def check(source_dir):
pyproject = pjoin(source_dir, 'pyproject.toml')
if isfile(pyproject):
log.info('Found pyproject.toml')
else:
log.error('Missing pyproject.toml')
return False
try:
with open(pyproject) as f:
pyproject_data = toml_load(f)
# Ensure the mandatory data can be loaded
buildsys = pyproject_data['build-system']
requires = buildsys['requires']
backend = buildsys['build-backend']
log.info('Loaded pyproject.toml')
except (TomlError, KeyError):
log.error("Invalid pyproject.toml", exc_info=True)
return False
hooks = Pep517HookCaller(source_dir, backend)
sdist_ok = check_build_sdist(hooks, requires)
wheel_ok = check_build_wheel(hooks, requires)
if not sdist_ok:
log.warning('Sdist checks failed; scroll up to see')
if not wheel_ok:
log.warning('Wheel checks failed')
return sdist_ok
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument(
'source_dir',
help="A directory containing pyproject.toml")
args = ap.parse_args(argv)
enable_colourful_output()
ok = check(args.source_dir)
if ok:
print(ansi('Checks passed', 'green'))
else:
print(ansi('Checks failed', 'red'))
sys.exit(1)
ansi_codes = {
'reset': '\x1b[0m',
'bold': '\x1b[1m',
'red': '\x1b[31m',
'green': '\x1b[32m',
}
def ansi(s, attr):
if os.name != 'nt' and sys.stdout.isatty():
return ansi_codes[attr] + str(s) + ansi_codes['reset']
else:
return str(s)
if __name__ == '__main__':
main()
+115
View File
@@ -0,0 +1,115 @@
"""Nicer log formatting with colours.
Code copied from Tornado, Apache licensed.
"""
# Copyright 2012 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import sys
try:
import curses
except ImportError:
curses = None
def _stderr_supports_color():
color = False
if curses and hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
try:
curses.setupterm()
if curses.tigetnum("colors") > 0:
color = True
except Exception:
pass
return color
class LogFormatter(logging.Formatter):
"""Log formatter with colour support
"""
DEFAULT_COLORS = {
logging.INFO: 2, # Green
logging.WARNING: 3, # Yellow
logging.ERROR: 1, # Red
logging.CRITICAL: 1,
}
def __init__(self, color=True, datefmt=None):
r"""
:arg bool color: Enables color support.
:arg string fmt: Log message format.
It will be applied to the attributes dict of log records. The
text between ``%(color)s`` and ``%(end_color)s`` will be colored
depending on the level if color support is on.
:arg dict colors: color mappings from logging level to terminal color
code
:arg string datefmt: Datetime format.
Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
.. versionchanged:: 3.2
Added ``fmt`` and ``datefmt`` arguments.
"""
logging.Formatter.__init__(self, datefmt=datefmt)
self._colors = {}
if color and _stderr_supports_color():
# The curses module has some str/bytes confusion in
# python3. Until version 3.2.3, most methods return
# bytes, but only accept strings. In addition, we want to
# output these strings with the logging module, which
# works with unicode strings. The explicit calls to
# unicode() below are harmless in python2 but will do the
# right conversion in python 3.
fg_color = (curses.tigetstr("setaf") or
curses.tigetstr("setf") or "")
if (3, 0) < sys.version_info < (3, 2, 3):
fg_color = str(fg_color, "ascii")
for levelno, code in self.DEFAULT_COLORS.items():
self._colors[levelno] = str(
curses.tparm(fg_color, code), "ascii")
self._normal = str(curses.tigetstr("sgr0"), "ascii")
scr = curses.initscr()
self.termwidth = scr.getmaxyx()[1]
curses.endwin()
else:
self._normal = ''
# Default width is usually 80, but too wide is
# worse than too narrow
self.termwidth = 70
def formatMessage(self, record):
mlen = len(record.message)
right_text = '{initial}-{name}'.format(initial=record.levelname[0],
name=record.name)
if mlen + len(right_text) < self.termwidth:
space = ' ' * (self.termwidth - (mlen + len(right_text)))
else:
space = ' '
if record.levelno in self._colors:
start_color = self._colors[record.levelno]
end_color = self._normal
else:
start_color = end_color = ''
return record.message + space + start_color + right_text + end_color
def enable_colourful_output(level=logging.INFO):
handler = logging.StreamHandler()
handler.setFormatter(LogFormatter())
logging.root.addHandler(handler)
logging.root.setLevel(level)
+23
View File
@@ -0,0 +1,23 @@
"""Handle reading and writing JSON in UTF-8, on Python 3 and 2."""
import json
import sys
if sys.version_info[0] >= 3:
# Python 3
def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, **kwargs)
def read_json(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# Python 2
def write_json(obj, path, **kwargs):
with open(path, 'wb') as f:
json.dump(obj, f, encoding='utf-8', **kwargs)
def read_json(path):
with open(path, 'rb') as f:
return json.load(f)
+158
View File
@@ -0,0 +1,158 @@
"""Build wheels/sdists by installing build deps to a temporary environment.
"""
import os
import logging
import pytoml
import shutil
from subprocess import check_call
import sys
from sysconfig import get_paths
from tempfile import mkdtemp
from .wrappers import Pep517HookCaller
log = logging.getLogger(__name__)
def _load_pyproject(source_dir):
with open(os.path.join(source_dir, 'pyproject.toml')) as f:
pyproject_data = pytoml.load(f)
buildsys = pyproject_data['build-system']
return buildsys['requires'], buildsys['build-backend']
class BuildEnvironment(object):
"""Context manager to install build deps in a simple temporary environment
Based on code I wrote for pip, which is MIT licensed.
"""
# Copyright (c) 2008-2016 The pip developers (see AUTHORS.txt file)
#
# 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, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# 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.
path = None
def __init__(self, cleanup=True):
self._cleanup = cleanup
def __enter__(self):
self.path = mkdtemp(prefix='pep517-build-env-')
log.info('Temporary build environment: %s', self.path)
self.save_path = os.environ.get('PATH', None)
self.save_pythonpath = os.environ.get('PYTHONPATH', None)
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
install_dirs = get_paths(install_scheme, vars={
'base': self.path,
'platbase': self.path,
})
scripts = install_dirs['scripts']
if self.save_path:
os.environ['PATH'] = scripts + os.pathsep + self.save_path
else:
os.environ['PATH'] = scripts + os.pathsep + os.defpath
if install_dirs['purelib'] == install_dirs['platlib']:
lib_dirs = install_dirs['purelib']
else:
lib_dirs = install_dirs['purelib'] + os.pathsep + \
install_dirs['platlib']
if self.save_pythonpath:
os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
self.save_pythonpath
else:
os.environ['PYTHONPATH'] = lib_dirs
return self
def pip_install(self, reqs):
"""Install dependencies into this env by calling pip in a subprocess"""
if not reqs:
return
log.info('Calling pip to install %s', reqs)
check_call([
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
'--prefix', self.path] + list(reqs))
def __exit__(self, exc_type, exc_val, exc_tb):
needs_cleanup = (
self._cleanup and
self.path is not None and
os.path.isdir(self.path)
)
if needs_cleanup:
shutil.rmtree(self.path)
if self.save_path is None:
os.environ.pop('PATH', None)
else:
os.environ['PATH'] = self.save_path
if self.save_pythonpath is None:
os.environ.pop('PYTHONPATH', None)
else:
os.environ['PYTHONPATH'] = self.save_pythonpath
def build_wheel(source_dir, wheel_dir, config_settings=None):
"""Build a wheel from a source directory using PEP 517 hooks.
:param str source_dir: Source directory containing pyproject.toml
:param str wheel_dir: Target directory to create wheel in
:param dict config_settings: Options to pass to build backend
This is a blocking function which will run pip in a subprocess to install
build requirements.
"""
if config_settings is None:
config_settings = {}
requires, backend = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend)
with BuildEnvironment() as env:
env.pip_install(requires)
reqs = hooks.get_requires_for_build_wheel(config_settings)
env.pip_install(reqs)
return hooks.build_wheel(wheel_dir, config_settings)
def build_sdist(source_dir, sdist_dir, config_settings=None):
"""Build an sdist from a source directory using PEP 517 hooks.
:param str source_dir: Source directory containing pyproject.toml
:param str sdist_dir: Target directory to place sdist in
:param dict config_settings: Options to pass to build backend
This is a blocking function which will run pip in a subprocess to install
build requirements.
"""
if config_settings is None:
config_settings = {}
requires, backend = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend)
with BuildEnvironment() as env:
env.pip_install(requires)
reqs = hooks.get_requires_for_build_sdist(config_settings)
env.pip_install(reqs)
return hooks.build_sdist(sdist_dir, config_settings)
+163
View File
@@ -0,0 +1,163 @@
from contextlib import contextmanager
import os
from os.path import dirname, abspath, join as pjoin
import shutil
from subprocess import check_call
import sys
from tempfile import mkdtemp
from . import compat
_in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py')
@contextmanager
def tempdir():
td = mkdtemp()
try:
yield td
finally:
shutil.rmtree(td)
class BackendUnavailable(Exception):
"""Will be raised if the backend cannot be imported in the hook process."""
class UnsupportedOperation(Exception):
"""May be raised by build_sdist if the backend indicates that it can't."""
def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
"""The default method of calling the wrapper subprocess."""
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
check_call(cmd, cwd=cwd, env=env)
class Pep517HookCaller(object):
"""A wrapper around a source directory to be built with a PEP 517 backend.
source_dir : The path to the source directory, containing pyproject.toml.
backend : The build backend spec, as per PEP 517, from pyproject.toml.
"""
def __init__(self, source_dir, build_backend):
self.source_dir = abspath(source_dir)
self.build_backend = build_backend
self._subprocess_runner = default_subprocess_runner
# TODO: Is this over-engineered? Maybe frontends only need to
# set this when creating the wrapper, not on every call.
@contextmanager
def subprocess_runner(self, runner):
prev = self._subprocess_runner
self._subprocess_runner = runner
yield
self._subprocess_runner = prev
def get_requires_for_build_wheel(self, config_settings=None):
"""Identify packages required for building a wheel
Returns a list of dependency specifications, e.g.:
["wheel >= 0.25", "setuptools"]
This does not include requirements specified in pyproject.toml.
It returns the result of calling the equivalently named hook in a
subprocess.
"""
return self._call_hook('get_requires_for_build_wheel', {
'config_settings': config_settings
})
def prepare_metadata_for_build_wheel(
self, metadata_directory, config_settings=None):
"""Prepare a *.dist-info folder with metadata for this project.
Returns the name of the newly created folder.
If the build backend defines a hook with this name, it will be called
in a subprocess. If not, the backend will be asked to build a wheel,
and the dist-info extracted from that.
"""
return self._call_hook('prepare_metadata_for_build_wheel', {
'metadata_directory': abspath(metadata_directory),
'config_settings': config_settings,
})
def build_wheel(
self, wheel_directory, config_settings=None,
metadata_directory=None):
"""Build a wheel from this project.
Returns the name of the newly created file.
In general, this will call the 'build_wheel' hook in the backend.
However, if that was previously called by
'prepare_metadata_for_build_wheel', and the same metadata_directory is
used, the previously built wheel will be copied to wheel_directory.
"""
if metadata_directory is not None:
metadata_directory = abspath(metadata_directory)
return self._call_hook('build_wheel', {
'wheel_directory': abspath(wheel_directory),
'config_settings': config_settings,
'metadata_directory': metadata_directory,
})
def get_requires_for_build_sdist(self, config_settings=None):
"""Identify packages required for building a wheel
Returns a list of dependency specifications, e.g.:
["setuptools >= 26"]
This does not include requirements specified in pyproject.toml.
It returns the result of calling the equivalently named hook in a
subprocess.
"""
return self._call_hook('get_requires_for_build_sdist', {
'config_settings': config_settings
})
def build_sdist(self, sdist_directory, config_settings=None):
"""Build an sdist from this project.
Returns the name of the newly created file.
This calls the 'build_sdist' backend hook in a subprocess.
"""
return self._call_hook('build_sdist', {
'sdist_directory': abspath(sdist_directory),
'config_settings': config_settings,
})
def _call_hook(self, hook_name, kwargs):
# On Python 2, pytoml returns Unicode values (which is correct) but the
# environment passed to check_call needs to contain string values. We
# convert here by encoding using ASCII (the backend can only contain
# letters, digits and _, . and : characters, and will be used as a
# Python identifier, so non-ASCII content is wrong on Python 2 in
# any case).
if sys.version_info[0] == 2:
build_backend = self.build_backend.encode('ASCII')
else:
build_backend = self.build_backend
with tempdir() as td:
compat.write_json({'kwargs': kwargs}, pjoin(td, 'input.json'),
indent=2)
# Run the hook in a subprocess
self._subprocess_runner(
[sys.executable, _in_proc_script, hook_name, td],
cwd=self.source_dir,
extra_environ={'PEP517_BUILD_BACKEND': build_backend}
)
data = compat.read_json(pjoin(td, 'output.json'))
if data.get('unsupported'):
raise UnsupportedOperation
if data.get('no_backend'):
raise BackendUnavailable
return data['return_val']
+1 -1
View File
@@ -22,7 +22,7 @@ import pkg_resources
# from graphviz import backend, Digraph
__version__ = '0.13.0'
__version__ = '0.13.1'
flatten = chain.from_iterable
+1497 -816
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -5,8 +5,8 @@
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'http://python-requests.org'
__version__ = '2.20.1'
__build__ = 0x022001
__version__ = '2.21.0'
__build__ = 0x022100
__author__ = 'Kenneth Reitz'
__author_email__ = 'me@kennethreitz.org'
__license__ = 'Apache 2.0'
+1 -1
View File
@@ -781,7 +781,7 @@ class Response(object):
return chunks
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None):
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None):
"""Iterates over the response data, one line at a time. When
stream=True is set on the request, this avoids reading the
content at once into memory for large responses.
+2 -1
View File
@@ -1,5 +1,6 @@
# -*- coding=utf-8 -*-
__version__ = '1.3.2'
from __future__ import absolute_import, print_function
__version__ = '1.4.0'
import logging
import warnings
+1 -1
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import absolute_import, print_function
import copy
import os
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import, unicode_literals, print_function
import collections
import io
File diff suppressed because it is too large Load Diff
+119 -61
View File
@@ -1,4 +1,6 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
import contextlib
import os
import sys
@@ -22,7 +24,9 @@ from vistir.contextmanagers import cd
from vistir.misc import run
from vistir.path import create_tracked_tempdir, ensure_mkdir_p, mkdir_p
from .utils import init_requirement, get_pyproject
from .utils import init_requirement, get_pyproject, get_name_variants
from ..environment import MYPY_RUNNING
from ..exceptions import RequirementError
try:
from os import scandir
@@ -30,6 +34,12 @@ except ImportError:
from scandir import scandir
if MYPY_RUNNING:
from typing import Any, Dict, List, Generator, Optional, Union
from pip_shims.shims import InstallRequirement
from pkg_resources import Requirement as PkgResourcesRequirement
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
# The following are necessary for people who like to use "if __name__" conditionals
@@ -40,6 +50,7 @@ _setup_distribution = None
@contextlib.contextmanager
def _suppress_distutils_logs():
# type: () -> None
"""Hack to hide noise generated by `setup.py develop`.
There isn't a good way to suppress them now, so let's monky-patch.
@@ -58,17 +69,22 @@ def _suppress_distutils_logs():
@ensure_mkdir_p(mode=0o775)
def _get_src_dir():
def _get_src_dir(root):
# type: (str) -> str
src = os.environ.get("PIP_SRC")
if src:
return src
virtual_env = os.environ.get("VIRTUAL_ENV")
if virtual_env:
return os.path.join(virtual_env, "src")
return os.path.join(os.getcwd(), "src") # Match pip's behavior.
if not root:
# Intentionally don't match pip's behavior here -- this is a temporary copy
root = create_tracked_tempdir(prefix="requirementslib-", suffix="-src")
return os.path.join(root, "src")
def ensure_reqs(reqs):
# type: (List[Union[str, PkgResourcesRequirement]]) -> List[PkgResourcesRequirement]
import pkg_resources
if not isinstance(reqs, Iterable):
raise TypeError("Expecting an Iterable, got %r" % reqs)
@@ -82,24 +98,27 @@ def ensure_reqs(reqs):
return new_reqs
def _prepare_wheel_building_kwargs(ireq):
download_dir = os.path.join(CACHE_DIR, "pkgs")
def _prepare_wheel_building_kwargs(ireq=None, src_root=None, editable=False):
# type: (Optional[InstallRequirement], Optional[str], bool) -> Dict[str, str]
download_dir = os.path.join(CACHE_DIR, "pkgs") # type: str
mkdir_p(download_dir)
wheel_download_dir = os.path.join(CACHE_DIR, "wheels")
wheel_download_dir = os.path.join(CACHE_DIR, "wheels") # type: str
mkdir_p(wheel_download_dir)
if ireq.source_dir is not None:
if ireq is None:
src_dir = _get_src_dir(root=src_root) # type: str
elif ireq is not None and ireq.source_dir is not None:
src_dir = ireq.source_dir
elif ireq.editable:
src_dir = _get_src_dir()
elif ireq is not None and ireq.editable:
src_dir = _get_src_dir(root=src_root)
else:
src_dir = create_tracked_tempdir(prefix="reqlib-src")
# This logic matches pip's behavior, although I don't fully understand the
# intention. I guess the idea is to build editables in-place, otherwise out
# of the source tree?
if ireq.editable:
if ireq is None and editable or (ireq is not None and ireq.editable):
build_dir = src_dir
else:
build_dir = create_tracked_tempdir(prefix="reqlib-build")
@@ -113,16 +132,25 @@ def _prepare_wheel_building_kwargs(ireq):
def iter_egginfos(path, pkg_name=None):
# type: (str, Optional[str]) -> Generator
if pkg_name is not None:
pkg_variants = get_name_variants(pkg_name)
non_matching_dirs = []
for entry in scandir(path):
if entry.is_dir():
if not entry.name.endswith("egg-info"):
for dir_entry in iter_egginfos(entry.path, pkg_name=pkg_name):
yield dir_entry
elif pkg_name is None or entry.name.startswith(pkg_name.replace("-", "_")):
yield entry
entry_name, ext = os.path.splitext(entry.name)
if ext.endswith("egg-info"):
if pkg_name is None or entry_name in pkg_variants:
yield entry
elif not entry.name.endswith("egg-info"):
non_matching_dirs.append(entry)
for entry in non_matching_dirs:
for dir_entry in iter_egginfos(entry.path, pkg_name=pkg_name):
yield dir_entry
def find_egginfo(target, pkg_name=None):
# type: (str, Optional[str]) -> Generator
egg_dirs = (egg_dir for egg_dir in iter_egginfos(target, pkg_name=pkg_name))
if pkg_name:
yield next(iter(egg_dirs), None)
@@ -132,8 +160,6 @@ def find_egginfo(target, pkg_name=None):
def get_metadata(path, pkg_name=None):
if pkg_name:
pkg_name = packaging.utils.canonicalize_name(pkg_name)
egg_dir = next(iter(find_egginfo(path, pkg_name=pkg_name)), None)
if egg_dir is not None:
import pkg_resources
@@ -148,7 +174,7 @@ def get_metadata(path, pkg_name=None):
if dist:
try:
requires = dist.requires()
except exception:
except Exception:
requires = []
try:
dep_map = dist._build_dep_map()
@@ -199,8 +225,9 @@ class SetupInfo(object):
ireq = attr.ib(default=None)
extra_kwargs = attr.ib(default=attr.Factory(dict), type=dict)
def parse_setup_cfg(self):
if self.setup_cfg is not None and self.setup_cfg.exists():
@classmethod
def get_setup_cfg(cls, setup_cfg_path):
if os.path.exists(setup_cfg_path):
default_opts = {
"metadata": {"name": "", "version": ""},
"options": {
@@ -212,46 +239,54 @@ class SetupInfo(object):
},
}
parser = configparser.ConfigParser(default_opts)
parser.read(self.setup_cfg.as_posix())
parser.read(setup_cfg_path)
results = {}
if parser.has_option("metadata", "name"):
name = parser.get("metadata", "name")
if not self.name and name is not None:
self.name = name
results["name"] = parser.get("metadata", "name")
if parser.has_option("metadata", "version"):
version = parser.get("metadata", "version")
if not self.version and version is not None:
self.version = version
results["version"] = parser.get("metadata", "version")
install_requires = {}
if parser.has_option("options", "install_requires"):
self.requires.update(
{
dep.strip(): init_requirement(dep.strip())
for dep in parser.get("options", "install_requires").split("\n")
if dep
}
)
install_requires = {
dep.strip(): init_requirement(dep.strip())
for dep in parser.get("options", "install_requires").split("\n")
if dep
}
results["install_requires"] = install_requires
if parser.has_option("options", "python_requires"):
python_requires = parser.get("options", "python_requires")
if python_requires and not self.python_requires:
self.python_requires = python_requires
results["python_requires"] = parser.get("options", "python_requires")
extras_require = {}
if "options.extras_require" in parser.sections():
self.extras.update(
{
section: [
init_requirement(dep.strip())
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"]
}
)
if self.ireq.extras:
self.requires.update({
extra: self.extras[extra]
for extra in self.ireq.extras if extra in self.extras
})
extras_require = {
section: [
init_requirement(dep.strip())
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
return results
def parse_setup_cfg(self):
if self.setup_cfg is not None and self.setup_cfg.exists():
parsed = self.get_setup_cfg(self.setup_cfg.as_posix())
if self.name is None:
self.name = parsed.get("name")
if self.version is None:
self.version = parsed.get("version")
self.requires.update(parsed["install_requires"])
if self.python_requires is None:
self.python_requires = parsed.get("python_requires")
self.extras.update(parsed["extras_require"])
if self.ireq is not None and self.ireq.extras:
self.requires.update({
extra: self.extras[extra]
for extra in self.ireq.extras if extra in self.extras
})
def run_setup(self):
if self.setup_py is not None and self.setup_py.exists():
@@ -304,6 +339,9 @@ class SetupInfo(object):
install_requires = dist.install_requires
if install_requires and not self.requires:
requirements = [init_requirement(req) for req in install_requires]
if getattr(self.ireq, "extras", None):
for extra in self.ireq.extras:
requirements.extend(list(self.extras.get(extra, [])))
self.requires.update({req.key: req for req in requirements})
if dist.setup_requires and not self.setup_requires:
self.setup_requires = dist.setup_requires
@@ -314,7 +352,7 @@ class SetupInfo(object):
if self.setup_py is not None and self.setup_py.exists():
metadata = get_metadata(self.setup_py.parent.as_posix(), pkg_name=self.name)
if metadata:
if not self.name:
if self.name is None:
self.name = metadata.get("name", self.name)
if not self.version:
self.version = metadata.get("version", self.version)
@@ -342,22 +380,35 @@ class SetupInfo(object):
self.build_requires = requires
def get_info(self):
initial_path = os.path.abspath(os.getcwd())
if self.setup_cfg and self.setup_cfg.exists():
self.parse_setup_cfg()
try:
self.parse_setup_cfg()
finally:
os.chdir(initial_path)
if self.setup_py and self.setup_py.exists():
if not self.requires or not self.name:
try:
self.run_setup()
except Exception:
self.get_egg_metadata()
finally:
os.chdir(initial_path)
if not self.requires or not self.name:
self.get_egg_metadata()
try:
self.get_egg_metadata()
finally:
os.chdir(initial_path)
if self.pyproject and self.pyproject.exists():
self.run_pyproject()
try:
self.run_pyproject()
finally:
os.chdir(initial_path)
return self.as_dict()
def as_dict(self):
# type: () -> Dict[str, Any]
prop_dict = {
"name": self.name,
"version": self.version,
@@ -411,7 +462,12 @@ class SetupInfo(object):
path = pip_shims.shims.url_to_path(unquote(ireq.link.url_without_fragment))
if pip_shims.shims.is_installable_dir(path):
ireq_src_dir = path
if not ireq.editable or not (pip_shims.is_file_url(ireq.link) and ireq_src_dir):
elif os.path.isdir(path):
raise RequirementError(
"The file URL points to a directory not installable: {}"
.format(ireq.link)
)
if not ireq.editable or not ireq.link.scheme == "file":
pip_shims.shims.unpack_url(
ireq.link,
ireq.source_dir,
@@ -454,4 +510,6 @@ class SetupInfo(object):
creation_kwargs["setup_cfg"] = setup_cfg
if ireq:
creation_kwargs["ireq"] = ireq
return cls(**creation_kwargs)
created = cls(**creation_kwargs)
created.get_info()
return created
+100 -15
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import absolute_import, print_function
import io
import os
@@ -21,26 +21,45 @@ from vistir.misc import dedup
from ..utils import SCHEME_LIST, VCS_LIST, is_star, add_ssh_scheme_to_git_uri
from ..environment import MYPY_RUNNING
if MYPY_RUNNING:
from typing import Union, Optional, List, Set, Any, TypeVar
from attr import _ValidatorType
from pkg_resources import Requirement as PkgResourcesRequirement
from pip_shims import Link
_T = TypeVar("_T")
HASH_STRING = " --hash={0}"
def filter_none(k, v):
# type: (str, Any) -> bool
if v:
return True
return False
def optional_instance_of(cls):
# type: (Any) -> _ValidatorType[Optional[_T]]
return validators.optional(validators.instance_of(cls))
def create_link(link):
# type: (str) -> Link
if not isinstance(link, six.string_types):
raise TypeError("must provide a string to instantiate a new link")
from pip_shims import Link
return Link(link)
def init_requirement(name):
# type: (str) -> PkgResourcesRequirement
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
@@ -62,14 +81,22 @@ def extras_to_string(extras):
def parse_extras(extras_str):
"""Turn a string of extras into a parsed extras list"""
# type: (str) -> List
"""
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):
"""Turn a list of specifier tuples into a string"""
# type: (List[str, Specifier]) -> str
"""
Turn a list of specifier tuples into a string
"""
if specs:
if isinstance(specs, six.string_types):
return specs
@@ -81,13 +108,20 @@ def specs_to_string(specs):
return ""
def build_vcs_link(vcs, uri, name=None, ref=None, subdirectory=None, extras=None):
def build_vcs_uri(
vcs, # type: str
uri, # type: str
name=None, # type: Optional[str]
ref=None, # type: Optional[str]
subdirectory=None, # type: Optional[str]
extras=None # type: Optional[List[str]]
):
# type: (...) -> str
if extras is None:
extras = []
vcs_start = "{0}+".format(vcs)
if not uri.startswith(vcs_start):
uri = "{0}{1}".format(vcs_start, uri)
uri = add_ssh_scheme_to_git_uri(uri)
if ref:
uri = "{0}@{1}".format(uri, ref)
if name:
@@ -97,7 +131,7 @@ def build_vcs_link(vcs, uri, name=None, ref=None, subdirectory=None, extras=None
uri = "{0}{1}".format(uri, extras)
if subdirectory:
uri = "{0}&subdirectory={1}".format(uri, subdirectory)
return create_link(uri)
return uri
def get_version(pipfile_entry):
@@ -115,6 +149,15 @@ def get_version(pipfile_entry):
def get_pyproject(path):
"""
Given a base path, look for the corresponding ``pyproject.toml`` file and return its
build_requires and build_backend.
:param str 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[str], str]
"""
from vistir.compat import Path
if not path:
return
@@ -146,7 +189,7 @@ def get_pyproject(path):
pyproject_data["build_system"] = build_system
else:
requires = build_system.get("requires")
backend = build_system.get("build-backend")
backend = build_system.get("build-backend", "setuptools.build_meta")
return (requires, backend)
@@ -232,6 +275,7 @@ def _requirement_to_str_lowercase_name(requirement):
important stuff that should not be lowercased (such as the marker). See
this issue for more information: https://github.com/pypa/pipenv/issues/2113.
"""
parts = [requirement.name.lower()]
if requirement.extras:
@@ -254,6 +298,7 @@ def format_requirement(ireq):
Generic formatter for pretty printing InstallRequirements to the terminal
in a less verbose way than using its `__str__` method.
"""
if ireq.editable:
line = '-e {}'.format(ireq.link)
else:
@@ -282,7 +327,8 @@ def format_specifier(ireq):
def get_pinned_version(ireq):
"""Get the pinned version of an InstallRequirement.
"""
Get the pinned version of an InstallRequirement.
An InstallRequirement is considered pinned if:
@@ -300,6 +346,7 @@ def get_pinned_version(ireq):
Raises `TypeError` if the input is not a valid InstallRequirement, or
`ValueError` if the InstallRequirement is not pinned.
"""
try:
specifier = ireq.specifier
except AttributeError:
@@ -324,7 +371,8 @@ def get_pinned_version(ireq):
def is_pinned_requirement(ireq):
"""Returns whether an InstallRequirement is a "pinned" requirement.
"""
Returns whether an InstallRequirement is a "pinned" requirement.
An InstallRequirement is considered pinned if:
@@ -339,6 +387,7 @@ def is_pinned_requirement(ireq):
django~=1.8 # NOT pinned
django==1.* # NOT pinned
"""
try:
get_pinned_version(ireq)
except (TypeError, ValueError):
@@ -350,6 +399,7 @@ def as_tuple(ireq):
"""
Pulls out the (name: str, version:str, extras:(str)) tuple from the pinned InstallRequirement.
"""
if not is_pinned_requirement(ireq):
raise TypeError('Expected a pinned InstallRequirement, got {}'.format(ireq))
@@ -360,12 +410,18 @@ def as_tuple(ireq):
def full_groupby(iterable, key=None):
"""Like groupby(), but sorts the input on the group key first."""
"""
Like groupby(), but sorts the input on the group key first.
"""
return groupby(sorted(iterable, key=key), key=key)
def flat_map(fn, collection):
"""Map a function over a collection and flatten the result by one-level"""
"""
Map a function over a collection and flatten the result by one-level
"""
return chain.from_iterable(map(fn, collection))
@@ -385,8 +441,7 @@ def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False):
For key functions that uniquely identify values, set unique=True:
>>> assert lookup_table(
... ['foo', 'bar', 'baz', 'qux', 'quux'], lambda s: s[0],
... unique=True) == {
... ['foo', 'bar', 'baz', 'qux', 'quux'], lambda s: s[0], unique=True) == {
... 'b': 'baz',
... 'f': 'foo',
... 'q': 'quux'
@@ -404,8 +459,8 @@ def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False):
... 'f': {'oo'},
... 'q': {'uux', 'ux'}
... }
"""
if keyval is None:
if key is None:
keyval = (lambda v: v)
@@ -443,7 +498,8 @@ def name_from_req(req):
def make_install_requirement(name, version, extras, markers, constraint=False):
"""make_install_requirement Generates an :class:`~pip._internal.req.req_install.InstallRequirement`.
"""
Generates an :class:`~pip._internal.req.req_install.InstallRequirement`.
Create an InstallRequirement from the supplied metadata.
@@ -539,6 +595,7 @@ def fix_requires_python_marker(requires_python):
def normalize_name(pkg):
# type: (str) -> str
"""Given a package name, return its normalized, non-canonicalized form.
:param str pkg: The name of a package
@@ -548,3 +605,31 @@ def normalize_name(pkg):
assert isinstance(pkg, six.string_types)
return pkg.replace("_", "-").lower()
def get_name_variants(pkg):
# type: (str) -> Set[str]
"""
Given a packager name, get the variants of its name for both the canonicalized
and "safe" forms.
:param str pkg: The package to lookup
:returns: A list of names.
:rtype: Set
"""
if not isinstance(pkg, six.string_types):
raise TypeError("must provide a string to derive package names")
from pkg_resources import safe_name
from packaging.utils import canonicalize_name
names = {safe_name(pkg), canonicalize_name(pkg)}
return names
SETUPTOOLS_SHIM = (
"import setuptools, tokenize;__file__=%r;"
"f=getattr(tokenize, 'open', open)(__file__);"
"code=f.read().replace('\\r\\n', '\\n');"
"f.close();"
"exec(compile(code, __file__, 'exec'))"
)
+33 -2
View File
@@ -1,11 +1,18 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
import attr
import importlib
import os
import pip_shims
import six
import sys
@attr.s
class VCSRepository(object):
DEFAULT_RUN_ARGS = None
url = attr.ib()
name = attr.ib()
checkout_directory = attr.ib()
@@ -14,13 +21,21 @@ class VCSRepository(object):
commit_sha = attr.ib(default=None)
ref = attr.ib(default=None)
repo_instance = attr.ib()
clone_log = attr.ib(default=None)
@repo_instance.default
def get_repo_instance(self):
from pip_shims import VcsSupport
if self.DEFAULT_RUN_ARGS is None:
default_run_args = self.monkeypatch_pip()
else:
default_run_args = self.DEFAULT_RUN_ARGS
from pip_shims.shims import VcsSupport
VCS_SUPPORT = VcsSupport()
backend = VCS_SUPPORT._registry.get(self.vcs_type)
return backend(url=self.url)
repo = backend(url=self.url)
if repo.run_command.__func__.__defaults__ != default_run_args:
repo.run_command.__func__.__defaults__ = default_run_args
return repo
@property
def is_local(self):
@@ -58,3 +73,19 @@ class VCSRepository(object):
def get_commit_hash(self, ref=None):
return self.repo_instance.get_revision(self.checkout_directory)
@classmethod
def monkeypatch_pip(cls):
target_module = pip_shims.shims.VcsSupport.__module__
pip_vcs = importlib.import_module(target_module)
run_command_defaults = pip_vcs.VersionControl.run_command.__defaults__
# set the default to not write stdout, the first option sets this value
new_defaults = [False,] + list(run_command_defaults)[1:]
new_defaults = tuple(new_defaults)
if six.PY3:
pip_vcs.VersionControl.run_command.__defaults__ = new_defaults
else:
pip_vcs.VersionControl.run_command.__func__.__defaults__ = new_defaults
sys.modules[target_module] = pip_vcs
cls.DEFAULT_RUN_ARGS = new_defaults
return new_defaults
+92 -17
View File
@@ -1,5 +1,5 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import
from __future__ import absolute_import, print_function
import contextlib
import logging
@@ -8,13 +8,14 @@ import os
import six
import sys
import tomlkit
import vistir
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc"))
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc"))
six.add_move(six.MovedAttribute("Set", "collections", "collections.abc"))
six.add_move(six.MovedAttribute("ItemsView", "collections", "collections.abc"))
from six.moves import Mapping, Sequence, Set, ItemsView
from six.moves.urllib.parse import urlparse, urlsplit
from six.moves.urllib.parse import urlparse, urlsplit, urlunparse
import pip_shims.shims
from vistir.compat import Path
@@ -81,18 +82,34 @@ def is_installable_dir(path):
def strip_ssh_from_git_uri(uri):
# type: (str) -> str
"""Return git+ssh:// formatted URI to git+git@ format"""
if isinstance(uri, six.string_types):
uri = uri.replace("git+ssh://", "git+", 1)
if "git+ssh://" in uri:
parsed = urlparse(uri)
# split the path on the first separating / so we can put the first segment
# into the 'netloc' section with a : separator
path_part, _, path = parsed.path.lstrip("/").partition("/")
path = "/{0}".format(path)
parsed = parsed._replace(
netloc="{0}:{1}".format(parsed.netloc, path_part), path=path
)
uri = urlunparse(parsed).replace("git+ssh://", "git+", 1)
return uri
def add_ssh_scheme_to_git_uri(uri):
# type: (str) -> str
"""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:
uri = uri.replace("git+", "git+ssh://", 1)
parsed = urlparse(uri)
if ":" in parsed.netloc:
netloc, _, path_start = parsed.netloc.rpartition(":")
path = "/{0}{1}".format(path_start, parsed.path)
uri = urlunparse(parsed._replace(netloc=netloc, path=path))
return uri
@@ -113,6 +130,8 @@ def is_vcs(pipfile_entry):
def is_editable(pipfile_entry):
if isinstance(pipfile_entry, Mapping):
return pipfile_entry.get("editable", False) is True
if isinstance(pipfile_entry, six.string_types):
return pipfile_entry.startswith("-e ")
return False
@@ -129,16 +148,30 @@ def is_star(val):
)
def convert_entry_to_path(path):
# type: (Dict[str, Any]) -> str
"""Convert a pipfile entry to a string"""
if not isinstance(path, Mapping):
raise TypeError("expecting a mapping, received {0!r}".format(path))
if not any(key in path for key in ["file", "path"]):
raise ValueError("missing path-like entry in supplied mapping {0!r}".format(path))
if "file" in path:
path = vistir.path.url_to_path(path["file"])
elif "path" in path:
path = path["path"]
return path
def is_installable_file(path):
"""Determine if a path can potentially be installed"""
from packaging import specifiers
if hasattr(path, "keys") and any(
key for key in path.keys() if key in ["file", "path"]
):
path = urlparse(path["file"]).path if "file" in path else path["path"]
if not isinstance(path, six.string_types) or path == "*":
return False
if isinstance(path, Mapping):
path = convert_entry_to_path(path)
# If the string starts with a valid specifier operator, test if it is a valid
# specifier set before making a path object (to avoid breaking windows)
@@ -152,23 +185,65 @@ def is_installable_file(path):
return False
parsed = urlparse(path)
if parsed.scheme == "file":
path = parsed.path
if not os.path.exists(os.path.abspath(path)):
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.path.url_to_path(path)
normalized_path = vistir.path.normalize_path(path)
if is_local and not os.path.exists(normalized_path):
return False
lookup_path = Path(path)
absolute_path = "{0}".format(lookup_path.absolute())
if lookup_path.is_dir() and is_installable_dir(absolute_path):
is_archive = pip_shims.shims.is_archive_file(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
elif lookup_path.is_file() and pip_shims.shims.is_archive_file(absolute_path):
if not is_local and pip_shims.shims.is_archive_file(parsed.path):
return True
return False
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')
else:
metadata = ""
feed_parser = FeedParser()
feed_parser.feed(metadata)
return feed_parser.close()
def get_setup_paths(base_path, subdirectory=None):
# type: (str, Optional[str]) -> Dict[str, Optional[str]]
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")
setup_cfg = os.path.join(base_path, "setup.cfg")
pyproject_toml = os.path.join(base_path, "pyproject.toml")
if subdirectory is not None:
base_path = os.path.join(base_path, subdirectory)
subdir_setup_py = os.path.join(subdirectory, "setup.py")
subdir_setup_cfg = os.path.join(subdirectory, "setup.cfg")
subdir_pyproject_toml = os.path.join(subdirectory, "pyproject.toml")
if subdirectory and os.path.exists(subdir_setup_py):
setup_py = subdir_setup_py
if subdirectory and os.path.exists(subdir_setup_cfg):
setup_cfg = subdir_setup_cfg
if subdirectory and os.path.exists(subdir_pyproject_toml):
pyproject_toml = subdir_pyproject_toml
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
}
def prepare_pip_source_args(sources, pip_args=None):
if pip_args is None:
pip_args = []
+1 -1
View File
@@ -4,7 +4,7 @@ import os
from ._core import ShellDetectionFailure
__version__ = '1.2.7'
__version__ = '1.2.8'
def detect_shell(pid=None, max_depth=6):
+12 -1
View File
@@ -75,7 +75,18 @@ def _iter_process():
# looking for. We can fix this when it actually matters. (#8)
continue
raise WinError()
info = {'executable': str(pe.szExeFile.decode('utf-8'))}
# The executable name would be encoded with the current code page if
# we're in ANSI mode (usually). Try to decode it into str/unicode,
# replacing invalid characters to be safe (not thoeratically necessary,
# I think). Note that we need to use 'mbcs' instead of encoding
# settings from sys because this is from the Windows API, not Python
# internals (which those settings reflect). (pypa/pipenv#3382)
executable = pe.szExeFile
if isinstance(executable, bytes):
executable = executable.decode('mbcs', 'replace')
info = {'executable': executable}
if pe.th32ParentProcessID:
info['parent_pid'] = pe.th32ParentProcessID
yield pe.th32ProcessID, info
+1 -1
View File
@@ -21,7 +21,7 @@ def _get_process_mapping():
processes = {}
for line in output.split('\n'):
try:
pid, ppid, args = line.strip().split(None, 2)
pid, ppid, args = line.strip().split(maxsplit=2)
except ValueError:
continue
processes[pid] = Process(
+27
View File
@@ -0,0 +1,27 @@
import collections
import shlex
import subprocess
import sys
Process = collections.namedtuple('Process', 'args pid ppid')
def get_process_mapping():
"""Try to look up the process tree via the output of `ps`.
"""
output = subprocess.check_output([
'ps', '-ww', '-o', 'pid=', '-o', 'ppid=', '-o', 'args=',
])
if not isinstance(output, str):
output = output.decode(sys.stdout.encoding)
processes = {}
for line in output.split('\n'):
try:
pid, ppid, args = line.strip().split(None, 2)
except ValueError:
continue
processes[pid] = Process(
args=tuple(shlex.split(args)), pid=pid, ppid=ppid,
)
return processes
+20 -14
View File
@@ -1,34 +1,40 @@
import os
import re
from ._default import Process
from ._core import Process
STAT_PPID = 3
STAT_TTY = 6
STAT_PATTERN = re.compile(r'\(.+\)|\S+')
def _get_stat(pid):
with open(os.path.join('/proc', str(pid), 'stat')) as f:
parts = STAT_PATTERN.findall(f.read())
return parts[STAT_TTY], parts[STAT_PPID]
def _get_cmdline(pid):
with open(os.path.join('/proc', str(pid), 'cmdline')) as f:
return tuple(f.read().split('\0')[:-1])
def get_process_mapping():
"""Try to look up the process tree via the /proc interface.
"""
with open('/proc/{0}/stat'.format(os.getpid())) as f:
self_tty = f.read().split()[STAT_TTY]
self_tty = _get_stat(os.getpid())[0]
processes = {}
for pid in os.listdir('/proc'):
if not pid.isdigit():
continue
try:
stat = '/proc/{0}/stat'.format(pid)
cmdline = '/proc/{0}/cmdline'.format(pid)
with open(stat) as fstat, open(cmdline) as fcmdline:
stat = re.findall(r'\(.+\)|\S+', fstat.read())
cmd = fcmdline.read().split('\x00')[:-1]
ppid = stat[STAT_PPID]
tty = stat[STAT_TTY]
if tty == self_tty:
processes[pid] = Process(
args=tuple(cmd), pid=pid, ppid=ppid,
)
tty, ppid = _get_stat(pid)
if tty != self_tty:
continue
args = _get_cmdline(pid)
processes[pid] = Process(args=args, pid=pid, ppid=ppid)
except IOError:
# Process has disappeared - just ignore it.
continue
+1 -3
View File
@@ -1,10 +1,8 @@
import collections
import shlex
import subprocess
import sys
Process = collections.namedtuple('Process', 'args pid ppid')
from ._core import Process
def get_process_mapping():
+35
View File
@@ -0,0 +1,35 @@
import os
import re
from ._default import Process
STAT_PPID = 3
STAT_TTY = 6
def get_process_mapping():
"""Try to look up the process tree via Linux's /proc
"""
with open('/proc/{0}/stat'.format(os.getpid())) as f:
self_tty = f.read().split()[STAT_TTY]
processes = {}
for pid in os.listdir('/proc'):
if not pid.isdigit():
continue
try:
stat = '/proc/{0}/stat'.format(pid)
cmdline = '/proc/{0}/cmdline'.format(pid)
with open(stat) as fstat, open(cmdline) as fcmdline:
stat = re.findall(r'\(.+\)|\S+', fstat.read())
cmd = fcmdline.read().split('\x00')[:-1]
ppid = stat[STAT_PPID]
tty = stat[STAT_TTY]
if tty == self_tty:
processes[pid] = Process(
args=tuple(cmd), pid=pid, ppid=ppid,
)
except IOError:
# Process has disappeared - just ignore it.
continue
return processes
+7 -4
View File
@@ -1,5 +1,4 @@
import errno
import shlex
import subprocess
import sys
@@ -34,9 +33,13 @@ def get_process_mapping():
for line in output.split('\n'):
try:
pid, ppid, args = line.strip().split(None, 2)
processes[pid] = Process(
args=tuple(shlex.split(args)), pid=pid, ppid=ppid,
)
# XXX: This is not right, but we are really out of options.
# ps does not offer a sane way to decode the argument display,
# and this is "Good Enough" for obtaining shell names. Hopefully
# people don't name their shell with a space, or have something
# like "/usr/bin/xonsh is uber". (sarugaku/shellingham#14)
args = tuple(a.strip() for a in args.split(' '))
except ValueError:
continue
processes[pid] = Process(args=args, pid=pid, ppid=ppid)
return processes
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2010-2017 Benjamin Peterson
Copyright (c) 2010-2018 Benjamin Peterson
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
+63 -2
View File
@@ -1,4 +1,4 @@
# Copyright (c) 2010-2017 Benjamin Peterson
# Copyright (c) 2010-2018 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -29,7 +29,7 @@ import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.11.0"
__version__ = "1.12.0"
# Useful for very coarse version differentiation.
@@ -844,10 +844,71 @@ def add_metaclass(metaclass):
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
if hasattr(cls, '__qualname__'):
orig_vars['__qualname__'] = cls.__qualname__
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
def ensure_binary(s, encoding='utf-8', errors='strict'):
"""Coerce **s** to six.binary_type.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> encoded to `bytes`
- `bytes` -> `bytes`
"""
if isinstance(s, text_type):
return s.encode(encoding, errors)
elif isinstance(s, binary_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
def ensure_str(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to `str`.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if not isinstance(s, (text_type, binary_type)):
raise TypeError("not expecting type '%s'" % type(s))
if PY2 and isinstance(s, text_type):
s = s.encode(encoding, errors)
elif PY3 and isinstance(s, binary_type):
s = s.decode(encoding, errors)
return s
def ensure_text(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to six.text_type.
For Python 2:
- `unicode` -> `unicode`
- `str` -> `unicode`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if isinstance(s, binary_type):
return s.decode(encoding, errors)
elif isinstance(s, text_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
def python_2_unicode_compatible(klass):
"""
A decorator that defines __unicode__ and __str__ methods under Python 2.
+1 -1
View File
@@ -22,4 +22,4 @@ from .api import value
from .api import ws
__version__ = "0.5.2"
__version__ = "0.5.3"
+15
View File
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
import copy
from ._compat import decode
from .exceptions import KeyAlreadyPresent
from .exceptions import NonExistentKey
@@ -600,3 +602,16 @@ class Container(dict):
self._map = state[0]
self._body = state[1]
self._parsed = state[2]
def copy(self): # type: () -> Container
return copy.copy(self)
def __copy__(self): # type: () -> Container
c = self.__class__(self._parsed)
for k, v in super(Container, self).copy().items():
super(Container, c).__setitem__(k, v)
c._body += self.body
c._map.update(self._map)
return c
+4 -1
View File
@@ -527,7 +527,10 @@ class DateTime(Item, datetime):
def __sub__(self, other):
result = super(DateTime, self).__sub__(other)
return self._new(result)
if isinstance(result, datetime):
result = self._new(result)
return result
def _new(self, result):
raw = result.isoformat()
-4
View File
@@ -45,10 +45,6 @@ class _State:
if self._save_marker:
self._source._marker = self._marker
# Restore exceptions are silently consumed, other exceptions need to
# propagate
return exception_type is None
class _StateHandler:
"""
+1 -1
View File
@@ -27,7 +27,7 @@ from logging import NullHandler
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
__license__ = 'MIT'
__version__ = '1.24'
__version__ = '1.24.1'
__all__ = (
'HTTPConnectionPool',
+4 -4
View File
@@ -69,9 +69,9 @@ class GzipDecoder(object):
return getattr(self._obj, name)
def decompress(self, data):
ret = b''
ret = bytearray()
if self._state == GzipDecoderState.SWALLOW_DATA or not data:
return ret
return bytes(ret)
while True:
try:
ret += self._obj.decompress(data)
@@ -81,11 +81,11 @@ class GzipDecoder(object):
self._state = GzipDecoderState.SWALLOW_DATA
if previous_state == GzipDecoderState.OTHER_MEMBERS:
# Allow trailing garbage acceptable in other gzip clients
return ret
return bytes(ret)
raise
data = self._obj.unused_data
if not data:
return ret
return bytes(ret)
self._state = GzipDecoderState.OTHER_MEMBERS
self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS)
+2
View File
@@ -263,6 +263,8 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None,
"""
context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23)
context.set_ciphers(ciphers or DEFAULT_CIPHERS)
# Setting the default here, as we may have no ssl module on import
cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
+10 -3
View File
@@ -6,6 +6,7 @@ from .compat import (
TemporaryDirectory,
partialmethod,
to_native_string,
StringIO,
)
from .contextmanagers import (
atomic_open_for_write,
@@ -14,6 +15,7 @@ from .contextmanagers import (
temp_environ,
temp_path,
spinner,
replaced_stream
)
from .misc import (
load_path,
@@ -26,12 +28,14 @@ from .misc import (
take,
chunked,
divide,
get_wrapped_stream,
StreamWrapper
)
from .path import mkdir_p, rmtree, create_tracked_tempdir, create_tracked_tempfile
from .spin import VistirSpinner, create_spinner
from .spin import create_spinner
__version__ = '0.2.5'
__version__ = '0.3.0'
__all__ = [
@@ -50,7 +54,6 @@ __all__ = [
"NamedTemporaryFile",
"partialmethod",
"spinner",
"VistirSpinner",
"create_spinner",
"create_tracked_tempdir",
"create_tracked_tempfile",
@@ -61,4 +64,8 @@ __all__ = [
"take",
"chunked",
"divide",
"StringIO",
"get_wrapped_stream",
"StreamWrapper",
"replaced_stream"
]
+102 -10
View File
@@ -1,6 +1,7 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import, print_function, unicode_literals
import codecs
import errno
import os
import sys
@@ -19,7 +20,6 @@ __all__ = [
"JSONDecodeError",
"FileNotFoundError",
"ResourceWarning",
"FileNotFoundError",
"PermissionError",
"IsADirectoryError",
"fs_str",
@@ -27,6 +27,15 @@ __all__ = [
"TemporaryDirectory",
"NamedTemporaryFile",
"to_native_string",
"Iterable",
"Mapping",
"Sequence",
"Set",
"ItemsView",
"fs_encode",
"fs_decode",
"_fs_encode_errors",
"_fs_decode_errors"
]
if sys.version_info >= (3, 5):
@@ -47,20 +56,22 @@ else:
try:
from weakref import finalize
except ImportError:
from pipenv.vendor.backports.weakref import finalize
from pipenv.vendor.backports.weakref import finalize # type: ignore
try:
from functools import partialmethod
except Exception:
from .backports.functools import partialmethod
from .backports.functools import partialmethod # type: ignore
try:
from json import JSONDecodeError
except ImportError: # Old Pythons.
JSONDecodeError = ValueError
JSONDecodeError = ValueError # type: ignore
if six.PY2:
from io import BytesIO as StringIO
class ResourceWarning(Warning):
pass
@@ -80,12 +91,24 @@ if six.PY2:
"""The command does not work on directories"""
pass
class FileExistsError(OSError):
def __init__(self, *args, **kwargs):
self.errno = errno.EEXIST
super(FileExistsError, self).__init__(*args, **kwargs)
else:
from builtins import ResourceWarning, FileNotFoundError, PermissionError, IsADirectoryError
six.add_move(six.MovedAttribute("Iterable", "collections", "collections.abc"))
from six.moves import Iterable
from builtins import (
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("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
if not sys.warnoptions:
warnings.simplefilter("default", ResourceWarning)
@@ -179,13 +202,82 @@ def fs_str(string):
Borrowed from pip-tools
"""
if isinstance(string, str):
return string
assert not isinstance(string, bytes)
return string.encode(_fs_encoding)
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
def _get_path(path):
"""
Fetch the string value from a path-like object
Returns **None** if there is no string value.
"""
if isinstance(path, (six.string_types, bytes)):
return path
path_type = type(path)
try:
path_repr = path_type.__fspath__(path)
except AttributeError:
return
if isinstance(path_repr, (six.string_types, bytes)):
return path_repr
return
def fs_encode(path):
"""
Encode a filesystem path to the proper filesystem encoding
:param Union[str, bytes] path: A string-like path
:returns: A bytes-encoded filesystem path representation
"""
path = _get_path(path)
if path is None:
raise TypeError("expected a valid path to encode")
if isinstance(path, six.text_type):
path = path.encode(_fs_encoding, _fs_encode_errors)
return path
def fs_decode(path):
"""
Decode a filesystem path using the proper filesystem encoding
:param path: The filesystem path to decode from bytes or string
:return: [description]
:rtype: [type]
"""
path = _get_path(path)
if path is None:
raise TypeError("expected a valid path to decode")
if isinstance(path, six.binary_type):
path = path.decode(_fs_encoding, _fs_decode_errors)
return path
if sys.version_info >= (3, 3) and os.name != "nt":
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
else:
_fs_encoding = "utf-8"
if six.PY3:
if os.name == "nt":
_fs_error_fn = None
alt_strategy = "surrogatepass"
else:
alt_strategy = "surrogateescape"
_fs_error_fn = getattr(sys, "getfilesystemencodeerrors", None)
_fs_encode_errors = _fs_error_fn() if _fs_error_fn is not None else alt_strategy
_fs_decode_errors = _fs_error_fn() if _fs_error_fn is not None else alt_strategy
else:
_fs_encode_errors = "backslashreplace"
_fs_decode_errors = "replace"
def to_native_string(string):
+56 -2
View File
@@ -1,5 +1,5 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import, unicode_literals, print_function
import io
import os
@@ -15,7 +15,8 @@ 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"
"temp_environ", "temp_path", "cd", "atomic_open_for_write", "open_file", "spinner",
"dummy_spinner", "replaced_stream"
]
@@ -286,3 +287,56 @@ def open_file(link, session=None, stream=True):
if conn is not None:
conn.close()
result.close()
@contextmanager
def replaced_stream(stream_name):
"""
Context manager to temporarily swap out *stream_name* with a stream wrapper.
:param str stream_name: The name of a sys stream to wrap
:returns: A ``StreamWrapper`` replacement, temporarily
>>> orig_stdout = sys.stdout
>>> with replaced_stream("stdout") as stdout:
... sys.stdout.write("hello")
... assert stdout.getvalue() == "hello"
>>> sys.stdout.write("hello")
'hello'
"""
orig_stream = getattr(sys, stream_name)
new_stream = six.StringIO()
try:
setattr(sys, stream_name, new_stream)
yield getattr(sys, stream_name)
finally:
setattr(sys, stream_name, orig_stream)
@contextmanager
def replaced_streams():
"""
Context manager to replace both ``sys.stdout`` and ``sys.stderr`` using
``replaced_stream``
returns: *(stdout, stderr)*
>>> import sys
>>> with vistir.contextmanagers.replaced_streams() as streams:
>>> stdout, stderr = streams
>>> sys.stderr.write("test")
>>> sys.stdout.write("hello")
>>> assert stdout.getvalue() == "hello"
>>> assert stderr.getvalue() == "test"
>>> stdout.getvalue()
'hello'
>>> stderr.getvalue()
'test'
"""
with replaced_stream("stdout") as stdout:
with replaced_stream("stderr") as stderr:
yield (stdout, stderr)
+184 -16
View File
@@ -1,6 +1,7 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import, unicode_literals, print_function
import io
import json
import logging
import locale
@@ -15,7 +16,7 @@ from itertools import islice, tee
import six
from .cmdparse import Script
from .compat import Path, fs_str, partialmethod, to_native_string, Iterable
from .compat import Path, fs_str, partialmethod, to_native_string, Iterable, StringIO
from .contextmanagers import spinner as spinner
if os.name != "nt":
@@ -38,6 +39,9 @@ __all__ = [
"divide",
"getpreferredencoding",
"decode_for_output",
"get_canonical_encoding_name",
"get_wrapped_stream",
"StreamWrapper",
]
@@ -159,7 +163,10 @@ def _create_subprocess(
c = _spawn_subprocess(cmd, env=env, block=block, cwd=cwd,
combine_stderr=combine_stderr)
except Exception as exc:
sys.stderr.write("Error %s while executing command %s", exc, " ".join(cmd._parts))
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()
@@ -279,14 +286,11 @@ def run(
_env = os.environ.copy()
if env:
_env.update(env)
env = _env
if six.PY2:
fs_encode = partial(to_bytes, encoding=locale_encoding)
_env = {fs_encode(k): fs_encode(v) for k, v in os.environ.items()}
for key, val in env.items():
_env[fs_encode(key)] = fs_encode(val)
_env = {fs_encode(k): fs_encode(v) for k, v in _env.items()}
else:
_env = {k: fs_str(v) for k, v in os.environ.items()}
_env = {k: fs_str(v) for k, v in _env.items()}
if not spinner_name:
spinner_name = "bouncingBar"
if six.PY2:
@@ -315,7 +319,6 @@ def run(
)
def load_path(python):
"""Load the :mod:`sys.path` from the given python executable's environment as json
@@ -329,7 +332,7 @@ def load_path(python):
python = Path(python).as_posix()
out, err = run([python, "-c", "import json, sys; print(json.dumps(sys.path))"],
nospin=True)
nospin=True)
if out:
return json.loads(out)
else:
@@ -515,19 +518,184 @@ def getpreferredencoding():
PREFERRED_ENCODING = getpreferredencoding()
def decode_for_output(output):
def get_output_encoding(source_encoding):
"""
Given a source encoding, determine the preferred output encoding.
:param str source_encoding: The encoding of the source material.
:returns: The output encoding to decode to.
:rtype: str
"""
if source_encoding is not None:
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)
def _encode(output, encoding=None, errors=None, translation_map=None):
if encoding is None:
encoding = PREFERRED_ENCODING
try:
output = output.encode(encoding)
except (UnicodeDecodeError, UnicodeEncodeError):
if translation_map is not None:
if six.PY2:
output = unicode.translate(
to_text(output, encoding=encoding, errors=errors), translation_map
)
else:
output = output.translate(translation_map)
else:
output = to_text(output, encoding=encoding, errors=errors)
except AttributeError:
pass
return output
def decode_for_output(output, target_stream=None, translation_map=None):
"""Given a string, decode it for output to a terminal
:param str output: A string to print to a terminal
:param target_stream: A stream to write to, we will encode to target this stream if possible.
:param dict translation_map: A mapping of unicode character ordinals to replacement strings.
:return: A re-encoded string using the preferred encoding
:rtype: str
"""
if not isinstance(output, six.string_types):
return output
encoding = None
if target_stream is not None:
encoding = getattr(target_stream, "encoding", None)
encoding = get_output_encoding(encoding)
try:
output = output.encode(PREFERRED_ENCODING)
except AttributeError:
pass
output = output.decode(PREFERRED_ENCODING)
return output
output = _encode(output, encoding=encoding, translation_map=translation_map)
except (UnicodeDecodeError, UnicodeEncodeError):
output = _encode(output, encoding=encoding, errors="replace",
translation_map=translation_map)
return to_text(output, encoding=encoding, errors="replace")
def get_canonical_encoding_name(name):
# type: (str) -> str
"""
Given an encoding name, get the canonical name from a codec lookup.
:param str name: The name of the codec to lookup
:return: The canonical version of the codec name
:rtype: str
"""
import codecs
try:
codec = codecs.lookup(name)
except LookupError:
return name
else:
return codec.name
def get_wrapped_stream(stream):
"""
Given a stream, wrap it in a `StreamWrapper` instance and return the wrapped stream.
:param stream: A stream instance to wrap
:returns: A new, wrapped stream
:rtype: :class:`StreamWrapper`
"""
if stream is None:
raise TypeError("must provide a stream to wrap")
encoding = getattr(stream, "encoding", None)
encoding = get_output_encoding(encoding)
return StreamWrapper(stream, encoding, "replace", line_buffering=True)
class StreamWrapper(io.TextIOWrapper):
"""
This wrapper class will wrap a provided stream and supply an interface
for compatibility.
"""
def __init__(self, stream, encoding, errors, line_buffering=True, **kwargs):
self._stream = stream = _StreamProvider(stream)
io.TextIOWrapper.__init__(
self, stream, encoding, errors, line_buffering=line_buffering, **kwargs
)
# 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)):
try:
self.flush()
except Exception:
pass
return self.buffer.write(str(x))
return io.TextIOWrapper.write(self, x)
def writelines(self, lines):
for line in lines:
self.write(line)
def __del__(self):
try:
self.detach()
except Exception:
pass
def isatty(self):
return self._stream.isatty()
# More things borrowed from click, this is because we are using `TextIOWrapper` instead of
# just a normal StringIO
class _StreamProvider(object):
def __init__(self, stream):
self._stream = stream
super(_StreamProvider, self).__init__()
def __getattr__(self, name):
return getattr(self._stream, name)
def read1(self, size):
fn = getattr(self._stream, "read1", None)
if fn is not None:
return fn(size)
if six.PY2:
return self._stream.readline(size)
return self._stream.read(size)
def readable(self):
fn = getattr(self._stream, "readable", None)
if fn is not None:
return fn()
try:
self._stream.read(0)
except Exception:
return False
return True
def writable(self):
fn = getattr(self._stream, "writable", None)
if fn is not None:
return fn()
try:
self._stream.write(b"")
except Exception:
return False
return True
def seekable(self):
fn = getattr(self._stream, "seekable", None)
if fn is not None:
return fn()
try:
self._stream.seek(self._stream.tell())
except Exception:
return False
return True
+13 -19
View File
@@ -23,6 +23,8 @@ from .compat import (
TemporaryDirectory,
_fs_encoding,
finalize,
fs_decode,
fs_encode
)
@@ -195,9 +197,8 @@ def is_readonly_path(fn):
Permissions check is `bool(path.stat & stat.S_IREAD)` or `not os.access(path, os.W_OK)`
"""
from .compat import to_native_string
fn = to_native_string(fn)
fn = fs_encode(fn)
if os.path.exists(fn):
file_stat = os.stat(fn).st_mode
return not bool(file_stat & stat.S_IWRITE) or not os.access(fn, os.W_OK)
@@ -212,20 +213,19 @@ def mkdir_p(newdir, mode=0o777):
:raises: OSError if a file is encountered along the way
"""
# http://code.activestate.com/recipes/82465-a-friendly-mkdir/
from .misc import to_bytes, to_text
newdir = to_bytes(newdir, "utf-8")
newdir = fs_encode(newdir)
if os.path.exists(newdir):
if not os.path.isdir(newdir):
raise OSError(
"a file with the same name as the desired dir, '{0}', already exists.".format(
newdir
fs_decode(newdir)
)
)
else:
head, tail = os.path.split(to_bytes(newdir, encoding="utf-8"))
head, tail = os.path.split(newdir)
# Make sure the tail doesn't point to the asame place as the head
curdir = to_bytes(".", encoding="utf-8")
curdir = fs_encode(".")
tail_and_head_match = (
os.path.relpath(tail, start=os.path.basename(head)) == curdir
)
@@ -234,7 +234,7 @@ def mkdir_p(newdir, mode=0o777):
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(
to_text(newdir, encoding="utf-8")
fs_decode(newdir)
)
)
os.makedirs(os.path.join(head, tail), mode)
@@ -296,9 +296,7 @@ def set_write_bit(fn):
:param str fn: The target filename or path
"""
from .compat import to_native_string
fn = to_native_string(fn)
fn = fs_encode(fn)
if not os.path.exists(fn):
return
file_stat = os.stat(fn).st_mode
@@ -330,9 +328,7 @@ def rmtree(directory, ignore_errors=False, onerror=None):
Setting `ignore_errors=True` may cause this to silently fail to delete the path
"""
from .compat import to_native_string
directory = to_native_string(directory)
directory = fs_encode(directory)
if onerror is None:
onerror = handle_remove_readonly
try:
@@ -341,9 +337,8 @@ def rmtree(directory, ignore_errors=False, onerror=None):
)
except (IOError, OSError, FileNotFoundError) as exc:
# Ignore removal failures where the file doesn't exist
if exc.errno == errno.ENOENT:
pass
raise
if exc.errno != errno.ENOENT:
raise
def handle_remove_readonly(func, path, exc):
@@ -361,7 +356,7 @@ def handle_remove_readonly(func, path, exc):
"""
# Check for read-only attribute
from .compat import (
ResourceWarning, FileNotFoundError, PermissionError, to_native_string
ResourceWarning, FileNotFoundError, PermissionError
)
PERM_ERRORS = (errno.EACCES, errno.EPERM, errno.ENOENT)
@@ -370,7 +365,6 @@ def handle_remove_readonly(func, path, exc):
)
# split the initial exception out into its type, exception, and traceback
exc_type, exc_exception, exc_tb = exc
path = to_native_string(path)
if is_readonly_path(path):
# Apply write permission and call original function
set_write_bit(path)
+105 -65
View File
@@ -1,4 +1,5 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
import functools
import os
@@ -8,41 +9,72 @@ import threading
import time
import colorama
import cursor
import six
from .compat import to_native_string
from .termcolors import COLOR_MAP, COLORS, colored, DISABLE_COLORS
from .misc import decode_for_output
from io import StringIO
try:
import yaspin
import cursor
except ImportError:
yaspin = None
Spinners = None
SpinBase = None
cursor = None
else:
from yaspin.spinners import Spinners
import yaspin.spinners
import yaspin.core
Spinners = yaspin.spinners.Spinners
SpinBase = yaspin.core.Yaspin
handler = None
if yaspin and os.name == "nt":
handler = yaspin.signal_handlers.default_handler
elif yaspin and os.name != "nt":
handler = yaspin.signal_handlers.fancy_handler
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``.
``signum`` and ``frame`` are mandatory arguments. Check ``signal.signal``
function for more details.
"""
spinner.fail()
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``.
``signum`` and ``frame`` are mandatory arguments. Check ``signal.signal``
function for more details.
"""
spinner.red.fail("")
spinner.stop()
sys.exit(0)
CLEAR_LINE = chr(27) + "[K"
TRANSLATION_MAP = {
10004: u"OK",
10008: u"x",
}
decode_output = functools.partial(decode_for_output, translation_map=TRANSLATION_MAP)
class DummySpinner(object):
def __init__(self, text="", **kwargs):
super(DummySpinner, self).__init__()
if DISABLE_COLORS:
colorama.init()
from .misc import decode_for_output
self.text = to_native_string(decode_for_output(text)) if text else ""
self.text = to_native_string(decode_output(text)) if text else ""
self.stdout = kwargs.get("stdout", sys.stdout)
self.stderr = kwargs.get("stderr", sys.stderr)
self.out_buff = StringIO()
self.write_to_stdout = kwargs.get("write_to_stdout", False)
super(DummySpinner, self).__init__()
def __enter__(self):
if self.text and self.text != "None":
@@ -50,11 +82,11 @@ class DummySpinner(object):
self.write(self.text)
return self
def __exit__(self, exc_type, exc_val, traceback):
def __exit__(self, exc_type, exc_val, tb):
if exc_type:
import traceback
from .misc import decode_for_output
self.write_err(decode_for_output(traceback.format_exception(*sys.exc_info())))
formatted_tb = traceback.format_exception(exc_type, exc_val, tb)
self.write_err("".join(formatted_tb))
self._close_output_buffer()
return False
@@ -76,56 +108,63 @@ class DummySpinner(object):
pass
def fail(self, exitcode=1, text="FAIL"):
from .misc import decode_for_output
if text and text != "None":
if text is not None and text != "None":
if self.write_to_stdout:
self.write(decode_for_output(text))
self.write(text)
else:
self.write_err(decode_for_output(text))
self.write_err(text)
self._close_output_buffer()
def ok(self, text="OK"):
if text and text != "None":
if text is not None and text != "None":
if self.write_to_stdout:
self.stdout.write(self.text)
self.write(text)
else:
self.stderr.write(self.text)
self.write_err(text)
self._close_output_buffer()
return 0
def hide_and_write(self, text, target=None):
if not target:
target = self.stdout
from .misc import decode_for_output
if text is None or isinstance(text, six.string_types) and text == "None":
pass
target.write(decode_for_output("\r"))
target.write(decode_output("\r", target_stream=target))
self._hide_cursor(target=target)
target.write(decode_for_output("{0}\n".format(text)))
target.write(decode_output("{0}\n".format(text), target_stream=target))
target.write(CLEAR_LINE)
self._show_cursor(target=target)
def write(self, text=None):
if not self.write_to_stdout:
return self.write_err(text)
from .misc import decode_for_output
if text is None or isinstance(text, six.string_types) and text == "None":
pass
text = decode_for_output(text)
self.stdout.write(decode_for_output("\r"))
line = decode_for_output("{0}\n".format(text))
self.stdout.write(line)
self.stdout.write(CLEAR_LINE)
if not self.stdout.closed:
stdout = self.stdout
else:
stdout = sys.stdout
text = decode_output(text, target_stream=stdout)
stdout.write(decode_output("\r", target_stream=stdout))
line = decode_output("{0}\n".format(text), target_stream=stdout)
stdout.write(line)
stdout.write(CLEAR_LINE)
def write_err(self, text=None):
from .misc import decode_for_output
if text is None or isinstance(text, six.string_types) and text == "None":
pass
text = decode_for_output(text)
self.stderr.write(decode_for_output("\r"))
line = decode_for_output("{0}\n".format(text))
self.stderr.write(line)
self.stderr.write(CLEAR_LINE)
if not self.stderr.closed:
stderr = self.stderr
else:
if sys.stderr.closed:
print(text)
return
stderr = sys.stderr
text = decode_output(text, target_stream=stderr)
stderr.write(decode_output("\r", target_stream=stderr))
line = decode_output("{0}\n".format(text), target_stream=stderr)
stderr.write(line)
stderr.write(CLEAR_LINE)
@staticmethod
def _hide_cursor(target=None):
@@ -136,10 +175,11 @@ class DummySpinner(object):
pass
base_obj = yaspin.core.Yaspin if yaspin is not None else DummySpinner
if SpinBase is None:
SpinBase = DummySpinner
class VistirSpinner(base_obj):
class VistirSpinner(SpinBase):
"A spinner class for handling spinners on windows and posix."
def __init__(self, *args, **kwargs):
@@ -182,6 +222,8 @@ class VistirSpinner(base_obj):
self.write_to_stdout = write_to_stdout
self.is_dummy = bool(yaspin is None)
super(VistirSpinner, self).__init__(*args, **kwargs)
if DISABLE_COLORS:
colorama.deinit()
def ok(self, text="OK", err=False):
"""Set Ok (success) finalizer to a spinner."""
@@ -204,38 +246,40 @@ class VistirSpinner(base_obj):
def hide_and_write(self, text, target=None):
if not target:
target = self.stdout
from .misc import decode_for_output
if text is None or isinstance(text, six.string_types) and text == "None":
pass
target.write(decode_for_output("\r"))
target.write(decode_output("\r"))
self._hide_cursor(target=target)
target.write(decode_for_output("{0}\n".format(text)))
target.write(decode_output("{0}\n".format(text)))
target.write(CLEAR_LINE)
self._show_cursor(target=target)
def write(self, text):
if not self.write_to_stdout:
return self.write_err(text)
from .misc import to_text
sys.stdout.write("\r")
self.stdout.write(CLEAR_LINE)
stdout = self.stdout
if self.stdout.closed:
stdout = sys.stdout
stdout.write(decode_output("\r", target_stream=stdout))
stdout.write(decode_output(CLEAR_LINE, target_stream=stdout))
if text is None:
text = ""
text = to_native_string("{0}\n".format(text))
self.stdout.write(text)
self.out_buff.write(to_text(text))
text = decode_output("{0}\n".format(text), target_stream=stdout)
stdout.write(text)
self.out_buff.write(decode_output(text, target_stream=self.out_buff))
def write_err(self, text):
"""Write error text in the terminal without breaking the spinner."""
from .misc import to_text
self.stderr.write("\r")
self.stderr.write(CLEAR_LINE)
stderr = self.stderr
if self.stderr.closed:
stderr = sys.stderr
stderr.write(decode_output("\r", target_stream=stderr))
stderr.write(decode_output(CLEAR_LINE, target_stream=stderr))
if text is None:
text = ""
text = to_native_string("{0}\n".format(text))
text = decode_output("{0}\n".format(text), target_stream=stderr)
self.stderr.write(text)
self.out_buff.write(to_text(text))
self.out_buff.write(decode_output(text, target_stream=self.out_buff))
def start(self):
if self._sigmap:
@@ -270,26 +314,22 @@ class VistirSpinner(base_obj):
if target.isatty():
self._show_cursor(target=target)
if self.stderr and self.stderr != sys.stderr:
self.stderr.close()
if self.stdout and self.stdout != sys.stdout:
self.stdout.close()
self.out_buff.close()
def _freeze(self, final_text, err=False):
"""Stop spinner, compose last frame and 'freeze' it."""
if not final_text:
final_text = ""
text = to_native_string(final_text)
target = self.stderr if err else self.stdout
if target.closed:
target = sys.stderr if err else sys.stdout
text = decode_output(final_text, target_stream=target)
self._last_frame = self._compose_out(text, mode="last")
# Should be stopped here, otherwise prints after
# self._freeze call will mess up the spinner
self.stop()
if err or not self.write_to_stdout:
self.stderr.write(self._last_frame)
else:
self.stdout.write(self._last_frame)
target.write(self._last_frame)
def _compose_color_func(self):
fn = functools.partial(
@@ -303,19 +343,19 @@ class VistirSpinner(base_obj):
def _compose_out(self, frame, mode=None):
# Ensure Unicode input
frame = to_native_string(frame)
frame = decode_output(frame)
if self._text is None:
self._text = ""
text = to_native_string(self._text)
text = decode_output(self._text)
if self._color_func is not None:
frame = self._color_func(frame)
if self._side == "right":
frame, text = text, frame
# Mode
if not mode:
out = to_native_string("\r{0} {1}".format(frame, text))
out = decode_output("\r{0} {1}".format(frame, text))
else:
out = to_native_string("{0} {1}\n".format(frame, text))
out = decode_output("{0} {1}\n".format(frame, text))
return out
def _spin(self):
+26 -14
View File
@@ -6,11 +6,11 @@ import warnings
import pytest
from vistir.compat import ResourceWarning, fs_str
from vistir.contextmanagers import temp_environ
from vistir.path import mkdir_p
from pipenv._compat import Path, TemporaryDirectory
from pipenv.exceptions import VirtualenvActivationException
from pipenv.utils import temp_environ
from pipenv.vendor import delegator, requests, toml, tomlkit
from pytest_pypi.app import prepare_fixtures
from pytest_pypi.app import prepare_packages as prepare_pypi_packages
@@ -187,14 +187,21 @@ class _Pipfile(object):
class _PipenvInstance(object):
"""An instance of a Pipenv Project..."""
def __init__(self, pypi=None, pipfile=True, chdir=False, path=None, home_dir=None):
def __init__(
self, pypi=None, pipfile=True, chdir=False, path=None, home_dir=None,
venv_root=None, ignore_virtualenvs=True, venv_in_project=True
):
self.pypi = pypi
self.original_umask = os.umask(0o007)
if ignore_virtualenvs:
os.environ["PIPENV_IGNORE_VIRTUALENVS"] = fs_str("1")
if venv_root:
os.environ["VIRTUAL_ENV"] = venv_root
if venv_in_project:
os.environ["PIPENV_VENV_IN_PROJECT"] = fs_str("1")
else:
os.environ.pop("PIPENV_VENV_IN_PROJECT", None)
self.original_dir = os.path.abspath(os.curdir)
os.environ["PIPENV_NOSPIN"] = fs_str("1")
os.environ["CI"] = fs_str("1")
warnings.simplefilter("ignore", category=ResourceWarning)
warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")
path = path if path else os.environ.get("PIPENV_PROJECT_DIR", None)
if not path:
self._path = TemporaryDirectory(suffix='-project', prefix='pipenv-')
@@ -224,10 +231,6 @@ class _PipenvInstance(object):
self._pipfile = _Pipfile(Path(p_path))
def __enter__(self):
os.environ['PIPENV_DONT_USE_PYENV'] = fs_str('1')
os.environ['PIPENV_IGNORE_VIRTUALENVS'] = fs_str('1')
os.environ['PIPENV_VENV_IN_PROJECT'] = fs_str('1')
os.environ['PIPENV_NOSPIN'] = fs_str('1')
if self.chdir:
os.chdir(self.path)
return self
@@ -243,7 +246,6 @@ class _PipenvInstance(object):
except OSError as e:
_warn_msg = warn_msg.format(e)
warnings.warn(_warn_msg, ResourceWarning)
os.umask(self.original_umask)
def pipenv(self, cmd, block=True):
if self.pipfile_path and os.path.isfile(self.pipfile_path):
@@ -290,8 +292,18 @@ class _PipenvInstance(object):
@pytest.fixture()
def PipenvInstance():
yield _PipenvInstance
with temp_environ():
original_umask = os.umask(0o007)
os.environ["PIPENV_NOSPIN"] = fs_str("1")
os.environ["CI"] = fs_str("1")
os.environ['PIPENV_DONT_USE_PYENV'] = fs_str('1')
os.environ['PIPENV_NOSPIN'] = fs_str('1')
warnings.simplefilter("ignore", category=ResourceWarning)
warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")
try:
yield _PipenvInstance
finally:
os.umask(original_umask)
@pytest.fixture(autouse=True)
def pip_src_dir(request, pathlib_tmpdir):
+26 -9
View File
@@ -8,6 +8,7 @@ import pytest
from pipenv.patched import pipfile
from pipenv.project import Project
from pipenv.utils import temp_environ
import pipenv.environments
@pytest.mark.project
@@ -168,25 +169,41 @@ def test_include_editable_packages(PipenvInstance, pypi, testsroot, pathlib_tmpd
@pytest.mark.project
@pytest.mark.virtualenv
def test_run_in_virtualenv(PipenvInstance, pypi, virtualenv):
with PipenvInstance(chdir=True, pypi=pypi) as p:
os.environ['PIPENV_IGNORE_VIRTUALENVS'] = '1'
def test_run_in_virtualenv_with_global_context(PipenvInstance, pypi, virtualenv):
with PipenvInstance(chdir=True, pypi=pypi, venv_root=virtualenv.as_posix(), ignore_virtualenvs=False, venv_in_project=False) as p:
c = p.pipenv('run which pip')
assert c.return_code == 0
assert 'virtualenv' not in c.out
os.environ.pop("PIPENV_IGNORE_VIRTUALENVS", None)
c = p.pipenv('run which pip')
assert c.return_code == 0
assert 'virtualenv' in c.out
assert 'Creating a virtualenv' not in c.err
project = Project()
assert project.virtualenv_location == str(virtualenv)
c = p.pipenv("run pip install click")
assert c.return_code == 0
assert "Courtesy Notice" in c.err
c = p.pipenv("install six")
assert c.return_code == 0
c = p.pipenv('run python -c "import click;print(click.__file__)"')
assert c.return_code == 0
assert c.out.strip().startswith(str(virtualenv))
c = p.pipenv("clean --dry-run")
assert c.return_code == 0
assert "click" in c.out
@pytest.mark.project
@pytest.mark.virtualenv
def test_run_in_virtualenv(PipenvInstance, pypi):
with PipenvInstance(chdir=True, pypi=pypi) as p:
c = p.pipenv('run which pip')
assert c.return_code == 0
assert 'Creating a virtualenv' in c.err
project = Project()
c = p.pipenv("run pip install click")
assert c.return_code == 0
c = p.pipenv("install six")
assert c.return_code == 0
c = p.pipenv('run python -c "import click;print(click.__file__)"')
assert c.return_code == 0
assert c.out.strip().startswith(str(project.virtualenv_location))
c = p.pipenv("clean --dry-run")
assert c.return_code == 0
assert "click" in c.out