Update all vendored and patched libraries

- Update pipfile and lockfile
- Update licenses
- Bump pathlib2 to latest version
- Include scandir without compiled library dependency
- Update vendoring script to remote scandir binaries going forward
- Update pinned dependencies in vendor.txt

Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
Dan Ryan
2018-06-16 00:57:24 -04:00
parent 69f75e980c
commit 9d9824c30d
128 changed files with 5383 additions and 2231 deletions
+2 -1
View File
@@ -12,8 +12,9 @@ pytest-pypy = {path = "./tests/pytest-pypi", editable = true}
pytest-tap = "*"
flaky = "*"
stdeb = {version="*", markers="sys_platform == 'linux'"}
white = {version="*", markers="python_version >= '3.6'"}
black = {version="*", markers="python_version >= '3.6'"}
pytz = "*"
towncrier = "*"
[packages]
Generated
+103 -104
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "65984957a775e890014e30415431347075837fb78fc5940451d3fb4f4eae475c"
"sha256": "c62a2508eed06b8d1e3d8328bcf6f985a0436f1f5026b48121fe31f01de357b0"
},
"pipfile-spec": 6,
"requires": {},
@@ -29,27 +29,42 @@
],
"version": "==1.4"
},
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
],
"version": "==1.4.3"
},
"atomicwrites": {
"hashes": [
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
"sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6"
],
"version": "==1.1.5"
},
"attrs": {
"hashes": [
"sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9",
"sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"
"sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
"sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
],
"version": "==17.4.0"
"version": "==18.1.0"
},
"babel": {
"hashes": [
"sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14",
"sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80"
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
],
"version": "==2.5.3"
"version": "==2.6.0"
},
"black": {
"hashes": [
"sha256:0461c7a52b5beb378936bf642753dec7a45305c96c6129d540b9c53227121a5a",
"sha256:7183263650ba3071034e90b40a1ea74abccbd32cf525cef6d7914479dbe7f2fb"
"sha256:3efe92eafbde15f8ac06478de11cfb84e47504896ccdde64507e751d2f91ec3a",
"sha256:fc26c4ab28c541fb824f59fa83d5702f75829495d5a1dee603b29bc4fbe79095"
],
"markers": "python_version >= '3.5'",
"version": "==18.3a0"
"index": "pypi",
"markers": "python_version >= '3.6'",
"version": "==18.6b2"
},
"certifi": {
"hashes": [
@@ -81,13 +96,6 @@
"markers": "sys_platform == 'win32'",
"version": "==0.3.9"
},
"configparser": {
"hashes": [
"sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a"
],
"markers": "python_version < '3.2'",
"version": "==3.5.0"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
@@ -96,16 +104,6 @@
],
"version": "==0.14"
},
"enum34": {
"hashes": [
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
"sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
],
"markers": "python_version < '3.4'",
"version": "==1.1.6"
},
"execnet": {
"hashes": [
"sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a",
@@ -131,25 +129,17 @@
},
"flask": {
"hashes": [
"sha256:7fab1062d11dd0038434e790d18c5b9133fd9e6b7257d707c4578ccc1e38b67c",
"sha256:b1883637bbee4dc7bc98d900792d0a304d609fce0f5bd9ca91d1b6457e5918dd"
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
],
"version": "==1.0"
},
"funcsigs": {
"hashes": [
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
],
"markers": "python_version < '3.0'",
"version": "==1.0.2"
},
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.6"
"version": "==2.7"
},
"imagesize": {
"hashes": [
@@ -158,6 +148,13 @@
],
"version": "==1.0.0"
},
"incremental": {
"hashes": [
"sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f",
"sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3"
],
"version": "==17.5.0"
},
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
@@ -194,25 +191,25 @@
},
"more-itertools": {
"hashes": [
"sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea",
"sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e",
"sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"
"sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
"sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3",
"sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"
],
"version": "==4.1.0"
"version": "==4.2.0"
},
"pathlib2": {
"packaging": {
"hashes": [
"sha256:24e0b33e1333b55e73c9d1e9a8342417d519f7789a9d3b440f4acd00ea45157e",
"sha256:deb3a960c1d55868dfbcac98432358b92ba89d95029cddd4040db1f27405055c"
"sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
"sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
],
"version": "==2.1.0"
"version": "==17.1"
},
"pbr": {
"hashes": [
"sha256:4e8a0ed6a8705a26768f4c3da26026013b157821fe5f95881599556ea9d91c19",
"sha256:dae4aaa78eafcad10ce2581fc34d694faa616727837fd8e55c1a00951ad6744f"
"sha256:3747c6f017f2dc099986c325239661948f9f5176f6880d9fdef164cb664cd665",
"sha256:a9c27eb8f0e24e786e544b2dbaedb729c9d8546342b5a6818d8eda098ad4340d"
],
"version": "==4.0.2"
"version": "==4.0.4"
},
"pipenv": {
"editable": true,
@@ -242,8 +239,6 @@
},
"pycodestyle": {
"hashes": [
"sha256:1ec08a51c901dfe44921576ed6e4c1f5b7ecbad403f871397feedb5eb8e4fa14",
"sha256:5ff2fbcbab997895ba9ead77e1b38b3ebc2e5c3b8a6194ef918666e4c790a00e",
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
],
@@ -263,13 +258,25 @@
],
"version": "==2.2.0"
},
"pyparsing": {
"hashes": [
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
"sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
"sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
"sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
"sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
"sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58",
"sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
],
"version": "==2.2.0"
},
"pytest": {
"hashes": [
"sha256:54713b26c97538db6ff0703a12b19aeaeb60b5e599de542e7fca0ec83b9038e8",
"sha256:829230122facf05a5f81a6d4dfe6454a04978ea3746853b2b84567ecf8e5c526"
"sha256:26838b2bc58620e01675485491504c3aa7ee0faf335c37fcd5f8731ca4319591",
"sha256:32c49a69566aa7c333188149ad48b58ac11a426d5352ea3d8f6ce843f88199cb"
],
"index": "pypi",
"version": "==3.5.1"
"version": "==3.6.1"
},
"pytest-forked": {
"hashes": [
@@ -300,24 +307,18 @@
},
"pytz": {
"hashes": [
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0",
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda"
"sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
"sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
],
"version": "==2018.3"
"index": "pypi",
"version": "==2018.4"
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
],
"version": "==2.18.4"
"version": "==2.19.1"
},
"requests-toolbelt": {
"hashes": [
@@ -350,10 +351,17 @@
},
"sphinx-click": {
"hashes": [
"sha256:0eef2d55ee4b5ebc448d8aa52e6084b5085fd2e7852d7571abb090411217c4df",
"sha256:c2680d84e8608bf47141e16924f1a981c653caff0faefe47a1d41c1438fbd5b4"
"sha256:2d1ead3335ee43c4959ed1dd2b29156de1e2709b6d45ba42497fcd8d334c9c2c",
"sha256:b3973152d29fb86f0196e47878c96e2b8b1edbcc854268f3f82847277e5182cf"
],
"index": "pypi",
"version": "==1.2.0"
},
"sphinxcontrib-websupport": {
"hashes": [
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
],
"version": "==1.1.0"
},
"stdeb": {
@@ -366,17 +374,31 @@
},
"tap.py": {
"hashes": [
"sha256:34e067d41988ce6d015c71d67f0b3025917f9a37dbc9b47aba5717a64a72b0f2",
"sha256:af320cef616d27922e0d3db3d729edecc93fdcb17c2fdc26b3e3d3dafe4d72c7"
"sha256:03accd27118473475b33b44703b223df2f148679b9b01b6ac59866df0b580073",
"sha256:06416d376f0d398ab163674f30ea3b4a320957e4baa51793b8e86bdfdfeb857d"
],
"version": "==2.2"
"version": "==2.4"
},
"toml": {
"hashes": [
"sha256:8e86bd6ce8cc11b9620cb637466453d94f5d57ad86f17e98a98d1f73e3baab2d"
],
"version": "==0.9.4"
},
"towncrier": {
"hashes": [
"sha256:2b6da2c90c0f053232775d96e816687bf607062b9b2947f625f5586c357cbd59",
"sha256:4643dafdde7d503cd6990dfba8043faa9f920fc4af0c2e69eb25488760534ac0"
],
"index": "pypi",
"version": "==18.5.0"
},
"tqdm": {
"hashes": [
"sha256:22c760d05b2eb6e96a91ad7ed1b03c9c97e6256fb7716410c27c2cb3265a5913",
"sha256:d7bfa112a55290a5e2d0c3263a09b17b24240686fbf20d1ef7c5e8edfbb71ad0"
"sha256:224291ee0d8c52d91b037fd90806f48c79bcd9994d3b0abc9e44b946a908fccd",
"sha256:77b8424d41b31e68f437c6dd9cd567aebc9a860507cb42fbd880a5f822d966fe"
],
"version": "==4.23.1"
"version": "==4.23.4"
},
"twine": {
"hashes": [
@@ -388,24 +410,10 @@
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
],
"version": "==1.22"
},
"virtualenv": {
"hashes": [
"sha256:1d7e241b431e7afce47e77f8843a276f652699d1fa4f93b9d8ce0076fd7b0b54",
"sha256:e8e05d4714a1c51a2f5921e62f547fcb0f713ebbe959e0a7f585cc8bef71d11f"
],
"version": "==15.2.0"
},
"virtualenv-clone": {
"hashes": [
"sha256:4507071d81013fd03ea9930ec26bc8648b997927a11fa80e8ee81198b57e0ac7",
"sha256:b5cfe535d14dc68dfc1d1bb4ac1209ea28235b91156e2bba8e250d291c3fb4f8"
],
"version": "==0.3.0"
"version": "==1.23"
},
"werkzeug": {
"hashes": [
@@ -413,15 +421,6 @@
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
],
"version": "==0.14.1"
},
"white": {
"hashes": [
"sha256:45e2c7f54de1facc60bf0a726b480cdc43422aad57c3a0bc5ba54cb536696683",
"sha256:bca98066256cfff6fb85ec36b95cc5913c888c170a8407c340786972b06c6f8f"
],
"index": "pypi",
"markers": "python_version >= '3.6'",
"version": "==0.1.2"
}
}
}
@@ -154,7 +154,7 @@ def is_manylinux1_compatible():
pass
# Check glibc version. CentOS 5 uses glibc 2.5.
return pipenv.patched.notpip._internal.utils.glibc.have_compatible_glibc(2, 5)
return pip._internal.utils.glibc.have_compatible_glibc(2, 5)
def get_darwin_arches(major, minor, machine):
@@ -22,7 +22,7 @@ def do_import(module_path, subimport=None, old_path=None, vendored_name=None):
if subimport:
return getattr(_tmp, subimport, _tmp)
return _tmp
InstallRequirement = do_import('req.req_install', 'InstallRequirement', vendored_name='notpip')
parse_requirements = do_import('req.req_file', 'parse_requirements', vendored_name='notpip')
+8 -8
View File
@@ -22,10 +22,10 @@ from .._compat import (
InstallationError,
)
from notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
from notpip._vendor.packaging.version import Version, InvalidVersion, parse as parse_version
from notpip._vendor.packaging.specifiers import SpecifierSet
from notpip._vendor.pyparsing import ParseException
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pipenv.patched.notpip._vendor.packaging.version import Version, InvalidVersion, parse as parse_version
from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet
from pipenv.patched.notpip._vendor.pyparsing import ParseException
from ..cache import CACHE_DIR
from pipenv.environments import PIPENV_CACHE_DIR
@@ -37,15 +37,15 @@ from .base import BaseRepository
try:
from notpip._internal.operations.prepare import RequirementPreparer
from notpip._internal.resolve import Resolver as PipResolver
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
from pipenv.patched.notpip._internal.resolve import Resolver as PipResolver
except ImportError:
pass
try:
from notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.cache import WheelCache
except ImportError:
from notpip.wheel import WheelCache
from pipenv.patched.notpip.wheel import WheelCache
class HashCache(SafeFileCache):
+2 -2
View File
@@ -8,7 +8,7 @@ from itertools import chain, count
import os
from first import first
from notpip._vendor.packaging.markers import default_environment
from pipenv.patched.notpip._vendor.packaging.markers import default_environment
from ._compat import InstallRequirement
from . import click
@@ -314,7 +314,7 @@ class Resolver(object):
dependency_strings = self.dependency_cache[ireq]
log.debug(' {:25} requires {}'.format(format_requirement(ireq),
', '.join(sorted(dependency_strings, key=lambda s: s.lower())) or '-'))
from notpip._vendor.packaging.markers import InvalidMarker
from pipenv.patched.notpip._vendor.packaging.markers import InvalidMarker
for dependency_string in dependency_strings:
try:
_dependency_string = dependency_string
+4 -2
View File
@@ -6,11 +6,12 @@ from . import converters, exceptions, filters, validators
from ._config import get_run_validators, set_run_validators
from ._funcs import asdict, assoc, astuple, evolve, has
from ._make import (
NOTHING, Attribute, Factory, attrib, attrs, fields, make_class, validate
NOTHING, Attribute, Factory, attrib, attrs, fields, fields_dict,
make_class, validate
)
__version__ = "17.4.0"
__version__ = "18.1.0"
__title__ = "attrs"
__description__ = "Classes Without Boilerplate"
@@ -43,6 +44,7 @@ __all__ = [
"evolve",
"exceptions",
"fields",
"fields_dict",
"filters",
"get_run_validators",
"has",
+16 -9
View File
@@ -10,6 +10,13 @@ PY2 = sys.version_info[0] == 2
PYPY = platform.python_implementation() == "PyPy"
if PYPY or sys.version_info[:2] >= (3, 6):
ordered_dict = dict
else:
from collections import OrderedDict
ordered_dict = OrderedDict
if PY2:
from UserDict import IterableUserDict
@@ -87,15 +94,12 @@ else:
return types.MappingProxyType(dict(d))
def import_ctypes(): # pragma: nocover
def import_ctypes():
"""
Moved into a function for testability.
"""
try:
import ctypes
return ctypes
except ImportError:
return None
import ctypes
return ctypes
if not PY2:
@@ -126,12 +130,15 @@ def make_set_closure_cell():
def set_closure_cell(cell, value):
cell.__setstate__((value,))
else:
ctypes = import_ctypes()
if ctypes is not None:
try:
ctypes = import_ctypes()
set_closure_cell = ctypes.pythonapi.PyCell_Set
set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
set_closure_cell.restype = ctypes.c_int
else:
except Exception:
# We try best effort to set the cell, but sometimes it's not
# possible. For example on Jython or on GAE.
set_closure_cell = just_warn
return set_closure_cell
+245 -67
View File
@@ -3,12 +3,15 @@ from __future__ import absolute_import, division, print_function
import hashlib
import linecache
import sys
import threading
import warnings
from operator import itemgetter
from . import _config
from ._compat import PY2, isclass, iteritems, metadata_proxy, set_closure_cell
from ._compat import (
PY2, isclass, iteritems, metadata_proxy, ordered_dict, set_closure_cell
)
from .exceptions import (
DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError,
UnannotatedAttributeError
@@ -20,6 +23,8 @@ _obj_setattr = object.__setattr__
_init_converter_pat = "__attr_converter_{}"
_init_factory_pat = "__attr_factory_{}"
_tuple_property_pat = " {attr_name} = property(itemgetter({index}))"
_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar")
_empty_metadata_singleton = metadata_proxy({})
@@ -45,7 +50,7 @@ class _Nothing(object):
return "NOTHING"
def __hash__(self):
return 0xdeadbeef
return 0xc0ffee
NOTHING = _Nothing()
@@ -56,7 +61,8 @@ Sentinel to indicate the lack of a value when ``None`` is ambiguous.
def attrib(default=NOTHING, validator=None,
repr=True, cmp=True, hash=None, init=True,
convert=None, metadata=None, type=None, converter=None):
convert=None, metadata=None, type=None, converter=None,
factory=None):
"""
Create a new attribute on a class.
@@ -81,6 +87,9 @@ def attrib(default=NOTHING, validator=None,
:type default: Any value.
:param callable factory: Syntactic sugar for
``default=attr.Factory(callable)``.
:param validator: :func:`callable` that is called by ``attrs``-generated
``__init__`` methods after the instance has been initialized. They
receive the initialized instance, the :class:`Attribute`, and the
@@ -97,7 +106,7 @@ def attrib(default=NOTHING, validator=None,
The validator can also be set using decorator notation as shown below.
:type validator: ``callable`` or a ``list`` of ``callable``\ s.
:type validator: ``callable`` or a ``list`` of ``callable``\\ s.
:param bool repr: Include this attribute in the generated ``__repr__``
method.
@@ -128,13 +137,15 @@ def attrib(default=NOTHING, validator=None,
.. versionadded:: 15.2.0 *convert*
.. versionadded:: 16.3.0 *metadata*
.. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
.. versionchanged:: 17.1.0
*hash* is ``None`` and therefore mirrors *cmp* by default.
.. versionadded:: 17.3.0 *type*
.. deprecated:: 17.4.0 *convert*
.. versionadded:: 17.4.0 *converter* as a replacement for the deprecated
*convert* to achieve consistency with other noun-based arguments.
.. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
.. versionchanged:: 17.1.0
*hash* is ``None`` and therefore mirrors *cmp* by default.
.. versionadded:: 17.3.0 *type*
.. deprecated:: 17.4.0 *convert*
.. versionadded:: 17.4.0 *converter* as a replacement for the deprecated
*convert* to achieve consistency with other noun-based arguments.
.. versionadded:: 18.1.0
``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
"""
if hash is not None and hash is not True and hash is not False:
raise TypeError(
@@ -154,6 +165,18 @@ def attrib(default=NOTHING, validator=None,
)
converter = convert
if factory is not None:
if default is not NOTHING:
raise ValueError(
"The `default` and `factory` arguments are mutually "
"exclusive."
)
if not callable(factory):
raise ValueError(
"The `factory` argument must be a callable."
)
default = Factory(factory)
if metadata is None:
metadata = {}
@@ -201,8 +224,9 @@ def _make_attr_tuple_class(cls_name, attr_names):
# Tuple class for extracted attributes from a class definition.
# `super_attrs` is a subset of `attrs`.
_Attributes = _make_attr_tuple_class("_Attributes", [
"attrs", # all attributes to build dunder methods for
"super_attrs", # attributes that have been inherited from super classes
"attrs", # all attributes to build dunder methods for
"super_attrs", # attributes that have been inherited
"super_attrs_map", # map inherited attributes to their originating classes
])
@@ -210,10 +234,11 @@ def _is_class_var(annot):
"""
Check whether *annot* is a typing.ClassVar.
The implementation is gross but importing `typing` is slow and there are
discussions to remove it from the stdlib alltogether.
The string comparison hack is used to avoid evaluating all string
annotations which would put attrs-based classes at a performance
disadvantage compared to plain old classes.
"""
return str(annot).startswith("typing.ClassVar")
return str(annot).startswith(_classvar_prefixes)
def _get_annotations(cls):
@@ -232,6 +257,13 @@ def _get_annotations(cls):
return anns
def _counter_getter(e):
"""
Key function for sorting to avoid re-creating a lambda for every class.
"""
return e[1].counter
def _transform_attrs(cls, these, auto_attribs):
"""
Transform all `_CountingAttr`s on a class into `Attribute`s.
@@ -244,11 +276,14 @@ def _transform_attrs(cls, these, auto_attribs):
anns = _get_annotations(cls)
if these is not None:
ca_list = sorted((
ca_list = [
(name, ca)
for name, ca
in iteritems(these)
), key=lambda e: e[1].counter)
]
if not isinstance(these, ordered_dict):
ca_list.sort(key=_counter_getter)
elif auto_attribs is True:
ca_names = {
name
@@ -298,6 +333,7 @@ def _transform_attrs(cls, these, auto_attribs):
]
super_attrs = []
super_attr_map = {} # A dictionary of superattrs to their classes.
taken_attr_names = {a.name: a for a in own_attrs}
# Traverse the MRO and collect attributes.
@@ -311,6 +347,7 @@ def _transform_attrs(cls, these, auto_attribs):
if prev_a is None:
super_attrs.append(a)
taken_attr_names[a.name] = a
super_attr_map[a.name] = super_cls
attr_names = [a.name for a in super_attrs + own_attrs]
@@ -341,7 +378,7 @@ def _transform_attrs(cls, these, auto_attribs):
a.init is not False:
had_default = True
return _Attributes((attrs, super_attrs))
return _Attributes((attrs, super_attrs, super_attr_map))
def _frozen_setattrs(self, name, value):
@@ -364,20 +401,24 @@ class _ClassBuilder(object):
"""
__slots__ = (
"_cls", "_cls_dict", "_attrs", "_super_names", "_attr_names", "_slots",
"_frozen", "_has_post_init",
"_frozen", "_has_post_init", "_delete_attribs", "_super_attr_map",
)
def __init__(self, cls, these, slots, frozen, auto_attribs):
attrs, super_attrs = _transform_attrs(cls, these, auto_attribs)
attrs, super_attrs, super_map = _transform_attrs(
cls, these, auto_attribs
)
self._cls = cls
self._cls_dict = dict(cls.__dict__) if slots else {}
self._attrs = attrs
self._super_names = set(a.name for a in super_attrs)
self._super_attr_map = super_map
self._attr_names = tuple(a.name for a in attrs)
self._slots = slots
self._frozen = frozen or _has_frozen_superclass(cls)
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
self._delete_attribs = not bool(these)
self._cls_dict["__attrs_attrs__"] = self._attrs
@@ -407,10 +448,11 @@ class _ClassBuilder(object):
super_names = self._super_names
# Clean class of attribute definitions (`attr.ib()`s).
for name in self._attr_names:
if name not in super_names and \
getattr(cls, name, None) is not None:
delattr(cls, name)
if self._delete_attribs:
for name in self._attr_names:
if name not in super_names and \
getattr(cls, name, None) is not None:
delattr(cls, name)
# Attach our dunder methods.
for name, value in self._cls_dict.items():
@@ -441,20 +483,26 @@ class _ClassBuilder(object):
if qualname is not None:
cd["__qualname__"] = qualname
attr_names = tuple(self._attr_names)
# __weakref__ is not writable.
state_attr_names = tuple(
an for an in self._attr_names if an != "__weakref__"
)
def slots_getstate(self):
"""
Automatically created by attrs.
"""
return tuple(getattr(self, name) for name in attr_names)
return tuple(
getattr(self, name)
for name in state_attr_names
)
def slots_setstate(self, state):
"""
Automatically created by attrs.
"""
__bound_setattr = _obj_setattr.__get__(self, Attribute)
for name, value in zip(attr_names, state):
for name, value in zip(state_attr_names, state):
__bound_setattr(name, value)
# slots and frozen require __getstate__/__setstate__ to work
@@ -526,6 +574,8 @@ class _ClassBuilder(object):
self._attrs,
self._has_post_init,
self._frozen,
self._slots,
self._super_attr_map,
)
)
@@ -575,7 +625,12 @@ def attrs(maybe_cls=None, these=None, repr_ns=None,
Django models) or don't want to.
If *these* is not ``None``, ``attrs`` will *not* search the class body
for attributes.
for attributes and will *not* remove any attributes from it.
If *these* is an ordered dict (:class:`dict` on Python 3.6+,
:class:`collections.OrderedDict` otherwise), the order is deduced from
the order of the attributes inside *these*. Otherwise the order
of the definition of the attributes is used.
:type these: :class:`dict` of :class:`str` to :func:`attr.ib`
@@ -656,13 +711,16 @@ def attrs(maybe_cls=None, these=None, repr_ns=None,
.. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/
.. versionadded:: 16.0.0 *slots*
.. versionadded:: 16.1.0 *frozen*
.. versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``.
.. versionchanged::
17.1.0 *hash* supports ``None`` as value which is also the default
now.
.. versionadded:: 16.0.0 *slots*
.. versionadded:: 16.1.0 *frozen*
.. versionadded:: 16.3.0 *str*
.. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
.. versionchanged:: 17.1.0
*hash* supports ``None`` as value which is also the default now.
.. versionadded:: 17.3.0 *auto_attribs*
.. versionchanged:: 18.1.0
If *these* is passed, no attributes are deleted from the class body.
.. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
"""
def wrap(cls):
if getattr(cls, "__class__", None) is None:
@@ -899,6 +957,9 @@ def _add_cmp(cls, attrs=None):
return cls
_already_repring = threading.local()
def _make_repr(attrs, ns):
"""
Make a repr method for *attr_names* adding *ns* to the full name.
@@ -913,6 +974,14 @@ def _make_repr(attrs, ns):
"""
Automatically created by attrs.
"""
try:
working_set = _already_repring.working_set
except AttributeError:
working_set = set()
_already_repring.working_set = working_set
if id(self) in working_set:
return "..."
real_cls = self.__class__
if ns is None:
qualname = getattr(real_cls, "__qualname__", None)
@@ -923,13 +992,23 @@ def _make_repr(attrs, ns):
else:
class_name = ns + "." + real_cls.__name__
return "{0}({1})".format(
class_name,
", ".join(
name + "=" + repr(getattr(self, name, NOTHING))
for name in attr_names
)
)
# Since 'self' remains on the stack (i.e.: strongly referenced) for the
# duration of this call, it's safe to depend on id(...) stability, and
# not need to track the instance and therefore worry about properties
# like weakref- or hash-ability.
working_set.add(id(self))
try:
result = [class_name, "("]
first = True
for name in attr_names:
if first:
first = False
else:
result.append(", ")
result.extend((name, "=", repr(getattr(self, name, NOTHING))))
return "".join(result) + ")"
finally:
working_set.remove(id(self))
return __repr__
@@ -944,7 +1023,7 @@ def _add_repr(cls, ns=None, attrs=None):
return cls
def _make_init(attrs, post_init, frozen):
def _make_init(attrs, post_init, frozen, slots, super_attr_map):
attrs = [
a
for a in attrs
@@ -958,10 +1037,12 @@ def _make_init(attrs, post_init, frozen):
sha1.hexdigest()
)
script, globs = _attrs_to_init_script(
script, globs, annotations = _attrs_to_init_script(
attrs,
frozen,
slots,
post_init,
super_attr_map,
)
locs = {}
bytecode = compile(script, unique_filename, "exec")
@@ -985,7 +1066,9 @@ def _make_init(attrs, post_init, frozen):
unique_filename,
)
return locs["__init__"]
__init__ = locs["__init__"]
__init__.__annotations__ = annotations
return __init__
def _add_init(cls, frozen):
@@ -996,13 +1079,15 @@ def _add_init(cls, frozen):
cls.__attrs_attrs__,
getattr(cls, "__attrs_post_init__", False),
frozen,
_is_slot_cls(cls),
{},
)
return cls
def fields(cls):
"""
Returns the tuple of ``attrs`` attributes for a class.
Return the tuple of ``attrs`` attributes for a class.
The tuple also allows accessing the fields by their names (see below for
examples).
@@ -1028,6 +1113,34 @@ def fields(cls):
return attrs
def fields_dict(cls):
"""
Return an ordered dictionary of ``attrs`` attributes for a class, whose
keys are the attribute names.
:param type cls: Class to introspect.
:raise TypeError: If *cls* is not a class.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
class.
:rtype: an ordered dict where keys are attribute names and values are
:class:`attr.Attribute`\\ s. This will be a :class:`dict` if it's
naturally ordered like on Python 3.6+ or an
:class:`~collections.OrderedDict` otherwise.
.. versionadded:: 18.1.0
"""
if not isclass(cls):
raise TypeError("Passed object must be a class.")
attrs = getattr(cls, "__attrs_attrs__", None)
if attrs is None:
raise NotAnAttrsClassError(
"{cls!r} is not an attrs-decorated class.".format(cls=cls)
)
return ordered_dict(((a.name, a) for a in attrs))
def validate(inst):
"""
Validate all attributes on *inst* that have a validator.
@@ -1045,37 +1158,92 @@ def validate(inst):
v(inst, a, getattr(inst, a.name))
def _attrs_to_init_script(attrs, frozen, post_init):
def _is_slot_cls(cls):
return "__slots__" in cls.__dict__
def _is_slot_attr(a_name, super_attr_map):
"""
Check if the attribute name comes from a slot class.
"""
return a_name in super_attr_map and _is_slot_cls(super_attr_map[a_name])
def _attrs_to_init_script(attrs, frozen, slots, post_init, super_attr_map):
"""
Return a script of an initializer for *attrs* and a dict of globals.
The globals are expected by the generated script.
If *frozen* is True, we cannot set the attributes directly so we use
If *frozen* is True, we cannot set the attributes directly so we use
a cached ``object.__setattr__``.
"""
lines = []
any_slot_ancestors = any(
_is_slot_attr(a.name, super_attr_map)
for a in attrs
)
if frozen is True:
lines.append(
# Circumvent the __setattr__ descriptor to save one lookup per
# assignment.
"_setattr = _cached_setattr.__get__(self, self.__class__)"
)
if slots is True:
lines.append(
# Circumvent the __setattr__ descriptor to save one lookup per
# assignment.
"_setattr = _cached_setattr.__get__(self, self.__class__)"
)
def fmt_setter(attr_name, value_var):
return "_setattr('%(attr_name)s', %(value_var)s)" % {
"attr_name": attr_name,
"value_var": value_var,
}
def fmt_setter(attr_name, value_var):
return "_setattr('%(attr_name)s', %(value_var)s)" % {
"attr_name": attr_name,
"value_var": value_var,
}
def fmt_setter_with_converter(attr_name, value_var):
conv_name = _init_converter_pat.format(attr_name)
return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
"attr_name": attr_name,
"value_var": value_var,
"conv": conv_name,
}
def fmt_setter_with_converter(attr_name, value_var):
conv_name = _init_converter_pat.format(attr_name)
return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
"attr_name": attr_name,
"value_var": value_var,
"conv": conv_name,
}
else:
# Dict frozen classes assign directly to __dict__.
# But only if the attribute doesn't come from an ancestor slot
# class.
lines.append(
"_inst_dict = self.__dict__"
)
if any_slot_ancestors:
lines.append(
# Circumvent the __setattr__ descriptor to save one lookup
# per assignment.
"_setattr = _cached_setattr.__get__(self, self.__class__)"
)
def fmt_setter(attr_name, value_var):
if _is_slot_attr(attr_name, super_attr_map):
res = "_setattr('%(attr_name)s', %(value_var)s)" % {
"attr_name": attr_name,
"value_var": value_var,
}
else:
res = "_inst_dict['%(attr_name)s'] = %(value_var)s" % {
"attr_name": attr_name,
"value_var": value_var,
}
return res
def fmt_setter_with_converter(attr_name, value_var):
conv_name = _init_converter_pat.format(attr_name)
if _is_slot_attr(attr_name, super_attr_map):
tmpl = "_setattr('%(attr_name)s', %(c)s(%(value_var)s))"
else:
tmpl = "_inst_dict['%(attr_name)s'] = %(c)s(%(value_var)s)"
return tmpl % {
"attr_name": attr_name,
"value_var": value_var,
"c": conv_name,
}
else:
# Not frozen.
def fmt_setter(attr_name, value):
return "self.%(attr_name)s = %(value)s" % {
"attr_name": attr_name,
@@ -1096,6 +1264,7 @@ def _attrs_to_init_script(attrs, frozen, post_init):
# This is a dictionary of names to validator and converter callables.
# Injecting this into __init__ globals lets us avoid lookups.
names_for_globals = {}
annotations = {'return': None}
for a in attrs:
if a.validator:
@@ -1186,6 +1355,9 @@ def _attrs_to_init_script(attrs, frozen, post_init):
else:
lines.append(fmt_setter(attr_name, arg_name))
if a.init is True and a.converter is None and a.type is not None:
annotations[arg_name] = a.type
if attrs_to_validate: # we can skip this if there are no validators.
names_for_globals["_config"] = _config
lines.append("if _config._run_validators is True:")
@@ -1205,7 +1377,7 @@ def __init__(self, {args}):
""".format(
args=", ".join(args),
lines="\n ".join(lines) if lines else "pass",
), names_for_globals
), names_for_globals, annotations
class Attribute(object):
@@ -1436,6 +1608,11 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
:param attrs: A list of names or a dictionary of mappings of names to
attributes.
If *attrs* is a list or an ordered dict (:class:`dict` on Python 3.6+,
:class:`collections.OrderedDict` otherwise), the order is deduced from
the order of the names or attributes inside *attrs*. Otherwise the
order of the definition of the attributes is used.
:type attrs: :class:`list` or :class:`dict`
:param tuple bases: Classes that the new class will subclass.
@@ -1445,7 +1622,8 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
:return: A new class with *attrs*.
:rtype: type
.. versionadded:: 17.1.0 *bases*
.. versionadded:: 17.1.0 *bases*
.. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
"""
if isinstance(attrs, dict):
cls_dict = attrs
+2 -2
View File
@@ -23,7 +23,7 @@ def include(*what):
Whitelist *what*.
:param what: What to whitelist.
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\\ s
:rtype: :class:`callable`
"""
@@ -40,7 +40,7 @@ def exclude(*what):
Blacklist *what*.
:param what: What to blacklist.
:type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
:type what: :class:`list` of classes or :class:`attr.Attribute`\\ s.
:rtype: :class:`callable`
"""
+1 -1
View File
@@ -1,3 +1,3 @@
from .core import where, old_where
__version__ = "2018.01.18"
__version__ = "2018.04.16"
-33
View File
@@ -3483,39 +3483,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----
# Issuer: CN=T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5 O=T\xdcRKTRUST Bilgi \u0130leti\u015fim ve Bili\u015fim G\xfcvenli\u011fi Hizmetleri A.\u015e.
# Subject: CN=T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5 O=T\xdcRKTRUST Bilgi \u0130leti\u015fim ve Bili\u015fim G\xfcvenli\u011fi Hizmetleri A.\u015e.
# Label: "T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5"
# Serial: 156233699172481
# MD5 Fingerprint: da:70:8e:f0:22:df:93:26:f6:5f:9f:d3:15:06:52:4e
# SHA1 Fingerprint: c4:18:f6:4d:46:d1:df:00:3d:27:30:13:72:43:a9:12:11:c6:75:fb
# SHA256 Fingerprint: 49:35:1b:90:34:44:c1:85:cc:dc:5c:69:3d:24:d8:55:5c:b2:08:d6:a8:14:13:07:69:9f:4a:f0:63:19:9d:78
-----BEGIN CERTIFICATE-----
MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE
BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn
aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg
QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg
SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0
MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD
VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom
/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR
Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3
4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z
5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0
hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID
AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX
SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l
VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq
URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf
peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF
Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW
+qtB4Uu2NQvAmxU=
-----END CERTIFICATE-----
# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903
# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903
# Label: "Certinomis - Root CA"
+1 -1
View File
@@ -1,7 +1,7 @@
License
-------
Copyright (c) 2013-2017, Kim Davies. All rights reserved.
Copyright (c) 2013-2018, Kim Davies. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Vendored Regular → Executable
View File
Vendored Regular → Executable
View File
Vendored Regular → Executable
View File
Vendored Regular → Executable
+26 -14
View File
@@ -34,7 +34,11 @@ class InvalidCodepointContext(IDNAError):
def _combining_class(cp):
return unicodedata.combining(unichr(cp))
v = unicodedata.combining(unichr(cp))
if v == 0:
if not unicodedata.name(unichr(cp)):
raise ValueError("Unknown character in unicodedata")
return v
def _is_script(cp, script):
return intranges_contain(ord(cp), idnadata.scripts[script])
@@ -71,7 +75,6 @@ def check_bidi(label, check_ltr=False):
raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx))
if direction in ['R', 'AL', 'AN']:
bidi_label = True
break
if not bidi_label and not check_ltr:
return True
@@ -244,8 +247,13 @@ def check_label(label):
if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']):
continue
elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']):
if not valid_contextj(label, pos):
raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label)))
try:
if not valid_contextj(label, pos):
raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format(
_unot(cp_value), pos+1, repr(label)))
except ValueError:
raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format(
_unot(cp_value), pos+1, repr(label)))
elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']):
if not valid_contexto(label, pos):
raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label)))
@@ -317,10 +325,10 @@ def uts46_remap(domain, std3_rules=True, transitional=False):
replacement = uts46row[2] if len(uts46row) == 3 else None
if (status == "V" or
(status == "D" and not transitional) or
(status == "3" and std3_rules and replacement is None)):
(status == "3" and not std3_rules and replacement is None)):
output += char
elif replacement is not None and (status == "M" or
(status == "3" and std3_rules) or
(status == "3" and not std3_rules) or
(status == "D" and transitional)):
output += replacement
elif status != "I":
@@ -344,15 +352,17 @@ def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False):
labels = s.split('.')
else:
labels = _unicode_dots_re.split(s)
while labels and not labels[0]:
del labels[0]
if not labels:
if not labels or labels == ['']:
raise IDNAError('Empty domain')
if labels[-1] == '':
del labels[-1]
trailing_dot = True
for label in labels:
result.append(alabel(label))
s = alabel(label)
if s:
result.append(s)
else:
raise IDNAError('Empty label')
if trailing_dot:
result.append(b'')
s = b'.'.join(result)
@@ -373,15 +383,17 @@ def decode(s, strict=False, uts46=False, std3_rules=False):
labels = _unicode_dots_re.split(s)
else:
labels = s.split(u'.')
while labels and not labels[0]:
del labels[0]
if not labels:
if not labels or labels == ['']:
raise IDNAError('Empty domain')
if not labels[-1]:
del labels[-1]
trailing_dot = True
for label in labels:
result.append(ulabel(label))
s = ulabel(label)
if s:
result.append(s)
else:
raise IDNAError('Empty label')
if trailing_dot:
result.append(u'')
return u'.'.join(result)
Vendored Regular → Executable
+353 -45
View File
@@ -1,11 +1,12 @@
# This file is automatically generated by tools/idna-data
__version__ = "6.3.0"
__version__ = "10.0.0"
scripts = {
'Greek': (
0x37000000374,
0x37500000378,
0x37a0000037e,
0x37f00000380,
0x38400000385,
0x38600000387,
0x3880000038b,
@@ -34,7 +35,9 @@ scripts = {
0x1ff200001ff5,
0x1ff600001fff,
0x212600002127,
0x101400001018b,
0xab650000ab66,
0x101400001018f,
0x101a0000101a1,
0x1d2000001d246,
),
'Han': (
@@ -46,12 +49,14 @@ scripts = {
0x30210000302a,
0x30380000303c,
0x340000004db6,
0x4e0000009fcd,
0x4e0000009feb,
0xf9000000fa6e,
0xfa700000fada,
0x200000002a6d7,
0x2a7000002b735,
0x2b7400002b81e,
0x2b8200002cea2,
0x2ceb00002ebe1,
0x2f8000002fa1e,
),
'Hebrew': (
@@ -68,7 +73,7 @@ scripts = {
'Hiragana': (
0x304100003097,
0x309d000030a0,
0x1b0010001b002,
0x1b0010001b11f,
0x1f2000001f201,
),
'Katakana': (
@@ -88,6 +93,7 @@ joining_types = {
0x602: 85,
0x603: 85,
0x604: 85,
0x605: 85,
0x608: 85,
0x60b: 85,
0x620: 68,
@@ -365,7 +371,7 @@ joining_types = {
0x844: 68,
0x845: 68,
0x846: 82,
0x847: 68,
0x847: 82,
0x848: 68,
0x849: 82,
0x84a: 68,
@@ -373,7 +379,7 @@ joining_types = {
0x84c: 68,
0x84d: 68,
0x84e: 68,
0x84f: 82,
0x84f: 68,
0x850: 68,
0x851: 68,
0x852: 68,
@@ -383,7 +389,19 @@ joining_types = {
0x856: 85,
0x857: 85,
0x858: 85,
0x860: 68,
0x861: 85,
0x862: 68,
0x863: 68,
0x864: 68,
0x865: 68,
0x866: 85,
0x867: 82,
0x868: 68,
0x869: 82,
0x86a: 82,
0x8a0: 68,
0x8a1: 68,
0x8a2: 68,
0x8a3: 68,
0x8a4: 68,
@@ -395,6 +413,23 @@ joining_types = {
0x8aa: 82,
0x8ab: 82,
0x8ac: 82,
0x8ad: 85,
0x8ae: 82,
0x8af: 68,
0x8b0: 68,
0x8b1: 82,
0x8b2: 82,
0x8b3: 68,
0x8b4: 68,
0x8b6: 68,
0x8b7: 68,
0x8b8: 68,
0x8b9: 82,
0x8ba: 68,
0x8bb: 68,
0x8bc: 68,
0x8bd: 68,
0x8e2: 85,
0x1806: 85,
0x1807: 68,
0x180a: 67,
@@ -492,8 +527,8 @@ joining_types = {
0x1882: 85,
0x1883: 85,
0x1884: 85,
0x1885: 85,
0x1886: 85,
0x1885: 84,
0x1886: 84,
0x1887: 68,
0x1888: 68,
0x1889: 68,
@@ -531,6 +566,7 @@ joining_types = {
0x18aa: 68,
0x200c: 85,
0x200d: 67,
0x202f: 85,
0x2066: 85,
0x2067: 85,
0x2068: 85,
@@ -587,6 +623,141 @@ joining_types = {
0xa871: 68,
0xa872: 76,
0xa873: 85,
0x10ac0: 68,
0x10ac1: 68,
0x10ac2: 68,
0x10ac3: 68,
0x10ac4: 68,
0x10ac5: 82,
0x10ac6: 85,
0x10ac7: 82,
0x10ac8: 85,
0x10ac9: 82,
0x10aca: 82,
0x10acb: 85,
0x10acc: 85,
0x10acd: 76,
0x10ace: 82,
0x10acf: 82,
0x10ad0: 82,
0x10ad1: 82,
0x10ad2: 82,
0x10ad3: 68,
0x10ad4: 68,
0x10ad5: 68,
0x10ad6: 68,
0x10ad7: 76,
0x10ad8: 68,
0x10ad9: 68,
0x10ada: 68,
0x10adb: 68,
0x10adc: 68,
0x10add: 82,
0x10ade: 68,
0x10adf: 68,
0x10ae0: 68,
0x10ae1: 82,
0x10ae2: 85,
0x10ae3: 85,
0x10ae4: 82,
0x10aeb: 68,
0x10aec: 68,
0x10aed: 68,
0x10aee: 68,
0x10aef: 82,
0x10b80: 68,
0x10b81: 82,
0x10b82: 68,
0x10b83: 82,
0x10b84: 82,
0x10b85: 82,
0x10b86: 68,
0x10b87: 68,
0x10b88: 68,
0x10b89: 82,
0x10b8a: 68,
0x10b8b: 68,
0x10b8c: 82,
0x10b8d: 68,
0x10b8e: 82,
0x10b8f: 82,
0x10b90: 68,
0x10b91: 82,
0x10ba9: 82,
0x10baa: 82,
0x10bab: 82,
0x10bac: 82,
0x10bad: 68,
0x10bae: 68,
0x10baf: 85,
0x1e900: 68,
0x1e901: 68,
0x1e902: 68,
0x1e903: 68,
0x1e904: 68,
0x1e905: 68,
0x1e906: 68,
0x1e907: 68,
0x1e908: 68,
0x1e909: 68,
0x1e90a: 68,
0x1e90b: 68,
0x1e90c: 68,
0x1e90d: 68,
0x1e90e: 68,
0x1e90f: 68,
0x1e910: 68,
0x1e911: 68,
0x1e912: 68,
0x1e913: 68,
0x1e914: 68,
0x1e915: 68,
0x1e916: 68,
0x1e917: 68,
0x1e918: 68,
0x1e919: 68,
0x1e91a: 68,
0x1e91b: 68,
0x1e91c: 68,
0x1e91d: 68,
0x1e91e: 68,
0x1e91f: 68,
0x1e920: 68,
0x1e921: 68,
0x1e922: 68,
0x1e923: 68,
0x1e924: 68,
0x1e925: 68,
0x1e926: 68,
0x1e927: 68,
0x1e928: 68,
0x1e929: 68,
0x1e92a: 68,
0x1e92b: 68,
0x1e92c: 68,
0x1e92d: 68,
0x1e92e: 68,
0x1e92f: 68,
0x1e930: 68,
0x1e931: 68,
0x1e932: 68,
0x1e933: 68,
0x1e934: 68,
0x1e935: 68,
0x1e936: 68,
0x1e937: 68,
0x1e938: 68,
0x1e939: 68,
0x1e93a: 68,
0x1e93b: 68,
0x1e93c: 68,
0x1e93d: 68,
0x1e93e: 68,
0x1e93f: 68,
0x1e940: 68,
0x1e941: 68,
0x1e942: 68,
0x1e943: 68,
}
codepoint_classes = {
'PVALID': (
@@ -858,6 +1029,10 @@ codepoint_classes = {
0x52300000524,
0x52500000526,
0x52700000528,
0x5290000052a,
0x52b0000052c,
0x52d0000052e,
0x52f00000530,
0x5590000055a,
0x56100000587,
0x591000005be,
@@ -881,15 +1056,14 @@ codepoint_classes = {
0x7c0000007f6,
0x8000000082e,
0x8400000085c,
0x8a0000008a1,
0x8a2000008ad,
0x8e4000008ff,
0x90000000958,
0x8600000086b,
0x8a0000008b5,
0x8b6000008be,
0x8d4000008e2,
0x8e300000958,
0x96000000964,
0x96600000970,
0x97100000978,
0x97900000980,
0x98100000984,
0x97100000984,
0x9850000098d,
0x98f00000991,
0x993000009a9,
@@ -902,6 +1076,7 @@ codepoint_classes = {
0x9d7000009d8,
0x9e0000009e4,
0x9e6000009f2,
0x9fc000009fd,
0xa0100000a04,
0xa0500000a0b,
0xa0f00000a11,
@@ -930,6 +1105,7 @@ codepoint_classes = {
0xad000000ad1,
0xae000000ae4,
0xae600000af0,
0xaf900000b00,
0xb0100000b04,
0xb0500000b0d,
0xb0f00000b11,
@@ -960,20 +1136,19 @@ codepoint_classes = {
0xbd000000bd1,
0xbd700000bd8,
0xbe600000bf0,
0xc0100000c04,
0xc0000000c04,
0xc0500000c0d,
0xc0e00000c11,
0xc1200000c29,
0xc2a00000c34,
0xc3500000c3a,
0xc2a00000c3a,
0xc3d00000c45,
0xc4600000c49,
0xc4a00000c4e,
0xc5500000c57,
0xc5800000c5a,
0xc5800000c5b,
0xc6000000c64,
0xc6600000c70,
0xc8200000c84,
0xc8000000c84,
0xc8500000c8d,
0xc8e00000c91,
0xc9200000ca9,
@@ -987,15 +1162,14 @@ codepoint_classes = {
0xce000000ce4,
0xce600000cf0,
0xcf100000cf3,
0xd0200000d04,
0xd0000000d04,
0xd0500000d0d,
0xd0e00000d11,
0xd1200000d3b,
0xd3d00000d45,
0xd1200000d45,
0xd4600000d49,
0xd4a00000d4f,
0xd5700000d58,
0xd6000000d64,
0xd5400000d58,
0xd5f00000d64,
0xd6600000d70,
0xd7a00000d80,
0xd8200000d84,
@@ -1008,6 +1182,7 @@ codepoint_classes = {
0xdcf00000dd5,
0xdd600000dd7,
0xdd800000de0,
0xde600000df0,
0xdf200000df4,
0xe0100000e33,
0xe3400000e3b,
@@ -1082,11 +1257,12 @@ codepoint_classes = {
0x13180000135b,
0x135d00001360,
0x138000001390,
0x13a0000013f5,
0x13a0000013f6,
0x14010000166d,
0x166f00001680,
0x16810000169b,
0x16a0000016eb,
0x16f1000016f9,
0x17000000170d,
0x170e00001715,
0x172000001735,
@@ -1103,7 +1279,7 @@ codepoint_classes = {
0x182000001878,
0x1880000018ab,
0x18b0000018f6,
0x19000000191d,
0x19000000191f,
0x19200000192c,
0x19300000193c,
0x19460000196e,
@@ -1117,6 +1293,7 @@ codepoint_classes = {
0x1a7f00001a8a,
0x1a9000001a9a,
0x1aa700001aa8,
0x1ab000001abe,
0x1b0000001b4c,
0x1b5000001b5a,
0x1b6b00001b74,
@@ -1125,15 +1302,15 @@ codepoint_classes = {
0x1c4000001c4a,
0x1c4d00001c7e,
0x1cd000001cd3,
0x1cd400001cf7,
0x1cd400001cfa,
0x1d0000001d2c,
0x1d2f00001d30,
0x1d3b00001d3c,
0x1d4e00001d4f,
0x1d6b00001d78,
0x1d7900001d9b,
0x1dc000001de7,
0x1dfc00001e00,
0x1dc000001dfa,
0x1dfb00001e00,
0x1e0100001e02,
0x1e0300001e04,
0x1e0500001e06,
@@ -1367,11 +1544,11 @@ codepoint_classes = {
0x309d0000309f,
0x30a1000030fb,
0x30fc000030ff,
0x31050000312e,
0x31050000312f,
0x31a0000031bb,
0x31f000003200,
0x340000004db6,
0x4e0000009fcd,
0x4e0000009feb,
0xa0000000a48d,
0xa4d00000a4fe,
0xa5000000a60d,
@@ -1413,7 +1590,9 @@ codepoint_classes = {
0xa6930000a694,
0xa6950000a696,
0xa6970000a698,
0xa69f0000a6e6,
0xa6990000a69a,
0xa69b0000a69c,
0xa69e0000a6e6,
0xa6f00000a6f2,
0xa7170000a720,
0xa7230000a724,
@@ -1463,30 +1642,39 @@ codepoint_classes = {
0xa7850000a786,
0xa7870000a789,
0xa78c0000a78d,
0xa78e0000a78f,
0xa78e0000a790,
0xa7910000a792,
0xa7930000a794,
0xa7930000a796,
0xa7970000a798,
0xa7990000a79a,
0xa79b0000a79c,
0xa79d0000a79e,
0xa79f0000a7a0,
0xa7a10000a7a2,
0xa7a30000a7a4,
0xa7a50000a7a6,
0xa7a70000a7a8,
0xa7a90000a7aa,
0xa7b50000a7b6,
0xa7b70000a7b8,
0xa7f70000a7f8,
0xa7fa0000a828,
0xa8400000a874,
0xa8800000a8c5,
0xa8800000a8c6,
0xa8d00000a8da,
0xa8e00000a8f8,
0xa8fb0000a8fc,
0xa8fd0000a8fe,
0xa9000000a92e,
0xa9300000a954,
0xa9800000a9c1,
0xa9cf0000a9da,
0xa9e00000a9ff,
0xaa000000aa37,
0xaa400000aa4e,
0xaa500000aa5a,
0xaa600000aa77,
0xaa7a0000aa7c,
0xaa800000aac3,
0xaa7a0000aac3,
0xaadb0000aade,
0xaae00000aaf0,
0xaaf20000aaf7,
@@ -1495,6 +1683,8 @@ codepoint_classes = {
0xab110000ab17,
0xab200000ab27,
0xab280000ab2f,
0xab300000ab5b,
0xab600000ab66,
0xabc00000abeb,
0xabec0000abee,
0xabf00000abfa,
@@ -1507,7 +1697,7 @@ codepoint_classes = {
0xfa230000fa25,
0xfa270000fa2a,
0xfb1e0000fb1f,
0xfe200000fe27,
0xfe200000fe30,
0xfe730000fe74,
0x100000001000c,
0x1000d00010027,
@@ -1519,20 +1709,32 @@ codepoint_classes = {
0x101fd000101fe,
0x102800001029d,
0x102a0000102d1,
0x103000001031f,
0x1033000010341,
0x102e0000102e1,
0x1030000010320,
0x1032d00010341,
0x103420001034a,
0x103500001037b,
0x103800001039e,
0x103a0000103c4,
0x103c8000103d0,
0x104280001049e,
0x104a0000104aa,
0x104d8000104fc,
0x1050000010528,
0x1053000010564,
0x1060000010737,
0x1074000010756,
0x1076000010768,
0x1080000010806,
0x1080800010809,
0x1080a00010836,
0x1083700010839,
0x1083c0001083d,
0x1083f00010856,
0x1086000010877,
0x108800001089f,
0x108e0000108f3,
0x108f4000108f6,
0x1090000010916,
0x109200001093a,
0x10980000109b8,
@@ -1545,31 +1747,137 @@ codepoint_classes = {
0x10a3800010a3b,
0x10a3f00010a40,
0x10a6000010a7d,
0x10a8000010a9d,
0x10ac000010ac8,
0x10ac900010ae7,
0x10b0000010b36,
0x10b4000010b56,
0x10b6000010b73,
0x10b8000010b92,
0x10c0000010c49,
0x10cc000010cf3,
0x1100000011047,
0x1106600011070,
0x11080000110bb,
0x1107f000110bb,
0x110d0000110e9,
0x110f0000110fa,
0x1110000011135,
0x1113600011140,
0x1115000011174,
0x1117600011177,
0x11180000111c5,
0x111d0000111da,
0x111ca000111cd,
0x111d0000111db,
0x111dc000111dd,
0x1120000011212,
0x1121300011238,
0x1123e0001123f,
0x1128000011287,
0x1128800011289,
0x1128a0001128e,
0x1128f0001129e,
0x1129f000112a9,
0x112b0000112eb,
0x112f0000112fa,
0x1130000011304,
0x113050001130d,
0x1130f00011311,
0x1131300011329,
0x1132a00011331,
0x1133200011334,
0x113350001133a,
0x1133c00011345,
0x1134700011349,
0x1134b0001134e,
0x1135000011351,
0x1135700011358,
0x1135d00011364,
0x113660001136d,
0x1137000011375,
0x114000001144b,
0x114500001145a,
0x11480000114c6,
0x114c7000114c8,
0x114d0000114da,
0x11580000115b6,
0x115b8000115c1,
0x115d8000115de,
0x1160000011641,
0x1164400011645,
0x116500001165a,
0x11680000116b8,
0x116c0000116ca,
0x120000001236f,
0x117000001171a,
0x1171d0001172c,
0x117300001173a,
0x118c0000118ea,
0x118ff00011900,
0x11a0000011a3f,
0x11a4700011a48,
0x11a5000011a84,
0x11a8600011a9a,
0x11ac000011af9,
0x11c0000011c09,
0x11c0a00011c37,
0x11c3800011c41,
0x11c5000011c5a,
0x11c7200011c90,
0x11c9200011ca8,
0x11ca900011cb7,
0x11d0000011d07,
0x11d0800011d0a,
0x11d0b00011d37,
0x11d3a00011d3b,
0x11d3c00011d3e,
0x11d3f00011d48,
0x11d5000011d5a,
0x120000001239a,
0x1248000012544,
0x130000001342f,
0x1440000014647,
0x1680000016a39,
0x16a4000016a5f,
0x16a6000016a6a,
0x16ad000016aee,
0x16af000016af5,
0x16b0000016b37,
0x16b4000016b44,
0x16b5000016b5a,
0x16b6300016b78,
0x16b7d00016b90,
0x16f0000016f45,
0x16f5000016f7f,
0x16f8f00016fa0,
0x1b0000001b002,
0x16fe000016fe2,
0x17000000187ed,
0x1880000018af3,
0x1b0000001b11f,
0x1b1700001b2fc,
0x1bc000001bc6b,
0x1bc700001bc7d,
0x1bc800001bc89,
0x1bc900001bc9a,
0x1bc9d0001bc9f,
0x1da000001da37,
0x1da3b0001da6d,
0x1da750001da76,
0x1da840001da85,
0x1da9b0001daa0,
0x1daa10001dab0,
0x1e0000001e007,
0x1e0080001e019,
0x1e01b0001e022,
0x1e0230001e025,
0x1e0260001e02b,
0x1e8000001e8c5,
0x1e8d00001e8d7,
0x1e9220001e94b,
0x1e9500001e95a,
0x200000002a6d7,
0x2a7000002b735,
0x2b7400002b81e,
0x2b8200002cea2,
0x2ceb00002ebe1,
),
'CONTEXTJ': (
0x200c0000200e,
Vendored Regular → Executable
View File
Vendored Regular → Executable
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = '2.6'
__version__ = '2.7'
Vendored Regular → Executable
+912 -367
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -27,7 +27,7 @@
:license: BSD, see LICENSE for more details.
"""
__docformat__ = 'restructuredtext en'
__version__ = '2.9.5'
__version__ = '2.10'
# high level interface
from jinja2.environment import Environment, Template
@@ -48,7 +48,7 @@ from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \
# exceptions
from jinja2.exceptions import TemplateError, UndefinedError, \
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
TemplateAssertionError
TemplateAssertionError, TemplateRuntimeError
# decorators and public utilities
from jinja2.filters import environmentfilter, contextfilter, \
@@ -64,6 +64,7 @@ __all__ = [
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
'TemplateRuntimeError',
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
+2
View File
@@ -0,0 +1,2 @@
# generated by scripts/generate_identifier_pattern.py
pattern = '·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯'
File diff suppressed because one or more lines are too long
+8 -6
View File
@@ -189,9 +189,9 @@ async def auto_aiter(iterable):
class AsyncLoopContext(LoopContextBase):
def __init__(self, async_iterator, after, length, recurse=None,
def __init__(self, async_iterator, undefined, after, length, recurse=None,
depth0=0):
LoopContextBase.__init__(self, recurse, depth0)
LoopContextBase.__init__(self, undefined, recurse, depth0)
self._async_iterator = async_iterator
self._after = after
self._length = length
@@ -221,15 +221,16 @@ class AsyncLoopContextIterator(object):
ctx.index0 += 1
if ctx._after is _last_iteration:
raise StopAsyncIteration()
next_elem = ctx._after
ctx._before = ctx._current
ctx._current = ctx._after
try:
ctx._after = await ctx._async_iterator.__anext__()
except StopAsyncIteration:
ctx._after = _last_iteration
return next_elem, ctx
return ctx._current, ctx
async def make_async_loop_context(iterable, recurse=None, depth0=0):
async def make_async_loop_context(iterable, undefined, recurse=None, depth0=0):
# Length is more complicated and less efficient in async mode. The
# reason for this is that we cannot know if length will be used
# upfront but because length is a property we cannot lazily execute it
@@ -251,4 +252,5 @@ async def make_async_loop_context(iterable, recurse=None, depth0=0):
after = await async_iterator.__anext__()
except StopAsyncIteration:
after = _last_iteration
return AsyncLoopContext(async_iterator, after, length, recurse, depth0)
return AsyncLoopContext(async_iterator, undefined, after, length, recurse,
depth0)
+1 -1
View File
@@ -297,7 +297,7 @@ class MemcachedBytecodeCache(BytecodeCache):
Libraries compatible with this class:
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
- `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
- `python-memcached <https://www.tummy.com/Community/software/python-memcached/>`_
- `cmemcache <http://gijsbert.org/cmemcache/>`_
(Unfortunately the django cache interface is not compatible because it
+78 -10
View File
@@ -130,9 +130,10 @@ class MacroRef(object):
class Frame(object):
"""Holds compile time information for us."""
def __init__(self, eval_ctx, parent=None):
def __init__(self, eval_ctx, parent=None, level=None):
self.eval_ctx = eval_ctx
self.symbols = Symbols(parent and parent.symbols or None)
self.symbols = Symbols(parent and parent.symbols or None,
level=level)
# a toplevel frame is the root + soft frames such as if conditions.
self.toplevel = False
@@ -168,8 +169,10 @@ class Frame(object):
rv.symbols = self.symbols.copy()
return rv
def inner(self):
def inner(self, isolated=False):
"""Return an inner frame."""
if isolated:
return Frame(self.eval_ctx, level=self.symbols.level + 1)
return Frame(self.eval_ctx, self)
def soft(self):
@@ -302,6 +305,9 @@ class CodeGenerator(NodeVisitor):
# Tracks parameter definition blocks
self._param_def_block = []
# Tracks the current context.
self._context_reference_stack = ['context']
# -- Various compilation helpers
def fail(self, msg, lineno):
@@ -472,8 +478,8 @@ class CodeGenerator(NodeVisitor):
if action == VAR_LOAD_PARAMETER:
pass
elif action == VAR_LOAD_RESOLVE:
self.writeline('%s = resolve(%r)' %
(target, param))
self.writeline('%s = %s(%r)' %
(target, self.get_resolve_func(), param))
elif action == VAR_LOAD_ALIAS:
self.writeline('%s = %s' % (target, param))
elif action == VAR_LOAD_UNDEFINED:
@@ -625,6 +631,27 @@ class CodeGenerator(NodeVisitor):
if self._param_def_block:
self._param_def_block[-1].discard(target)
def push_context_reference(self, target):
self._context_reference_stack.append(target)
def pop_context_reference(self):
self._context_reference_stack.pop()
def get_context_ref(self):
return self._context_reference_stack[-1]
def get_resolve_func(self):
target = self._context_reference_stack[-1]
if target == 'context':
return 'resolve'
return '%s.resolve' % target
def derive_context(self, frame):
return '%s.derived(%s)' % (
self.get_context_ref(),
self.dump_local_context(frame),
)
def parameter_is_undeclared(self, target):
"""Checks if a given target is an undeclared parameter."""
if not self._param_def_block:
@@ -793,8 +820,11 @@ class CodeGenerator(NodeVisitor):
self.writeline('if parent_template is None:')
self.indent()
level += 1
context = node.scoped and (
'context.derived(%s)' % self.dump_local_context(frame)) or 'context'
if node.scoped:
context = self.derive_context(frame)
else:
context = self.get_context_ref()
if supports_yield_from and not self.environment.is_async and \
frame.buffer is None:
@@ -1082,9 +1112,9 @@ class CodeGenerator(NodeVisitor):
self.write(')')
if node.recursive:
self.write(', loop_render_func, depth):')
self.write(', undefined, loop_render_func, depth):')
else:
self.write(extended_loop and '):' or ':')
self.write(extended_loop and ', undefined):' or ':')
self.indent()
self.enter_frame(loop_frame)
@@ -1129,6 +1159,13 @@ class CodeGenerator(NodeVisitor):
self.indent()
self.blockvisit(node.body, if_frame)
self.outdent()
for elif_ in node.elif_:
self.writeline('elif ', elif_)
self.visit(elif_.test, if_frame)
self.write(':')
self.indent()
self.blockvisit(elif_.body, if_frame)
self.outdent()
if node.else_:
self.writeline('else:')
self.indent()
@@ -1348,7 +1385,12 @@ class CodeGenerator(NodeVisitor):
self.newline(node)
self.visit(node.target, frame)
self.write(' = (Markup if context.eval_ctx.autoescape '
'else identity)(concat(%s))' % block_frame.buffer)
'else identity)(')
if node.filter is not None:
self.visit_Filter(node.filter, block_frame)
else:
self.write('concat(%s)' % block_frame.buffer)
self.write(')')
self.pop_assign_tracking(frame)
self.leave_frame(block_frame)
@@ -1373,6 +1415,18 @@ class CodeGenerator(NodeVisitor):
self.write(ref)
def visit_NSRef(self, node, frame):
# NSRefs can only be used to store values; since they use the normal
# `foo.bar` notation they will be parsed as a normal attribute access
# when used anywhere but in a `set` context
ref = frame.symbols.ref(node.name)
self.writeline('if not isinstance(%s, Namespace):' % ref)
self.indent()
self.writeline('raise TemplateRuntimeError(%r)' %
'cannot assign attribute on non-namespace object')
self.outdent()
self.writeline('%s[%r]' % (ref, node.attr))
def visit_Const(self, node, frame):
val = node.as_const(frame.eval_ctx)
if isinstance(val, float):
@@ -1631,6 +1685,20 @@ class CodeGenerator(NodeVisitor):
self.blockvisit(node.body, scope_frame)
self.leave_frame(scope_frame)
def visit_OverlayScope(self, node, frame):
ctx = self.temporary_identifier()
self.writeline('%s = %s' % (ctx, self.derive_context(frame)))
self.writeline('%s.vars = ' % ctx)
self.visit(node.context, frame)
self.push_context_reference(ctx)
scope_frame = frame.inner(isolated=True)
scope_frame.symbols.analyze_node(node)
self.enter_frame(scope_frame)
self.blockvisit(node.body, scope_frame)
self.leave_frame(scope_frame)
self.pop_context_reference()
def visit_EvalContextModifier(self, node, frame):
for keyword in node.options:
self.writeline('context.eval_ctx.%s = ' % keyword.key)
+1 -1
View File
@@ -198,7 +198,7 @@ def translate_exception(exc_info, initial_skip=0):
def get_jinja_locals(real_locals):
ctx = real_locals.get('context')
if ctx:
locals = ctx.get_all()
locals = ctx.get_all().copy()
else:
locals = {}
+4 -2
View File
@@ -9,7 +9,7 @@
:license: BSD, see LICENSE for more details.
"""
from jinja2._compat import range_type
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner, Namespace
# defaults for the parser / lexer
@@ -35,7 +35,8 @@ DEFAULT_NAMESPACE = {
'dict': dict,
'lipsum': generate_lorem_ipsum,
'cycler': Cycler,
'joiner': Joiner
'joiner': Joiner,
'namespace': Namespace
}
@@ -47,6 +48,7 @@ DEFAULT_POLICIES = {
'truncate.leeway': 5,
'json.dumps_function': None,
'json.dumps_kwargs': {'sort_keys': True},
'ext.i18n.trimmed': False,
}
+1 -1
View File
@@ -809,7 +809,7 @@ class Environment(object):
@internalcode
def get_template(self, name, parent=None, globals=None):
"""Load a template from the loader. If a loader is configured this
method ask the loader for the template and returns a :class:`Template`.
method asks the loader for the template and returns a :class:`Template`.
If the `parent` parameter is not `None`, :meth:`join_path` is called
to get the real template name before loading.
+19 -1
View File
@@ -10,6 +10,8 @@
:copyright: (c) 2017 by the Jinja Team.
:license: BSD.
"""
import re
from jinja2 import nodes
from jinja2.defaults import BLOCK_START_STRING, \
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
@@ -223,6 +225,7 @@ class InternationalizationExtension(Extension):
plural_expr = None
plural_expr_assignment = None
variables = {}
trimmed = None
while parser.stream.current.type != 'block_end':
if variables:
parser.stream.expect('comma')
@@ -241,6 +244,9 @@ class InternationalizationExtension(Extension):
if parser.stream.current.type == 'assign':
next(parser.stream)
variables[name.value] = var = parser.parse_expression()
elif trimmed is None and name.value in ('trimmed', 'notrimmed'):
trimmed = name.value == 'trimmed'
continue
else:
variables[name.value] = var = nodes.Name(name.value, 'load')
@@ -256,7 +262,7 @@ class InternationalizationExtension(Extension):
parser.stream.expect('block_end')
plural = plural_names = None
plural = None
have_plural = False
referenced = set()
@@ -297,6 +303,13 @@ class InternationalizationExtension(Extension):
elif plural_expr is None:
parser.fail('pluralize without variables', lineno)
if trimmed is None:
trimmed = self.environment.policies['ext.i18n.trimmed']
if trimmed:
singular = self._trim_whitespace(singular)
if plural:
plural = self._trim_whitespace(plural)
node = self._make_node(singular, plural, variables, plural_expr,
bool(referenced),
num_called_num and have_plural)
@@ -306,6 +319,9 @@ class InternationalizationExtension(Extension):
else:
return node
def _trim_whitespace(self, string, _ws_re=re.compile(r'\s*\n\s*')):
return _ws_re.sub(' ', string.strip())
def _parse_block(self, parser, allow_pluralize):
"""Parse until the next block tag with a given name."""
referenced = []
@@ -583,6 +599,8 @@ def babel_extract(fileobj, keywords, comment_tags, options):
auto_reload=False
)
if getbool(options, 'trimmed'):
environment.policies['ext.i18n.trimmed'] = True
if getbool(options, 'newstyle_gettext'):
environment.newstyle_gettext = True
+160 -43
View File
@@ -10,9 +10,10 @@
"""
import re
import math
import random
import warnings
from random import choice
from itertools import groupby
from itertools import groupby, chain
from collections import namedtuple
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
unicode_urlencode, htmlsafe_json_dumps
@@ -52,22 +53,34 @@ def environmentfilter(f):
return f
def make_attrgetter(environment, attribute):
def ignore_case(value):
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings
to lowercase and returns other types as-is."""
return value.lower() if isinstance(value, string_types) else value
def make_attrgetter(environment, attribute, postprocess=None):
"""Returns a callable that looks up the given attribute from a
passed object with the rules of the environment. Dots are allowed
to access attributes of attributes. Integer parts in paths are
looked up as integers.
"""
if not isinstance(attribute, string_types) \
or ('.' not in attribute and not attribute.isdigit()):
return lambda x: environment.getitem(x, attribute)
attribute = attribute.split('.')
if attribute is None:
attribute = []
elif isinstance(attribute, string_types):
attribute = [int(x) if x.isdigit() else x for x in attribute.split('.')]
else:
attribute = [attribute]
def attrgetter(item):
for part in attribute:
if part.isdigit():
part = int(part)
item = environment.getitem(item, part)
if postprocess is not None:
item = postprocess(item)
return item
return attrgetter
@@ -190,7 +203,7 @@ def do_title(s):
if item])
def do_dictsort(value, case_sensitive=False, by='key'):
def do_dictsort(value, case_sensitive=False, by='key', reverse=False):
"""Sort a dict and yield (key, value) pairs. Because python dicts are
unsorted you may want to use this function to order them by either
key or value:
@@ -200,6 +213,9 @@ def do_dictsort(value, case_sensitive=False, by='key'):
{% for item in mydict|dictsort %}
sort the dict by key, case insensitive
{% for item in mydict|dictsort(reverse=true) %}
sort the dict by key, case insensitive, reverse order
{% for item in mydict|dictsort(true) %}
sort the dict by key, case sensitive
@@ -211,20 +227,25 @@ def do_dictsort(value, case_sensitive=False, by='key'):
elif by == 'value':
pos = 1
else:
raise FilterArgumentError('You can only sort by either '
'"key" or "value"')
raise FilterArgumentError(
'You can only sort by either "key" or "value"'
)
def sort_func(item):
value = item[pos]
if isinstance(value, string_types) and not case_sensitive:
value = value.lower()
if not case_sensitive:
value = ignore_case(value)
return value
return sorted(value.items(), key=sort_func)
return sorted(value.items(), key=sort_func, reverse=reverse)
@environmentfilter
def do_sort(environment, value, reverse=False, case_sensitive=False,
attribute=None):
def do_sort(
environment, value, reverse=False, case_sensitive=False, attribute=None
):
"""Sort an iterable. Per default it sorts ascending, if you pass it
true as first argument it will reverse the sorting.
@@ -250,18 +271,85 @@ def do_sort(environment, value, reverse=False, case_sensitive=False,
.. versionchanged:: 2.6
The `attribute` parameter was added.
"""
if not case_sensitive:
def sort_func(item):
if isinstance(item, string_types):
item = item.lower()
return item
else:
sort_func = None
if attribute is not None:
getter = make_attrgetter(environment, attribute)
def sort_func(item, processor=sort_func or (lambda x: x)):
return processor(getter(item))
return sorted(value, key=sort_func, reverse=reverse)
key_func = make_attrgetter(
environment, attribute,
postprocess=ignore_case if not case_sensitive else None
)
return sorted(value, key=key_func, reverse=reverse)
@environmentfilter
def do_unique(environment, value, case_sensitive=False, attribute=None):
"""Returns a list of unique items from the the given iterable.
.. sourcecode:: jinja
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique }}
-> ['foo', 'bar', 'foobar']
The unique items are yielded in the same order as their first occurrence in
the iterable passed to the filter.
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Filter objects with unique values for this attribute.
"""
getter = make_attrgetter(
environment, attribute,
postprocess=ignore_case if not case_sensitive else None
)
seen = set()
for item in value:
key = getter(item)
if key not in seen:
seen.add(key)
yield item
def _min_or_max(environment, value, func, case_sensitive, attribute):
it = iter(value)
try:
first = next(it)
except StopIteration:
return environment.undefined('No aggregated item, sequence was empty.')
key_func = make_attrgetter(
environment, attribute,
ignore_case if not case_sensitive else None
)
return func(chain([first], it), key=key_func)
@environmentfilter
def do_min(environment, value, case_sensitive=False, attribute=None):
"""Return the smallest item from the sequence.
.. sourcecode:: jinja
{{ [1, 2, 3]|min }}
-> 1
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Get the object with the max value of this attribute.
"""
return _min_or_max(environment, value, min, case_sensitive, attribute)
@environmentfilter
def do_max(environment, value, case_sensitive=False, attribute=None):
"""Return the largest item from the sequence.
.. sourcecode:: jinja
{{ [1, 2, 3]|max }}
-> 3
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Get the object with the max value of this attribute.
"""
return _min_or_max(environment, value, max, case_sensitive, attribute)
def do_default(value, default_value=u'', boolean=False):
@@ -359,13 +447,13 @@ def do_last(environment, seq):
return environment.undefined('No last item, sequence was empty.')
@environmentfilter
def do_random(environment, seq):
@contextfilter
def do_random(context, seq):
"""Return a random item from the sequence."""
try:
return choice(seq)
return random.choice(seq)
except IndexError:
return environment.undefined('No random item, sequence was empty.')
return context.environment.undefined('No random item, sequence was empty.')
def do_filesizeformat(value, binary=False):
@@ -445,21 +533,44 @@ def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
return rv
def do_indent(s, width=4, indentfirst=False):
"""Return a copy of the passed string, each line indented by
4 spaces. The first line is not indented. If you want to
change the number of spaces or indent the first line too
you can pass additional parameters to the filter:
def do_indent(
s, width=4, first=False, blank=False, indentfirst=None
):
"""Return a copy of the string with each line indented by 4 spaces. The
first line and blank lines are not indented by default.
.. sourcecode:: jinja
:param width: Number of spaces to indent by.
:param first: Don't skip indenting the first line.
:param blank: Don't skip indenting empty lines.
{{ mytext|indent(2, true) }}
indent by two spaces and indent the first line too.
.. versionchanged:: 2.10
Blank lines are not indented by default.
Rename the ``indentfirst`` argument to ``first``.
"""
if indentfirst is not None:
warnings.warn(DeprecationWarning(
'The "indentfirst" argument is renamed to "first".'
), stacklevel=2)
first = indentfirst
s += u'\n' # this quirk is necessary for splitlines method
indention = u' ' * width
rv = (u'\n' + indention).join(s.splitlines())
if indentfirst:
if blank:
rv = (u'\n' + indention).join(s.splitlines())
else:
lines = s.splitlines()
rv = lines.pop(0)
if lines:
rv += u'\n' + u'\n'.join(
indention + line if line else line for line in lines
)
if first:
rv = indention + rv
return rv
@@ -865,6 +976,9 @@ def do_select(*args, **kwargs):
{{ numbers|select("odd") }}
{{ numbers|select("odd") }}
{{ numbers|select("divisibleby", 3) }}
{{ numbers|select("lessthan", 42) }}
{{ strings|select("equalto", "mystring") }}
.. versionadded:: 2.7
"""
@@ -1045,6 +1159,8 @@ FILTERS = {
'list': do_list,
'lower': do_lower,
'map': do_map,
'min': do_min,
'max': do_max,
'pprint': do_pprint,
'random': do_random,
'reject': do_reject,
@@ -1063,6 +1179,7 @@ FILTERS = {
'title': do_title,
'trim': do_trim,
'truncate': do_truncate,
'unique': do_unique,
'upper': do_upper,
'urlencode': do_urlencode,
'urlize': do_urlize,
+19 -6
View File
@@ -24,11 +24,13 @@ def symbols_for_node(node, parent_symbols=None):
class Symbols(object):
def __init__(self, parent=None):
if parent is None:
self.level = 0
else:
self.level = parent.level + 1
def __init__(self, parent=None, level=None):
if level is None:
if parent is None:
level = 0
else:
level = parent.level + 1
self.level = level
self.parent = parent
self.refs = {}
self.loads = {}
@@ -167,6 +169,10 @@ class RootVisitor(NodeVisitor):
for child in node.iter_child_nodes(exclude=('call',)):
self.sym_visitor.visit(child)
def visit_OverlayScope(self, node, **kwargs):
for child in node.body:
self.sym_visitor.visit(child)
def visit_For(self, node, for_branch='body', **kwargs):
if for_branch == 'body':
self.sym_visitor.visit(node.target, store_as_param=True)
@@ -209,6 +215,9 @@ class FrameSymbolVisitor(NodeVisitor):
elif node.ctx == 'load':
self.symbols.load(node.name)
def visit_NSRef(self, node, **kwargs):
self.symbols.load(node.name)
def visit_If(self, node, **kwargs):
self.visit(node.test, **kwargs)
@@ -222,9 +231,10 @@ class FrameSymbolVisitor(NodeVisitor):
return rv
body_symbols = inner_visit(node.body)
elif_symbols = inner_visit(node.elif_)
else_symbols = inner_visit(node.else_ or ())
self.symbols.branch_update([body_symbols, else_symbols])
self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
def visit_Macro(self, node, **kwargs):
self.symbols.store(node.name)
@@ -271,3 +281,6 @@ class FrameSymbolVisitor(NodeVisitor):
def visit_Block(self, node, **kwargs):
"""Stop visiting at blocks."""
def visit_OverlayScope(self, node, **kwargs):
"""Do not visit into overlay scopes."""
+29 -27
View File
@@ -15,14 +15,12 @@
:license: BSD, see LICENSE for more details.
"""
import re
import sys
from operator import itemgetter
from collections import deque
from operator import itemgetter
from jinja2._compat import implements_iterator, intern, iteritems, text_type
from jinja2.exceptions import TemplateSyntaxError
from jinja2.utils import LRUCache
from jinja2._compat import iteritems, implements_iterator, text_type, intern
# cache for the lexers. Exists in order to be able to have multiple
# environments with the same lexer
@@ -34,28 +32,25 @@ string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
integer_re = re.compile(r'\d+')
def _make_name_re():
try:
compile('föö', '<unknown>', 'eval')
except SyntaxError:
return re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
try:
# check if this Python supports Unicode identifiers
compile('föö', '<unknown>', 'eval')
except SyntaxError:
# no Unicode support, use ASCII identifiers
name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
check_ident = False
else:
# Unicode support, build a pattern to match valid characters, and set flag
# to use str.isidentifier to validate during lexing
from jinja2 import _identifier
name_re = re.compile(r'[\w{0}]+'.format(_identifier.pattern))
check_ident = True
# remove the pattern from memory after building the regex
import sys
del sys.modules['jinja2._identifier']
import jinja2
from jinja2 import _stringdefs
name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
_stringdefs.xid_continue))
# Save some memory here
sys.modules.pop('jinja2._stringdefs')
del _stringdefs
del jinja2._stringdefs
return name_re
# we use the unicode identifier rule if this python version is able
# to handle unicode identifiers, otherwise the standard ASCII one.
name_re = _make_name_re()
del _make_name_re
del jinja2._identifier
del _identifier
float_re = re.compile(r'(?<!\.)\d+\.\d+')
newline_re = re.compile(r'(\r\n|\r|\n)')
@@ -352,7 +347,10 @@ class TokenStream(object):
return self.next_if(expr) is not None
def __next__(self):
"""Go one token ahead and return the old one"""
"""Go one token ahead and return the old one.
Use the built-in :func:`next` instead of calling this directly.
"""
rv = self.current
if self._pushed:
self.current = self._pushed.popleft()
@@ -577,6 +575,10 @@ class Lexer(object):
token = value
elif token == 'name':
value = str(value)
if check_ident and not value.isidentifier():
raise TemplateSyntaxError(
'Invalid character in identifier',
lineno, name, filename)
elif token == 'string':
# try to unescape string
try:
+220
View File
@@ -0,0 +1,220 @@
import sys
from ast import literal_eval
from itertools import islice, chain
from jinja2 import nodes
from jinja2._compat import text_type
from jinja2.compiler import CodeGenerator, has_safe_repr
from jinja2.environment import Environment, Template
from jinja2.utils import concat, escape
def native_concat(nodes):
"""Return a native Python type from the list of compiled nodes. If the
result is a single node, its value is returned. Otherwise, the nodes are
concatenated as strings. If the result can be parsed with
:func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
string is returned.
"""
head = list(islice(nodes, 2))
if not head:
return None
if len(head) == 1:
out = head[0]
else:
out = u''.join([text_type(v) for v in chain(head, nodes)])
try:
return literal_eval(out)
except (ValueError, SyntaxError, MemoryError):
return out
class NativeCodeGenerator(CodeGenerator):
"""A code generator which avoids injecting ``to_string()`` calls around the
internal code Jinja uses to render templates.
"""
def visit_Output(self, node, frame):
"""Same as :meth:`CodeGenerator.visit_Output`, but do not call
``to_string`` on output nodes in generated code.
"""
if self.has_known_extends and frame.require_output_check:
return
finalize = self.environment.finalize
finalize_context = getattr(finalize, 'contextfunction', False)
finalize_eval = getattr(finalize, 'evalcontextfunction', False)
finalize_env = getattr(finalize, 'environmentfunction', False)
if finalize is not None:
if finalize_context or finalize_eval:
const_finalize = None
elif finalize_env:
def const_finalize(x):
return finalize(self.environment, x)
else:
const_finalize = finalize
else:
def const_finalize(x):
return x
# If we are inside a frame that requires output checking, we do so.
outdent_later = False
if frame.require_output_check:
self.writeline('if parent_template is None:')
self.indent()
outdent_later = True
# Try to evaluate as many chunks as possible into a static string at
# compile time.
body = []
for child in node.nodes:
try:
if const_finalize is None:
raise nodes.Impossible()
const = child.as_const(frame.eval_ctx)
if not has_safe_repr(const):
raise nodes.Impossible()
except nodes.Impossible:
body.append(child)
continue
# the frame can't be volatile here, because otherwise the as_const
# function would raise an Impossible exception at that point
try:
if frame.eval_ctx.autoescape:
if hasattr(const, '__html__'):
const = const.__html__()
else:
const = escape(const)
const = const_finalize(const)
except Exception:
# if something goes wrong here we evaluate the node at runtime
# for easier debugging
body.append(child)
continue
if body and isinstance(body[-1], list):
body[-1].append(const)
else:
body.append([const])
# if we have less than 3 nodes or a buffer we yield or extend/append
if len(body) < 3 or frame.buffer is not None:
if frame.buffer is not None:
# for one item we append, for more we extend
if len(body) == 1:
self.writeline('%s.append(' % frame.buffer)
else:
self.writeline('%s.extend((' % frame.buffer)
self.indent()
for item in body:
if isinstance(item, list):
val = repr(native_concat(item))
if frame.buffer is None:
self.writeline('yield ' + val)
else:
self.writeline(val + ',')
else:
if frame.buffer is None:
self.writeline('yield ', item)
else:
self.newline(item)
close = 0
if finalize is not None:
self.write('environment.finalize(')
if finalize_context:
self.write('context, ')
close += 1
self.visit(item, frame)
if close > 0:
self.write(')' * close)
if frame.buffer is not None:
self.write(',')
if frame.buffer is not None:
# close the open parentheses
self.outdent()
self.writeline(len(body) == 1 and ')' or '))')
# otherwise we create a format string as this is faster in that case
else:
format = []
arguments = []
for item in body:
if isinstance(item, list):
format.append(native_concat(item).replace('%', '%%'))
else:
format.append('%s')
arguments.append(item)
self.writeline('yield ')
self.write(repr(concat(format)) + ' % (')
self.indent()
for argument in arguments:
self.newline(argument)
close = 0
if finalize is not None:
self.write('environment.finalize(')
if finalize_context:
self.write('context, ')
elif finalize_eval:
self.write('context.eval_ctx, ')
elif finalize_env:
self.write('environment, ')
close += 1
self.visit(argument, frame)
self.write(')' * close + ', ')
self.outdent()
self.writeline(')')
if outdent_later:
self.outdent()
class NativeTemplate(Template):
def render(self, *args, **kwargs):
"""Render the template to produce a native Python type. If the result
is a single node, its value is returned. Otherwise, the nodes are
concatenated as strings. If the result can be parsed with
:func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
string is returned.
"""
vars = dict(*args, **kwargs)
try:
return native_concat(self.root_render_func(self.new_context(vars)))
except Exception:
exc_info = sys.exc_info()
return self.environment.handle_exception(exc_info, True)
class NativeEnvironment(Environment):
"""An environment that renders templates to native Python types."""
code_generator_class = NativeCodeGenerator
template_class = NativeTemplate
+77 -17
View File
@@ -314,7 +314,7 @@ class For(Stmt):
class If(Stmt):
"""If `test` is true, `body` is rendered, else `else_`."""
fields = ('test', 'body', 'else_')
fields = ('test', 'body', 'elif_', 'else_')
class Macro(Stmt):
@@ -387,7 +387,7 @@ class Assign(Stmt):
class AssignBlock(Stmt):
"""Assigns a block to a target."""
fields = ('target', 'body')
fields = ('target', 'filter', 'body')
class Expr(Node):
@@ -465,6 +465,18 @@ class Name(Expr):
'True', 'False', 'None')
class NSRef(Expr):
"""Reference to a namespace value assignment"""
fields = ('name', 'attr')
def can_assign(self):
# We don't need any special checks here; NSRef assignments have a
# runtime check to ensure the target is a namespace object which will
# have been checked already as it is created using a normal assignment
# which goes through a `Name` node.
return True
class Literal(Expr):
"""Baseclass for literals."""
abstract = True
@@ -587,6 +599,25 @@ class CondExpr(Expr):
return self.expr2.as_const(eval_ctx)
def args_as_const(node, eval_ctx):
args = [x.as_const(eval_ctx) for x in node.args]
kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)
if node.dyn_args is not None:
try:
args.extend(node.dyn_args.as_const(eval_ctx))
except Exception:
raise Impossible()
if node.dyn_kwargs is not None:
try:
kwargs.update(node.dyn_kwargs.as_const(eval_ctx))
except Exception:
raise Impossible()
return args, kwargs
class Filter(Expr):
"""This node applies a filter on an expression. `name` is the name of
the filter, the rest of the fields are the same as for :class:`Call`.
@@ -594,44 +625,41 @@ class Filter(Expr):
If the `node` of a filter is `None` the contents of the last buffer are
filtered. Buffers are created by macros and filter blocks.
"""
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
def as_const(self, eval_ctx=None):
eval_ctx = get_eval_context(self, eval_ctx)
if eval_ctx.volatile or self.node is None:
raise Impossible()
# we have to be careful here because we call filter_ below.
# if this variable would be called filter, 2to3 would wrap the
# call in a list beause it is assuming we are talking about the
# builtin filter function here which no longer returns a list in
# python 3. because of that, do not rename filter_ to filter!
filter_ = self.environment.filters.get(self.name)
if filter_ is None or getattr(filter_, 'contextfilter', False):
raise Impossible()
# We cannot constant handle async filters, so we need to make sure
# to not go down this path.
if eval_ctx.environment.is_async and \
getattr(filter_, 'asyncfiltervariant', False):
if (
eval_ctx.environment.is_async
and getattr(filter_, 'asyncfiltervariant', False)
):
raise Impossible()
obj = self.node.as_const(eval_ctx)
args = [obj] + [x.as_const(eval_ctx) for x in self.args]
args, kwargs = args_as_const(self, eval_ctx)
args.insert(0, self.node.as_const(eval_ctx))
if getattr(filter_, 'evalcontextfilter', False):
args.insert(0, eval_ctx)
elif getattr(filter_, 'environmentfilter', False):
args.insert(0, self.environment)
kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
if self.dyn_args is not None:
try:
args.extend(self.dyn_args.as_const(eval_ctx))
except Exception:
raise Impossible()
if self.dyn_kwargs is not None:
try:
kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
except Exception:
raise Impossible()
try:
return filter_(*args, **kwargs)
except Exception:
@@ -642,8 +670,24 @@ class Test(Expr):
"""Applies a test on an expression. `name` is the name of the test, the
rest of the fields are the same as for :class:`Call`.
"""
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
def as_const(self, eval_ctx=None):
test = self.environment.tests.get(self.name)
if test is None:
raise Impossible()
eval_ctx = get_eval_context(self, eval_ctx)
args, kwargs = args_as_const(self, eval_ctx)
args.insert(0, self.node.as_const(eval_ctx))
try:
return test(*args, **kwargs)
except Exception:
raise Impossible()
class Call(Expr):
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
@@ -914,6 +958,22 @@ class Scope(Stmt):
fields = ('body',)
class OverlayScope(Stmt):
"""An overlay scope for extensions. This is a largely unoptimized scope
that however can be used to introduce completely arbitrary variables into
a sub scope from a dictionary or dictionary like object. The `context`
field has to evaluate to a dictionary object.
Example usage::
OverlayScope(context=self.call_method('get_context'),
body=[...])
.. versionadded:: 2.10
"""
fields = ('context', 'body')
class EvalContextModifier(Stmt):
"""Modifies the eval context. For each option that should be modified,
a :class:`Keyword` has to be added to the :attr:`options` list.
+19 -14
View File
@@ -176,13 +176,14 @@ class Parser(object):
def parse_set(self):
"""Parse an assign statement."""
lineno = next(self.stream).lineno
target = self.parse_assign_target()
target = self.parse_assign_target(with_namespace=True)
if self.stream.skip_if('assign'):
expr = self.parse_tuple()
return nodes.Assign(target, expr, lineno=lineno)
filter_node = self.parse_filter(None)
body = self.parse_statements(('name:endset',),
drop_needle=True)
return nodes.AssignBlock(target, body, lineno=lineno)
return nodes.AssignBlock(target, filter_node, body, lineno=lineno)
def parse_for(self):
"""Parse a for loop."""
@@ -210,17 +211,16 @@ class Parser(object):
node.test = self.parse_tuple(with_condexpr=False)
node.body = self.parse_statements(('name:elif', 'name:else',
'name:endif'))
node.elif_ = []
node.else_ = []
token = next(self.stream)
if token.test('name:elif'):
new_node = nodes.If(lineno=self.stream.current.lineno)
node.else_ = [new_node]
node = new_node
node = nodes.If(lineno=self.stream.current.lineno)
result.elif_.append(node)
continue
elif token.test('name:else'):
node.else_ = self.parse_statements(('name:endif',),
drop_needle=True)
else:
node.else_ = []
result.else_ = self.parse_statements(('name:endif',),
drop_needle=True)
break
return result
@@ -334,10 +334,9 @@ class Parser(object):
if parse_context() or self.stream.current.type != 'comma':
break
else:
break
self.stream.expect('name')
if not hasattr(node, 'with_context'):
node.with_context = False
self.stream.skip_if('comma')
return node
def parse_signature(self, node):
@@ -395,15 +394,21 @@ class Parser(object):
return node
def parse_assign_target(self, with_tuple=True, name_only=False,
extra_end_rules=None):
extra_end_rules=None, with_namespace=False):
"""Parse an assignment target. As Jinja2 allows assignments to
tuples, this function can parse all allowed assignment targets. Per
default assignments to tuples are parsed, that can be disable however
by setting `with_tuple` to `False`. If only assignments to names are
wanted `name_only` can be set to `True`. The `extra_end_rules`
parameter is forwarded to the tuple parsing function.
parameter is forwarded to the tuple parsing function. If
`with_namespace` is enabled, a namespace assignment may be parsed.
"""
if name_only:
if with_namespace and self.stream.look().type == 'dot':
token = self.stream.expect('name')
next(self.stream) # dot
attr = self.stream.expect('name')
target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
elif name_only:
token = self.stream.expect('name')
target = nodes.Name(token.value, 'store', lineno=token.lineno)
else:
+43 -15
View File
@@ -11,9 +11,11 @@
import sys
from itertools import chain
from types import MethodType
from jinja2.nodes import EvalContext, _context_function_types
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
internalcode, object_type_repr, evalcontextfunction
internalcode, object_type_repr, evalcontextfunction, Namespace
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
TemplateNotFound
from jinja2._compat import imap, text_type, iteritems, \
@@ -25,7 +27,7 @@ from jinja2._compat import imap, text_type, iteritems, \
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
'TemplateRuntimeError', 'missing', 'concat', 'escape',
'markup_join', 'unicode_join', 'to_string', 'identity',
'TemplateNotFound']
'TemplateNotFound', 'Namespace']
#: the name of the function that is used to convert something into
#: a string. We can just use the text type here.
@@ -34,6 +36,7 @@ to_string = text_type
#: the identity function. Useful for certain things in the environment
identity = lambda x: x
_first_iteration = object()
_last_iteration = object()
@@ -167,7 +170,7 @@ class Context(with_metaclass(ContextMeta)):
# In case we detect the fast resolve mode we can set up an alias
# here that bypasses the legacy code logic.
if self._fast_resolve_mode:
self.resolve_or_missing = resolve_or_missing
self.resolve_or_missing = MethodType(resolve_or_missing, self)
def super(self, name, current):
"""Render a parent block."""
@@ -239,13 +242,14 @@ class Context(with_metaclass(ContextMeta)):
__traceback_hide__ = True # noqa
# Allow callable classes to take a context
fn = __obj.__call__
for fn_type in ('contextfunction',
'evalcontextfunction',
'environmentfunction'):
if hasattr(fn, fn_type):
__obj = fn
break
if hasattr(__obj, '__call__'):
fn = __obj.__call__
for fn_type in ('contextfunction',
'evalcontextfunction',
'environmentfunction'):
if hasattr(fn, fn_type):
__obj = fn
break
if isinstance(__obj, _context_function_types):
if getattr(__obj, 'contextfunction', 0):
@@ -347,13 +351,17 @@ class BlockReference(object):
class LoopContextBase(object):
"""A loop context for dynamic iteration."""
_before = _first_iteration
_current = _first_iteration
_after = _last_iteration
_length = None
def __init__(self, recurse=None, depth0=0):
def __init__(self, undefined, recurse=None, depth0=0):
self._undefined = undefined
self._recurse = recurse
self.index0 = -1
self.depth0 = depth0
self._last_checked_value = missing
def cycle(self, *args):
"""Cycles among the arguments with the current loop index."""
@@ -361,6 +369,13 @@ class LoopContextBase(object):
raise TypeError('no items for cycling given')
return args[self.index0 % len(args)]
def changed(self, *value):
"""Checks whether the value has changed since the last call."""
if self._last_checked_value != value:
self._last_checked_value = value
return True
return False
first = property(lambda x: x.index0 == 0)
last = property(lambda x: x._after is _last_iteration)
index = property(lambda x: x.index0 + 1)
@@ -368,6 +383,18 @@ class LoopContextBase(object):
revindex0 = property(lambda x: x.length - x.index)
depth = property(lambda x: x.depth0 + 1)
@property
def previtem(self):
if self._before is _first_iteration:
return self._undefined('there is no previous item')
return self._before
@property
def nextitem(self):
if self._after is _last_iteration:
return self._undefined('there is no next item')
return self._after
def __len__(self):
return self.length
@@ -393,8 +420,8 @@ class LoopContextBase(object):
class LoopContext(LoopContextBase):
def __init__(self, iterable, recurse=None, depth0=0):
LoopContextBase.__init__(self, recurse, depth0)
def __init__(self, iterable, undefined, recurse=None, depth0=0):
LoopContextBase.__init__(self, undefined, recurse, depth0)
self._iterator = iter(iterable)
# try to get the length of the iterable early. This must be done
@@ -446,9 +473,10 @@ class LoopContextIterator(object):
ctx.index0 += 1
if ctx._after is _last_iteration:
raise StopIteration()
next_elem = ctx._after
ctx._before = ctx._current
ctx._current = ctx._after
ctx._after = ctx._safe_next()
return next_elem, ctx
return ctx._current, ctx
class Macro(object):
+1 -1
View File
@@ -107,7 +107,7 @@ class _MagicFormatMapping(Mapping):
"""This class implements a dummy wrapper to fix a bug in the Python
standard library for string formatting.
See http://bugs.python.org/issue13598 for information about why
See https://bugs.python.org/issue13598 for information about why
this is necessary.
"""
+22 -32
View File
@@ -8,6 +8,7 @@
:copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import operator
import re
from collections import Mapping
from jinja2.runtime import Undefined
@@ -103,28 +104,6 @@ def test_sequence(value):
return True
def test_equalto(value, other):
"""Check if an object has the same value as another object:
.. sourcecode:: jinja
{% if foo.expression is equalto 42 %}
the foo attribute evaluates to the constant 42
{% endif %}
This appears to be a useless test as it does exactly the same as the
``==`` operator, but it can be useful when used together with the
`selectattr` function:
.. sourcecode:: jinja
{{ users|selectattr("email", "equalto", "foo@bar.invalid") }}
.. versionadded:: 2.8
"""
return value == other
def test_sameas(value, other):
"""Check if an object points to the same memory address than another
object:
@@ -152,14 +131,12 @@ def test_escaped(value):
return hasattr(value, '__html__')
def test_greaterthan(value, other):
"""Check if value is greater than other."""
return value > other
def test_in(value, seq):
"""Check if value is in seq.
def test_lessthan(value, other):
"""Check if value is less than other."""
return value < other
.. versionadded:: 2.10
"""
return value in seq
TESTS = {
@@ -178,8 +155,21 @@ TESTS = {
'iterable': test_iterable,
'callable': test_callable,
'sameas': test_sameas,
'equalto': test_equalto,
'escaped': test_escaped,
'greaterthan': test_greaterthan,
'lessthan': test_lessthan
'in': test_in,
'==': operator.eq,
'eq': operator.eq,
'equalto': operator.eq,
'!=': operator.ne,
'ne': operator.ne,
'>': operator.gt,
'gt': operator.gt,
'greaterthan': operator.gt,
'ge': operator.ge,
'>=': operator.ge,
'<': operator.lt,
'lt': operator.lt,
'lessthan': operator.lt,
'<=': operator.le,
'le': operator.le,
}
+24 -1
View File
@@ -567,7 +567,7 @@ def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
.replace(u'>', u'\\u003e') \
.replace(u'&', u'\\u0026') \
.replace(u"'", u'\\u0027')
return rv
return Markup(rv)
@implements_iterator
@@ -612,6 +612,29 @@ class Joiner(object):
return self.sep
class Namespace(object):
"""A namespace object that can hold arbitrary attributes. It may be
initialized from a dictionary or with keyword argments."""
def __init__(*args, **kwargs):
self, args = args[0], args[1:]
self.__attrs = dict(*args, **kwargs)
def __getattribute__(self, name):
if name == '_Namespace__attrs':
return object.__getattribute__(self, name)
try:
return self.__attrs[name]
except KeyError:
raise AttributeError(name)
def __setitem__(self, name, value):
self.__attrs[name] = value
def __repr__(self):
return '<Namespace %r>' % self.__attrs
# does this python version support async for in and async generators?
try:
exec('async def _():\n async for _ in ():\n yield _')
+118 -34
View File
@@ -2,8 +2,8 @@ r'''Parse strings using a specification based on the Python format() syntax.
``parse()`` is the opposite of ``format()``
The module is set up to only export ``parse()``, ``search()`` and
``findall()`` when ``import *`` is used:
The module is set up to only export ``parse()``, ``search()``, ``findall()``,
and ``with_pattern()`` when ``import *`` is used:
>>> from parse import *
@@ -37,6 +37,12 @@ compile it once:
("compile" is not exported for ``import *`` usage as it would override the
built-in ``compile()`` function)
The default behaviour is to match strings case insensitively. You may match with
case by specifying `case_sensitive=True`:
>>> parse('SPAM', 'spam', case_sensitive=True) is None
True
Format Syntax
-------------
@@ -91,6 +97,9 @@ spam
>>> print(r['quest']['name'])
to seek the holy grail!
If the text you're matching has braces in it you can match those by including
a double-brace ``{{`` or ``}}`` in your format string, just like format() does.
Format Specification
--------------------
@@ -132,6 +141,7 @@ Type Characters Matched Output
n Numbers with thousands separators (, or .) int
% Percentage (converted to value/100.0) float
f Fixed-point numbers float
F Decimal numbers Decimal
e Floating-point numbers with exponent float
e.g. 1.1e-10, NAN (all case insensitive)
g General number format (either d, f or e) float
@@ -287,10 +297,47 @@ A more complete example of a custom type might be:
... return yesno_mapping[text.lower()]
If the type converter ``pattern`` uses regex-grouping (with parenthesis),
you should indicate this by using the optional ``regex_group_count`` parameter
in the ``with_pattern()`` decorator:
>>> @with_pattern(r'((\d+))', regex_group_count=2)
... def parse_number2(text):
... return int(text)
>>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
<Result (42, 43) {}>
Otherwise, this may cause parsing problems with unnamed/fixed parameters.
Potential Gotchas
-----------------
`parse()` will always match the shortest text necessary (from left to right)
to fulfil the parse pattern, so for example:
>>> pattern = '{dir1}/{dir2}'
>>> data = 'root/parent/subdir'
>>> sorted(parse(pattern, data).named.items())
[('dir1', 'root'), ('dir2', 'parent/subdir')]
So, even though `{'dir1': 'root/parent', 'dir2': 'subdir'}` would also fit
the pattern, the actual match represents the shortest successful match for
`dir1`.
----
**Version history (in brief)**:
- 1.8.4 Add LICENSE file at request of packagers.
Correct handling of AM/PM to follow most common interpretation.
Correct parsing of hexadecimal that looks like a binary prefix.
Add ability to parse case sensitively.
Add parsing of numbers to Decimal with "F" (thanks John Vandenberg)
- 1.8.3 Add regex_group_count to with_pattern() decorator to support
user-defined types that contain brackets/parenthesis (thanks Jens Engel)
- 1.8.2 add documentation for including braces in format string
- 1.8.1 ensure bare hexadecimal digits are not matched
- 1.8.0 support manual control over result evaluation (thanks Timo Furrer)
- 1.7.0 parse dict fields (thanks Mark Visser) and adapted to allow
more than 100 re groups in Python 3.5+ (thanks David King)
@@ -340,12 +387,15 @@ A more complete example of a custom type might be:
This code is copyright 2012-2017 Richard Jones <richard@python.org>
See the end of the source file for the license of use.
'''
__version__ = '1.8.0'
from __future__ import absolute_import
__version__ = '1.8.4'
# yes, I now have two problems
import re
import sys
from datetime import datetime, time, tzinfo, timedelta
from decimal import Decimal
from functools import partial
import logging
@@ -354,7 +404,7 @@ __all__ = 'parse search findall with_pattern'.split()
log = logging.getLogger(__name__)
def with_pattern(pattern):
def with_pattern(pattern, regex_group_count=None):
"""Attach a regular expression pattern matcher to a custom type converter
function.
@@ -373,10 +423,12 @@ def with_pattern(pattern):
>>> parse_number.pattern = r"\d+"
:param pattern: regular expression pattern (as text)
:param regex_group_count: Indicates how many regex-groups are in pattern.
:return: wrapped function
"""
def decorator(func):
func.pattern = pattern
func.regex_group_count = regex_group_count
return func
return decorator
@@ -388,6 +440,9 @@ def int_convert(base):
It may be of a base other than 10.
If may start with a base indicator, 0#nnnn, which we assume should
override the specified base.
It may also have other non-numeric characters that we can ignore.
'''
CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'
@@ -398,7 +453,7 @@ def int_convert(base):
else:
sign = 1
if string[0] == '0' and len(string) > 1:
if string[0] == '0' and len(string) > 2:
if string[1] in 'bB':
base = 2
elif string[1] in 'oO':
@@ -506,14 +561,18 @@ def date_convert(string, match, ymd=None, mdy=None, dmy=None,
H = int(H)
M = int(M)
day_incr = False
if am is not None:
am = groups[am]
if am and am.strip() == 'PM':
if am:
am = am.strip()
if am == 'AM' and H == 12:
# correction for "12" hour functioning as "0" hour: 12:15 AM = 00:15 by 24 hr clock
H -= 12
elif am == 'PM' and H == 12:
# no correction needed: 12PM is midday, 12:00 by 24 hour clock
pass
elif am == 'PM':
H += 12
if H > 23:
day_incr = True
H -= 24
if tz is not None:
tz = groups[tz]
@@ -548,9 +607,6 @@ def date_convert(string, match, ymd=None, mdy=None, dmy=None,
d = int(d)
d = datetime(y, m, d, H, M, S, u, tzinfo=tz)
if day_incr:
d = d + timedelta(days=1)
return d
@@ -567,7 +623,7 @@ class RepeatedNameError(ValueError):
REGEX_SAFETY = re.compile('([?\\\\.[\]()*+\^$!\|])')
# allowed field types
ALLOWED_TYPES = set(list('nbox%fegwWdDsS') +
ALLOWED_TYPES = set(list('nbox%fFegwWdDsS') +
['t' + c for c in 'ieahgcts'])
@@ -609,7 +665,7 @@ def extract_format(format, extra_types):
# the rest is the type, if present
type = format
if type and type not in ALLOWED_TYPES and type not in extra_types:
raise ValueError('type %r not recognised' % type)
raise ValueError('format spec %r not recognised' % type)
return locals()
@@ -620,7 +676,7 @@ PARSE_RE = re.compile(r"""({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})"
class Parser(object):
'''Encapsulate a format string that may be used to parse other strings.
'''
def __init__(self, format, extra_types={}):
def __init__(self, format, extra_types=None, case_sensitive=False):
# a mapping of a name as in {hello.world} to a regex-group compatible
# name, like hello__world Its used to prevent the transformation of
# name-to-group and group to name to fail subtly, such as in:
@@ -634,7 +690,13 @@ class Parser(object):
self._name_types = {}
self._format = format
if extra_types is None:
extra_types = {}
self._extra_types = extra_types
if case_sensitive:
self._re_flags = re.DOTALL
else:
self._re_flags = re.IGNORECASE | re.DOTALL
self._fixed_fields = []
self._named_fields = []
self._group_index = 0
@@ -643,7 +705,7 @@ class Parser(object):
self.__search_re = None
self.__match_re = None
log.debug('format %r -> %r' % (format, self._expression))
log.debug('format %r -> %r', format, self._expression)
def __repr__(self):
if len(self._format) > 20:
@@ -655,8 +717,7 @@ class Parser(object):
def _search_re(self):
if self.__search_re is None:
try:
self.__search_re = re.compile(self._expression,
re.IGNORECASE | re.DOTALL)
self.__search_re = re.compile(self._expression, self._re_flags)
except AssertionError:
# access error through sys to keep py3k and backward compat
e = str(sys.exc_info()[1])
@@ -670,8 +731,7 @@ class Parser(object):
if self.__match_re is None:
expression = '^%s$' % self._expression
try:
self.__match_re = re.compile(expression,
re.IGNORECASE | re.DOTALL)
self.__match_re = re.compile(expression, self._re_flags)
except AssertionError:
# access error through sys to keep py3k and backward compat
e = str(sys.exc_info()[1])
@@ -721,8 +781,8 @@ class Parser(object):
else:
return Match(self, m)
def findall(self, string, pos=0, endpos=None, extra_types={}, evaluate_result=True):
'''Search "string" for the all occurrances of "format".
def findall(self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True):
'''Search "string" for all occurrences of "format".
Optionally start the search at "pos" character index and limit the
search to a maximum index of endpos - equivalent to
@@ -821,7 +881,7 @@ class Parser(object):
elif '_' in field:
group = field.replace('_', '_' * n)
else:
raise KeyError('duplicated group name %r' % (field, ))
raise KeyError('duplicated group name %r' % (field,))
# save off the mapping
self._group_to_name_map[group] = field
@@ -875,6 +935,10 @@ class Parser(object):
if type in self._extra_types:
type_converter = self._extra_types[type]
s = getattr(type_converter, 'pattern', r'.+?')
regex_group_count = getattr(type_converter, 'regex_group_count', 0)
if regex_group_count is None:
regex_group_count = 0
self._group_index += regex_group_count
def f(string, m):
return type_converter(string)
@@ -902,6 +966,9 @@ class Parser(object):
elif type == 'f':
s = r'\d+\.\d+'
self._type_conversions[group] = lambda s, m: float(s)
elif type == 'F':
s = r'\d+\.\d+'
self._type_conversions[group] = lambda s, m: Decimal(s)
elif type == 'e':
s = r'\d+\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF'
self._type_conversions[group] = lambda s, m: float(s)
@@ -910,7 +977,7 @@ class Parser(object):
self._group_index += 2
self._type_conversions[group] = lambda s, m: float(s)
elif type == 'd':
s = r'\d+|0[xX][0-9a-fA-F]+|[0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+'
s = r'\d+|0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+'
self._type_conversions[group] = int_convert(10)
elif type == 'ti':
s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % \
@@ -963,7 +1030,7 @@ class Parser(object):
am=n + 4, tz=n + 5)
self._group_index += 5
elif type == 'ts':
s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % (MONTHS_PAT)
s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % MONTHS_PAT
n = self._group_index
self._type_conversions[group] = partial(date_convert, mm=n+1, dd=n+3,
hms=n + 5)
@@ -1089,7 +1156,7 @@ class ResultIterator(object):
next = __next__
def parse(format, string, extra_types={}, evaluate_result=True):
def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False):
'''Using "format" attempt to pull values from "string".
The format must match the string contents exactly. If the value
@@ -1106,17 +1173,22 @@ def parse(format, string, extra_types={}, evaluate_result=True):
.evaluate_result() - This will return a Result instance like you would get
with ``evaluate_result`` set to True
The default behaviour is to match strings case insensitively. You may match with
case by specifying case_sensitive=True.
If the format is invalid a ValueError will be raised.
See the module documentation for the use of "extra_types".
In the case there is no match parse() will return None.
'''
return Parser(format, extra_types=extra_types).parse(string, evaluate_result=evaluate_result)
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
return p.parse(string, evaluate_result=evaluate_result)
def search(format, string, pos=0, endpos=None, extra_types={}, evaluate_result=True):
'''Search "string" for the first occurance of "format".
def search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True,
case_sensitive=False):
'''Search "string" for the first occurrence of "format".
The format may occur anywhere within the string. If
instead you wish for the format to exactly match the string
@@ -1135,17 +1207,22 @@ def search(format, string, pos=0, endpos=None, extra_types={}, evaluate_result=T
.evaluate_result() - This will return a Result instance like you would get
with ``evaluate_result`` set to True
The default behaviour is to match strings case insensitively. You may match with
case by specifying case_sensitive=True.
If the format is invalid a ValueError will be raised.
See the module documentation for the use of "extra_types".
In the case there is no match parse() will return None.
'''
return Parser(format, extra_types=extra_types).search(string, pos, endpos, evaluate_result=evaluate_result)
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
return p.search(string, pos, endpos, evaluate_result=evaluate_result)
def findall(format, string, pos=0, endpos=None, extra_types={}, evaluate_result=True):
'''Search "string" for the all occurrances of "format".
def findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True,
case_sensitive=False):
'''Search "string" for all occurrences of "format".
You will be returned an iterator that holds Result instances
for each format match found.
@@ -1163,19 +1240,26 @@ def findall(format, string, pos=0, endpos=None, extra_types={}, evaluate_result=
.evaluate_result() - This will return a Result instance like you would get
with ``evaluate_result`` set to True
The default behaviour is to match strings case insensitively. You may match with
case by specifying case_sensitive=True.
If the format is invalid a ValueError will be raised.
See the module documentation for the use of "extra_types".
'''
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
return Parser(format, extra_types=extra_types).findall(string, pos, endpos, evaluate_result=evaluate_result)
def compile(format, extra_types={}):
def compile(format, extra_types=None, case_sensitive=False):
'''Create a Parser instance to parse "format".
The resultant Parser has a method .parse(string) which
behaves in the same manner as parse(format, string).
The default behaviour is to match strings case insensitively. You may match with
case by specifying case_sensitive=True.
Use this function if you intend to parse many strings
with the same format.
+23
View File
@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2014-2017 Matthias C. M. Troffaes
Copyright (c) 2012-2014 Antoine Pitrou and contributors
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.
+209 -105
View File
@@ -1,3 +1,7 @@
# Copyright (c) 2014-2017 Matthias C. M. Troffaes
# Copyright (c) 2012-2014 Antoine Pitrou and contributors
# Distributed under the terms of the MIT License.
import ctypes
import fnmatch
import functools
@@ -9,9 +13,9 @@ import re
import six
import sys
from collections import Sequence
from contextlib import contextmanager
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST, EPERM, EACCES
from operator import attrgetter
from stat import (
S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO)
try:
@@ -24,23 +28,22 @@ try:
intern = intern
except NameError:
intern = sys.intern
try:
basestring = basestring
except NameError:
basestring = str
supports_symlinks = True
try:
if os.name == 'nt':
import nt
except ImportError:
nt = None
else:
if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
from nt import _getfinalpathname
else:
supports_symlinks = False
_getfinalpathname = None
else:
nt = None
try:
from os import scandir as os_scandir
except ImportError:
from scandir import scandir as os_scandir
__all__ = [
"PurePath", "PurePosixPath", "PureWindowsPath",
@@ -59,12 +62,15 @@ def _py2_fsencode(parts):
else part for part in parts]
def _try_except_fileexistserror(try_func, except_func):
def _try_except_fileexistserror(try_func, except_func, else_func=None):
if sys.version_info >= (3, 3):
try:
try_func()
except FileExistsError as exc:
except_func(exc)
else:
if else_func is not None:
else_func()
else:
try:
try_func()
@@ -73,6 +79,45 @@ def _try_except_fileexistserror(try_func, except_func):
raise
else:
except_func(exc)
else:
if else_func is not None:
else_func()
def _try_except_filenotfounderror(try_func, except_func):
if sys.version_info >= (3, 3):
try:
try_func()
except FileNotFoundError as exc:
except_func(exc)
else:
try:
try_func()
except EnvironmentError as exc:
if exc.errno != ENOENT:
raise
else:
except_func(exc)
def _try_except_permissionerror_iter(try_iter, except_iter):
if sys.version_info >= (3, 3):
try:
for x in try_iter():
yield x
except PermissionError as exc:
for x in except_iter(exc):
yield x
else:
try:
for x in try_iter():
yield x
except EnvironmentError as exc:
if exc.errno not in (EPERM, EACCES):
raise
else:
for x in except_iter(exc):
yield x
def _win32_get_unique_path_id(path):
@@ -220,10 +265,7 @@ class _WindowsFlavour(_Flavour):
is_supported = (os.name == 'nt')
drive_letters = (
set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
set(chr(x) for x in range(ord('A'), ord('Z') + 1))
)
drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
ext_namespace_prefix = '\\\\?\\'
reserved_names = (
@@ -283,12 +325,28 @@ class _WindowsFlavour(_Flavour):
def casefold_parts(self, parts):
return [p.lower() for p in parts]
def resolve(self, path):
def resolve(self, path, strict=False):
s = str(path)
if not s:
return os.getcwd()
previous_s = None
if _getfinalpathname is not None:
return self._ext_to_normal(_getfinalpathname(s))
if strict:
return self._ext_to_normal(_getfinalpathname(s))
else:
# End of the path after the first one not found
tail_parts = []
while True:
try:
s = self._ext_to_normal(_getfinalpathname(s))
except FileNotFoundError:
previous_s = s
s, tail = os.path.split(s)
tail_parts.append(tail)
if previous_s == s:
return path
else:
return os.path.join(s, *reversed(tail_parts))
# Means fallback on absolute
return None
@@ -393,7 +451,7 @@ class _PosixFlavour(_Flavour):
def casefold_parts(self, parts):
return parts
def resolve(self, path):
def resolve(self, path, strict=False):
sep = self.sep
accessor = path._accessor
seen = {}
@@ -424,9 +482,10 @@ class _PosixFlavour(_Flavour):
try:
target = accessor.readlink(newpath)
except OSError as e:
if e.errno != EINVAL:
if e.errno != EINVAL and strict:
raise
# Not a symlink
# Not a symlink, or non-strict mode. We just leave the path
# untouched.
path = newpath
else:
seen[newpath] = None # not resolved symlink
@@ -463,6 +522,7 @@ class _PosixFlavour(_Flavour):
raise RuntimeError("Can't determine home directory "
"for %r" % username)
_windows_flavour = _WindowsFlavour()
_posix_flavour = _PosixFlavour()
@@ -495,6 +555,8 @@ class _NormalAccessor(_Accessor):
listdir = _wrap_strfunc(os.listdir)
scandir = _wrap_strfunc(os_scandir)
chmod = _wrap_strfunc(os.chmod)
if hasattr(os, "lchmod"):
@@ -541,27 +603,6 @@ _normal_accessor = _NormalAccessor()
# Globbing helpers
#
@contextmanager
def _cached(func):
try:
func.__cached__
yield func
except AttributeError:
cache = {}
def wrapper(*args):
try:
return cache[args]
except KeyError:
value = cache[args] = func(*args)
return value
wrapper.__cached__ = True
try:
yield wrapper
finally:
cache.clear()
def _make_selector(pattern_parts):
pat = pattern_parts[0]
child_parts = pattern_parts[1:]
@@ -576,6 +617,7 @@ def _make_selector(pattern_parts):
cls = _PreciseSelector
return cls(pat, child_parts)
if hasattr(functools, "lru_cache"):
_make_selector = functools.lru_cache()(_make_selector)
@@ -589,8 +631,10 @@ class _Selector:
self.child_parts = child_parts
if child_parts:
self.successor = _make_selector(child_parts)
self.dironly = True
else:
self.successor = _TerminatingSelector()
self.dironly = False
def select_from(self, parent_path):
"""Iterate over all child paths of `parent_path` matched by this
@@ -598,13 +642,15 @@ class _Selector:
path_cls = type(parent_path)
is_dir = path_cls.is_dir
exists = path_cls.exists
listdir = parent_path._accessor.listdir
return self._select_from(parent_path, is_dir, exists, listdir)
scandir = parent_path._accessor.scandir
if not is_dir(parent_path):
return iter([])
return self._select_from(parent_path, is_dir, exists, scandir)
class _TerminatingSelector:
def _select_from(self, parent_path, is_dir, exists, listdir):
def _select_from(self, parent_path, is_dir, exists, scandir):
yield parent_path
@@ -614,14 +660,20 @@ class _PreciseSelector(_Selector):
self.name = name
_Selector.__init__(self, child_parts)
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
path = parent_path._make_child_relpath(self.name)
if (is_dir if self.dironly else exists)(path):
for p in self.successor._select_from(
path, is_dir, exists, scandir):
yield p
def except_iter(exc):
return
path = parent_path._make_child_relpath(self.name)
if exists(path):
for p in self.successor._select_from(
path, is_dir, exists, listdir):
yield p
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
class _WildcardSelector(_Selector):
@@ -630,17 +682,26 @@ class _WildcardSelector(_Selector):
self.pat = re.compile(fnmatch.translate(pat))
_Selector.__init__(self, child_parts)
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
cf = parent_path._flavour.casefold
entries = list(scandir(parent_path))
for entry in entries:
if not self.dironly or entry.is_dir():
name = entry.name
casefolded = cf(name)
if self.pat.match(casefolded):
path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(
path, is_dir, exists, scandir):
yield p
def except_iter(exc):
return
cf = parent_path._flavour.casefold
for name in listdir(parent_path):
casefolded = cf(name)
if self.pat.match(casefolded):
path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(
path, is_dir, exists, listdir):
yield p
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
class _RecursiveWildcardSelector(_Selector):
@@ -648,31 +709,46 @@ class _RecursiveWildcardSelector(_Selector):
def __init__(self, pat, child_parts):
_Selector.__init__(self, child_parts)
def _iterate_directories(self, parent_path, is_dir, listdir):
def _iterate_directories(self, parent_path, is_dir, scandir):
yield parent_path
for name in listdir(parent_path):
path = parent_path._make_child_relpath(name)
if is_dir(path):
for p in self._iterate_directories(path, is_dir, listdir):
yield p
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
def try_iter():
entries = list(scandir(parent_path))
for entry in entries:
if entry.is_dir() and not entry.is_symlink():
path = parent_path._make_child_relpath(entry.name)
for p in self._iterate_directories(path, is_dir, scandir):
yield p
def except_iter(exc):
return
with _cached(listdir) as listdir:
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
yielded = set()
try:
successor_select = self.successor._select_from
for starting_point in self._iterate_directories(
parent_path, is_dir, listdir):
parent_path, is_dir, scandir):
for p in successor_select(
starting_point, is_dir, exists, listdir):
starting_point, is_dir, exists, scandir):
if p not in yielded:
yield p
yielded.add(p)
finally:
yielded.clear()
def except_iter(exc):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
#
# Public API
@@ -743,13 +819,25 @@ class PurePath(object):
for a in args:
if isinstance(a, PurePath):
parts += a._parts
elif isinstance(a, basestring):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
else:
raise TypeError(
"argument should be a path or str object, not %r"
% type(a))
if sys.version_info >= (3, 6):
a = os.fspath(a)
else:
# duck typing for older Python versions
if hasattr(a, "__fspath__"):
a = a.__fspath__()
if isinstance(a, str):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
# also handle unicode for PY2 (six.text_type = unicode)
elif six.PY2 and isinstance(a, six.text_type):
# cast to str using filesystem encoding
parts.append(a.encode(sys.getfilesystemencoding()))
else:
raise TypeError(
"argument should be a str object or an os.PathLike "
"object returning str, not %r"
% type(a))
return cls._flavour.parse_parts(parts)
@classmethod
@@ -783,7 +871,7 @@ class PurePath(object):
return cls._flavour.join(parts)
def _init(self):
# Overriden in concrete Path
# Overridden in concrete Path
pass
def _make_child(self, args):
@@ -802,6 +890,9 @@ class PurePath(object):
self._parts) or '.'
return self._str
def __fspath__(self):
return str(self)
def as_posix(self):
"""Return the string representation of the path with forward (/)
slashes."""
@@ -982,7 +1073,7 @@ class PurePath(object):
cf = self._flavour.casefold_parts
if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
raise ValueError("{!r} does not start with {!r}"
raise ValueError("{0!r} does not start with {1!r}"
.format(str(self), str(formatted)))
return self._from_parsed_parts('', root if n == 1 else '',
abs_parts[n:])
@@ -1070,6 +1161,12 @@ class PurePath(object):
return True
# Can't subclass os.PathLike from PurePath and keep the constructor
# optimizations in PurePath._parse_args().
if sys.version_info >= (3, 6):
os.PathLike.register(PurePath)
class PurePosixPath(PurePath):
_flavour = _posix_flavour
__slots__ = ()
@@ -1156,8 +1253,8 @@ class Path(PurePath):
return cls(cls()._flavour.gethomedir(None))
def samefile(self, other_path):
"""Return whether `other_file` is the same or not as this file.
(as returned by os.path.samefile(file, other_file)).
"""Return whether other_path is the same or not as this file
(as returned by os.path.samefile()).
"""
if hasattr(os.path, "samestat"):
st = self.stat()
@@ -1191,6 +1288,8 @@ class Path(PurePath):
"""Iterate over this subtree and yield all existing files (of any
kind, including directories) matching the given pattern.
"""
if not pattern:
raise ValueError("Unacceptable pattern: {0!r}".format(pattern))
pattern = self._flavour.casefold(pattern)
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
if drv or root:
@@ -1229,7 +1328,7 @@ class Path(PurePath):
obj._init(template=self)
return obj
def resolve(self):
def resolve(self, strict=False):
"""
Make the path absolute, resolving all symlinks on the way and also
normalizing it (for example turning slashes into backslashes under
@@ -1237,7 +1336,7 @@ class Path(PurePath):
"""
if self._closed:
self._raise_closed()
s = self._flavour.resolve(self)
s = self._flavour.resolve(self, strict=strict)
if s is None:
# No symlink resolution => for consistency, raise an error if
# the path doesn't exist or is forbidden
@@ -1307,7 +1406,7 @@ class Path(PurePath):
if not isinstance(data, six.binary_type):
raise TypeError(
'data must be %s, not %s' %
(six.binary_type.__class__.__name__, data.__class__.__name__))
(six.binary_type.__name__, data.__class__.__name__))
with self.open(mode='wb') as f:
return f.write(data)
@@ -1318,7 +1417,7 @@ class Path(PurePath):
if not isinstance(data, six.text_type):
raise TypeError(
'data must be %s, not %s' %
(six.text_type.__class__.__name__, data.__class__.__name__))
(six.text_type.__name__, data.__class__.__name__))
with self.open(mode='w', encoding=encoding, errors=errors) as f:
return f.write(data)
@@ -1346,27 +1445,26 @@ class Path(PurePath):
os.close(fd)
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
def helper(exc):
if not exist_ok or not self.is_dir():
raise exc
"""
Create a new directory at this given path.
"""
if self._closed:
self._raise_closed()
if not parents:
_try_except_fileexistserror(
lambda: self._accessor.mkdir(self, mode),
helper)
else:
try:
_try_except_fileexistserror(
lambda: self._accessor.mkdir(self, mode),
helper)
except OSError as e:
if e.errno != ENOENT:
raise
self.parent.mkdir(parents=True)
self._accessor.mkdir(self, mode)
def _try_func():
self._accessor.mkdir(self, mode)
def _exc_func(exc):
if not parents or self.parent == self:
raise exc
self.parent.mkdir(parents=True, exist_ok=True)
self.mkdir(mode, parents=False, exist_ok=exist_ok)
try:
_try_except_filenotfounderror(_try_func, _exc_func)
except OSError:
if not exist_ok or not self.is_dir():
raise
def chmod(self, mode):
"""
@@ -1564,3 +1662,9 @@ class PosixPath(Path, PurePosixPath):
class WindowsPath(Path, PureWindowsPath):
__slots__ = ()
def owner(self):
raise NotImplementedError("Path.owner() is unsupported on this system")
def group(self):
raise NotImplementedError("Path.group() is unsupported on this system")
+1 -1
View File
@@ -75,7 +75,7 @@ if sys.platform != 'win32':
from .pty_spawn import spawn, spawnu
from .run import run, runu
__version__ = '4.5.0'
__version__ = '4.6.0'
__revision__ = ''
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
'which', 'split_command_line', '__version__', '__revision__']
+12 -5
View File
@@ -18,11 +18,6 @@ from .exceptions import EOF
from .utils import string_types
class PopenSpawn(SpawnBase):
if PY3:
crlf = '\n'.encode('ascii')
else:
crlf = '\n'
def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None,
logfile=None, cwd=None, env=None, encoding=None,
codec_errors='strict', preexec_fn=None):
@@ -30,6 +25,18 @@ class PopenSpawn(SpawnBase):
searchwindowsize=searchwindowsize, logfile=logfile,
encoding=encoding, codec_errors=codec_errors)
# Note that `SpawnBase` initializes `self.crlf` to `\r\n`
# because the default behaviour for a PTY is to convert
# incoming LF to `\r\n` (see the `onlcr` flag and
# https://stackoverflow.com/a/35887657/5397009). Here we set
# it to `os.linesep` because that is what the spawned
# application outputs by default and `popen` doesn't translate
# anything.
if encoding is None:
self.crlf = os.linesep.encode ("ascii")
else:
self.crlf = self.string_type (os.linesep)
kwargs = dict(bufsize=0, stdin=subprocess.PIPE,
stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
cwd=cwd, preexec_fn=preexec_fn, env=env)
+15 -7
View File
@@ -259,7 +259,7 @@ class pxssh (spawn):
sync_multiplier=1, check_local_ip=True,
password_regex=r'(?i)(?:password:)|(?:passphrase for key)',
ssh_tunnels={}, spawn_local_ssh=True,
sync_original_prompt=True):
sync_original_prompt=True, ssh_config=None):
'''This logs the user into the given server.
It uses
@@ -294,8 +294,15 @@ class pxssh (spawn):
session to do so. Setting this option to `False` and not having an active session
will trigger an error.
Set ``ssh_key`` to `True` to force passing the current SSH authentication socket to the
Set ``ssh_key`` to a file path to an SSH private key to use that SSH key
for the session authentication.
Set ``ssh_key`` to `True` to force passing the current SSH authentication socket
to the desired ``hostname``.
Set ``ssh_config`` to a file path string of an SSH client config file to pass that
file to the client to handle itself. You may set any options you wish in here, however
doing so will require you to post extra information that you may not want to if you
run into issues.
'''
session_regex_array = ["(?i)are you sure you want to continue connecting", original_prompt, password_regex, "(?i)permission denied", "(?i)terminal type", TIMEOUT]
@@ -310,6 +317,10 @@ class pxssh (spawn):
ssh_options = ssh_options + " -o'NoHostAuthenticationForLocalhost=yes'"
if self.force_password:
ssh_options = ssh_options + ' ' + self.SSH_OPTS
if ssh_config is not None:
if spawn_local_ssh and not os.path.isfile(ssh_config):
raise ExceptionPxssh('SSH config does not exist or is not a file.')
ssh_options = ssh_options + '-F ' + ssh_config
if port is not None:
ssh_options = ssh_options + ' -p %s'%(str(port))
if ssh_key is not None:
@@ -317,11 +328,8 @@ class pxssh (spawn):
if ssh_key==True:
ssh_options = ssh_options + ' -A'
else:
try:
if spawn_local_ssh:
os.path.isfile(ssh_key)
except:
raise ExceptionPxssh('private ssh key does not exist')
if spawn_local_ssh and not os.path.isfile(ssh_key):
raise ExceptionPxssh('private ssh key does not exist or is not a file.')
ssh_options = ssh_options + ' -i %s' % (ssh_key)
# SSH tunnels, make sure you know what you're putting into the lists
+1 -1
View File
@@ -165,7 +165,7 @@ def poll_ignore_interrupts(fds, timeout=None):
poller = select.poll()
for fd in fds:
poller.register(fd)
poller.register(fd, select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR)
while True:
try:
+7 -1
View File
@@ -1 +1,7 @@
__version__ = '0.0.5'
from __future__ import print_function, absolute_import
__version__ = "0.1.2"
__all__ = ["Finder", "WindowsFinder", "SystemPath"]
from .pythonfinder import Finder
from .models import SystemPath, WindowsFinder
+1 -1
View File
@@ -8,5 +8,5 @@ PYTHONFINDER_PACKAGE = os.path.dirname(PYTHONFINDER_MAIN)
from pythonfinder import cli as cli
if __name__ == '__main__':
if __name__ == "__main__":
sys.exit(cli())
+41 -21
View File
@@ -5,35 +5,55 @@ import click
import crayons
import sys
from . import __version__
from .pythonfinder import PythonFinder
from .pythonfinder import Finder
# @click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
@click.command()
@click.option('--find', default=False, nargs=1, help="Find a specific python version.")
@click.option('--findall', is_flag=True, default=False, help="Find all python versions.")
@click.option("--find", default=False, nargs=1, help="Find a specific python version.")
@click.option("--which", default=False, nargs=1, help="Run the which commend.")
@click.option(
"--findall", is_flag=True, default=False, help="Find all python versions."
)
@click.option(
"--version", is_flag=True, default=False, help="Display PythonFinder version."
)
# @click.version_option(prog_name=crayons.normal('pyfinder', bold=True), version=__version__)
@click.pass_context
def cli(
ctx, find=False, findall=False
):
if not find and not findall:
click.echo('Please provide a command', color='red')
sys.exit(1)
if find:
if any([find.startswith('{0}'.format(n)) for n in range(10)]):
found = PythonFinder.from_version(find.strip())
else:
found = PythonFinder.from_line()
if found:
click.echo('Found Python Version: {0}'.format(found), color='white')
sys.exit(0)
else:
#TODO: implement this
click.echo('This is not yet implemented')
def cli(ctx, find=False, which=False, findall=False, version=False):
if version:
click.echo(
"{0} version {1}".format(
crayons.white("PythonFinder", bold=True), crayons.yellow(__version__)
)
)
sys.exit(0)
finder = Finder()
if find:
if any([find.startswith("{0}".format(n)) for n in range(10)]):
found = finder.find_python_version(find.strip())
else:
found = finder.system_path.python_executables
if found:
click.echo("Found Python Version: {0}".format(found), color="white")
sys.exit(0)
else:
click.echo("Failed to find matching executable...")
sys.exit(1)
elif which:
found = finder.system_path.which(which.strip())
if found:
click.echo("Found Executable: {0}".format(found), color="white")
sys.exit(0)
else:
click.echo("Failed to find matching executable...")
sys.exit(1)
else:
click.echo("Please provide a command", color="red")
sys.exit(1)
sys.exit()
if __name__ == '__main__':
if __name__ == "__main__":
cli()
+16
View File
@@ -0,0 +1,16 @@
# -*- coding=utf-8 -*-
import os
import platform
import sys
PYENV_INSTALLED = bool(os.environ.get("PYENV_SHELL")) or bool(
os.environ.get("PYENV_ROOT")
)
PYENV_ROOT = os.path.expandvars(os.environ.get("PYENV_ROOT", "~/.pyenv"))
IS_64BIT_OS = None
SYSTEM_ARCH = platform.architecture()[0]
if sys.maxsize > 2 ** 32:
IS_64BIT_OS = platform.machine() == "AMD64"
else:
IS_64BIT_OS = False
+80
View File
@@ -0,0 +1,80 @@
# -*- coding=utf-8 -*-
import abc
import operator
import six
from ..utils import KNOWN_EXTS
@six.add_metaclass(abc.ABCMeta)
class BaseFinder(object):
def get_versions(self):
"""Return the available versions from the finder"""
raise NotImplementedError
@classmethod
def create(cls):
raise NotImplementedError
@property
def version_paths(self):
return self.versions.values()
@property
def expanded_paths(self):
return (p.paths.values() for p in self.version_paths)
class BasePath(object):
def which(self, name):
"""Search in this path for an executable.
:param executable: The name of an executable to search for.
:type executable: str
:returns: :class:`~pythonfinder.models.PathEntry` instance.
"""
valid_names = [
"{0}.{1}".format(name, ext).lower() if ext else "{0}".format(name).lower()
for ext in KNOWN_EXTS
]
finder = filter(operator.attrgetter("is_executable"), self.children.values())
name_getter = operator.attrgetter("path.name")
return next(
(child for child in finder if name_getter(child).lower() in valid_names),
None,
)
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
"""Search or self for the specified Python version and return the first match.
:param major: Major version number.
:type major: int
:param minor: Minor python version, defaults to None
:param minor: int, optional
:param patch: Patch python version, defaults to None
:param patch: int, optional
:returns: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
"""
version_matcher = operator.methodcaller(
"matches", major, minor=minor, patch=patch, pre=pre, dev=dev
)
is_py = operator.attrgetter("is_python")
py_version = operator.attrgetter("as_python")
if not self.is_dir:
if self.is_python:
return self if version_matcher(self.as_python) else None
return
finder = (c for c in self.children.values() if is_py(c) and py_version(c))
py_filter = filter(
None, filter(lambda c: version_matcher(py_version(c)), finder)
)
version_sort = operator.attrgetter("py_version.version")
return next(
(c for c in sorted(py_filter, key=version_sort, reverse=True)), None
)
from .path import SystemPath
from .windows import WindowsFinder
from .python import PythonVersion
+297
View File
@@ -0,0 +1,297 @@
# -*- coding=utf-8 -*-
import attr
import operator
import os
import sys
from collections import defaultdict
from . import BasePath
from .python import PythonVersion
from ..environment import PYENV_INSTALLED, PYENV_ROOT
from ..utils import (
optional_instance_of,
filter_pythons,
path_is_known_executable,
is_python_name,
ensure_path,
)
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
@attr.s
class SystemPath(object):
paths = attr.ib(default=attr.Factory(defaultdict))
_executables = attr.ib(default=attr.Factory(list))
_python_executables = attr.ib(default=attr.Factory(list))
path_order = attr.ib(default=attr.Factory(list))
python_version_dict = attr.ib()
only_python = attr.ib(default=False)
pyenv_finder = attr.ib(default=None, validator=optional_instance_of("PyenvPath"))
system = attr.ib(default=False)
@property
def executables(self):
if not self._executables:
self._executables = [p for p in self.paths.values() if p.is_executable]
return self._executables
@property
def python_executables(self):
if not self._python_executables:
self._python_executables = [p for p in self.paths.values() if p.is_python]
return self._python_executables
@python_version_dict.default
def get_python_version_dict(self):
version_dict = defaultdict(list)
for p in self.python_executables:
try:
version_object = PythonVersion.from_path(p)
except ValueError:
continue
version_dict[version_object.version_tuple].append(version_object)
return version_dict
def __attrs_post_init__(self):
#: slice in pyenv
if not self.__class__ == SystemPath:
return
if os.name == "nt":
self._setup_windows()
if PYENV_INSTALLED:
self._setup_pyenv()
venv = os.environ.get('VIRTUAL_ENV')
if venv:
if os.name == 'nt':
bin_dir = 'Scripts'
else:
bin_dir = 'bin'
p = Path(venv)
self.path_order = [(p / bin_dir).as_posix()] + self.path_order
self.paths[p] = PathEntry.create(
path=p, is_root=True, only_python=False
)
if self.system:
syspath = Path(sys.executable)
self.path_order = [syspath.parent.as_posix()] + self.path_order
self.paths[syspath.parent.as_posix()] = PathEntry.create(
path=syspath.parent, is_root=True, only_python=True
)
def _setup_pyenv(self):
from .pyenv import PyenvFinder
last_pyenv = next(
(p for p in reversed(self.path_order) if PYENV_ROOT.lower() in p.lower()),
None,
)
pyenv_index = self.path_order.index(last_pyenv)
self.pyenv_finder = PyenvFinder.create(root=PYENV_ROOT)
# paths = (v.paths.values() for v in self.pyenv_finder.versions.values())
root_paths = (
p for path in self.pyenv_finder.expanded_paths for p in path if p.is_root
)
before_path = self.path_order[: pyenv_index + 1]
after_path = self.path_order[pyenv_index + 2 :]
self.path_order = (
before_path + [p.path.as_posix() for p in root_paths] + after_path
)
self.paths.update({p.path: p for p in root_paths})
def _setup_windows(self):
from .windows import WindowsFinder
self.windows_finder = WindowsFinder.create()
root_paths = (p for p in self.windows_finder.paths if p.is_root)
path_addition = [p.path.as_posix() for p in root_paths]
self.path_order = self.path_order[:] + path_addition
self.paths.update({p.path: p for p in root_paths})
def get_path(self, path):
_path = self.paths.get(path)
if not _path and path in self.path_order:
self.paths[path] = PathEntry.create(
path=path, is_root=True, only_python=self.only_python
)
return self.paths.get(path)
def which(self, executable):
"""Search for an executable on the path.
:param executable: Name of the executable to be located.
:type executable: str
:returns: :class:`~pythonfinder.models.PathEntry` object.
"""
sub_which = operator.methodcaller("which", name=executable)
return next(
(sub_which(self.get_path(k)) for k in self.path_order), None
)
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
"""Search for a specific python version on the path.
:param major: Major python version to search for.
:type major: int
:param minor: Minor python version to search for, defaults to None
:param minor: int, optional
:param path: Patch python version to search for, defaults to None
:param path: int, optional
:return: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
:rtype: :class:`~pythonfinder.models.PathEntry`
"""
sub_finder = operator.methodcaller(
"find_python_version", major, minor=minor, patch=patch, pre=pre, dev=dev
)
if os.name == "nt" and self.windows_finder:
windows_finder_version = sub_finder(self.windows_finder)
if windows_finder_version:
return windows_finder_version
paths = [self.get_path(k) for k in self.path_order]
path_filter = filter(None, [sub_finder(p) for p in paths])
version_sort = operator.attrgetter("as_python.version")
return next(
(c for c in sorted(path_filter, key=version_sort, reverse=True)), None
)
@classmethod
def create(cls, path=None, system=False, only_python=False):
"""Create a new :class:`pythonfinder.models.SystemPath` instance.
:param path: Search path to prepend when searching, defaults to None
:param path: str, optional
:param system: Whether to use the running python by default instead of searching, defaults to False
:param system: bool, optional
:param only_python: Whether to search only for python executables, defaults to False
:param only_python: bool, optional
:return: A new :class:`pythonfinder.models.SystemPath` instance.
:rtype: :class:`pythonfinder.models.SystemPath`
"""
path_entries = defaultdict(PathEntry)
paths = os.environ.get("PATH").split(os.pathsep)
if path:
paths = [path] + paths
_path_objects = [ensure_path(p.strip('"')) for p in paths]
paths = [p.as_posix() for p in _path_objects]
path_entries.update(
{
p.as_posix(): PathEntry.create(
path=p, is_root=True, only_python=only_python
)
for p in _path_objects
}
)
return cls(paths=path_entries, path_order=paths, only_python=only_python, system=system)
@attr.s
class PathEntry(BasePath):
path = attr.ib(default=None, validator=optional_instance_of(Path))
_children = attr.ib(default=attr.Factory(dict))
is_root = attr.ib(default=True)
only_python = attr.ib(default=False)
py_version = attr.ib(default=None)
pythons = attr.ib(default=None)
def _filter_children(self):
if self.only_python:
children = filter_pythons(self.path)
else:
children = self.path.iterdir()
return children
@property
def children(self):
if not self._children and self.is_dir and self.is_root:
self._children = {
child.as_posix(): PathEntry(path=child, is_root=False)
for child in self._filter_children()
}
return self._children
@property
def as_python(self):
if not self.is_dir and self.is_python:
if not self.py_version:
try:
from .python import PythonVersion
self.py_version = PythonVersion.from_path(self.path)
except ValueError:
self.py_version = None
return self.py_version
@classmethod
def create(cls, path, is_root=False, only_python=False, pythons=None):
"""Helper method for creating new :class:`pythonfinder.models.PathEntry` instances.
:param path: Path to the specified location.
:type path: str
:param is_root: Whether this is a root from the environment PATH variable, defaults to False
:param is_root: bool, optional
:param only_python: Whether to search only for python executables, defaults to False
:param only_python: bool, optional
:param pythons: A dictionary of existing python objects (usually from a finder), defaults to None
:param pythons: dict, optional
:return: A new instance of the class.
:rtype: :class:`pythonfinder.models.PathEntry`
"""
target = ensure_path(path)
_new = cls(
path=target, is_root=is_root, only_python=only_python, pythons=pythons
)
if pythons and only_python:
children = {}
for pth, python in pythons.items():
pth = ensure_path(pth)
children[pth.as_posix()] = PathEntry(
path=pth, is_root=False, only_python=only_python, py_version=python
)
_new._children = children
return _new
@property
def name(self):
return self.path.name
@property
def is_dir(self):
return self.path.is_dir()
@property
def is_executable(self):
return path_is_known_executable(self.path)
@property
def is_python(self):
return self.is_executable and (
self.py_version or is_python_name(self.path.name)
)
@attr.s
class VersionPath(SystemPath):
base = attr.ib(default=None, validator=optional_instance_of(Path))
@classmethod
def create(cls, path, only_python=True, pythons=None):
"""Accepts a path to a base python version directory.
Generates the pyenv version listings for it"""
path = ensure_path(path)
path_entries = defaultdict(PathEntry)
if not path.name.lower() in ["scripts", "bin"]:
bin_name = "Scripts" if os.name == "nt" else "bin"
bin_dir = path / bin_name
else:
bin_dir = path
current_entry = PathEntry.create(
bin_dir, is_root=True, only_python=True, pythons=pythons
)
path_entries[bin_dir.as_posix()] = current_entry
return cls(base=bin_dir, paths=path_entries)
+39
View File
@@ -0,0 +1,39 @@
# -*- coding=utf-8 -*-
import attr
from collections import defaultdict
from . import BaseFinder
from .path import VersionPath
from .python import PythonVersion
from ..utils import optional_instance_of, ensure_path
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
@attr.s
class PyenvFinder(BaseFinder):
root = attr.ib(default=None, validator=optional_instance_of(Path))
versions = attr.ib()
@versions.default
def get_versions(self):
versions = defaultdict(VersionPath)
for p in self.root.glob("versions/*"):
version = PythonVersion.parse(p.name)
version_tuple = (
version.get("major"),
version.get("minor"),
version.get("patch"),
version.get("is_prerelease"),
version.get("is_devrelease"),
)
versions[version_tuple] = VersionPath.create(path=p, only_python=True)
return versions
@classmethod
def create(cls, root):
root = ensure_path(root)
return cls(root=root)
+169
View File
@@ -0,0 +1,169 @@
# -*- coding=utf-8 -*-
import attr
import copy
import platform
from packaging.version import parse as parse_version, Version
from ..environment import SYSTEM_ARCH
from ..utils import _filter_none, optional_instance_of, get_python_version, ensure_path
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
@attr.s
class PythonVersion(object):
major = attr.ib(default=0)
minor = attr.ib(default=None)
patch = attr.ib(default=None)
is_prerelease = attr.ib(default=False)
is_postrelease = attr.ib(default=False)
is_devrelease = attr.ib(default=False)
version = attr.ib(default=None, validator=optional_instance_of(Version))
architecture = attr.ib(default=None)
comes_from = attr.ib(default=None)
executable = attr.ib(default=None)
@property
def version_tuple(self):
"""Provides a version tuple for using as a dictionary key.
:return: A tuple describing the python version meetadata contained.
:rtype: tuple
"""
return (
self.major,
self.minor,
self.patch,
self.is_prerelease,
self.is_devrelease,
)
def matches(self, major, minor=None, patch=None, pre=False, dev=False):
return (
self.major == major
and (minor is None or self.minor == minor)
and (patch is None or self.patch == patch)
and (pre is None or self.is_prerelease == pre)
and (dev is None or self.is_devrelease == dev)
)
def as_major(self):
self_dict = attr.asdict(self, recurse=False, filter=_filter_none).copy()
self_dict.update({"minor": None, "patch": None})
return self.create(**self_dict)
def as_minor(self):
self_dict = attr.asdict(self, recurse=False, filter=_filter_none).copy()
self_dict.update({"patch": None})
return self.create(**self_dict)
@classmethod
def parse(cls, version):
"""Parse a valid version string into a dictionary
Raises:
ValueError -- Unable to parse version string
ValueError -- Not a valid python version
:param version: A valid version string
:type version: str
:return: A dictionary with metadata about the specified python version.
:rtype: dict.
"""
try:
version = parse_version(version)
except TypeError:
raise ValueError("Unable to parse version: %s" % version)
if not version or not version.release:
raise ValueError("Not a valid python version: %r" % version)
return
if len(version.release) >= 3:
major, minor, patch = version.release[:3]
elif len(version.release) == 2:
major, minor = version.release
patch = None
else:
major = version.release[0]
minor = None
patch = None
return {
"major": major,
"minor": minor,
"patch": patch,
"is_prerelease": version.is_prerelease,
"is_postrelease": version.is_postrelease,
"is_devrelease": version.is_devrelease,
"version": version,
}
@classmethod
def from_path(cls, path):
"""Parses a python version from a system path.
Raises:
ValueError -- Not a valid python path
:param path: A string or :class:`~pythonfinder.models.path.PathEntry`
:type path: str or :class:`~pythonfinder.models.path.PathEntry` instance
:param launcher_entry: A python launcher environment object.
:return: An instance of a PythonVersion.
:rtype: :class:`~pythonfinder.models.python.PythonVersion`
"""
from .path import PathEntry
if not isinstance(path, PathEntry):
path = PathEntry(path)
if not path.is_python:
raise ValueError("Not a valid python path: %s" % path.path)
return
py_version, _ = get_python_version(str(path.path))
instance_dict = cls.parse(py_version)
if not isinstance(instance_dict.get("version"), Version):
raise ValueError("Not a valid python path: %s" % path.path)
return
architecture, _ = platform.architecture(path.path.as_posix())
instance_dict.update({"comes_from": path, "architecture": architecture})
return cls(**instance_dict)
@classmethod
def from_windows_launcher(cls, launcher_entry):
"""Create a new PythonVersion instance from a Windows Launcher Entry
:param launcher_entry: A python launcher environment object.
:return: An instance of a PythonVersion.
:rtype: :class:`~pythonfinder.models.python.PythonVersion`
"""
from .path import PathEntry
creation_dict = cls.parse(launcher_entry.info.version)
base_path = ensure_path(launcher_entry.info.install_path.__getattr__(""))
default_path = base_path / "python.exe"
if not default_path.exists():
default_path = base_path / "Scripts" / "python.exe"
exe_path = ensure_path(
getattr(launcher_entry.info.install_path, "executable_path", default_path)
)
creation_dict.update(
{
"architecture": getattr(
launcher_entry, "sys_architecture", SYSTEM_ARCH
),
"executable": exe_path,
}
)
py_version = cls.create(**creation_dict)
comes_from = PathEntry.create(exe_path, only_python=True)
comes_from.py_version = copy.deepcopy(py_version)
py_version.comes_from = comes_from
return py_version
@classmethod
def create(cls, **kwargs):
return cls(**kwargs)
+52
View File
@@ -0,0 +1,52 @@
# -*- coding=utf-8 -*-
import attr
import operator
from collections import defaultdict
from . import BaseFinder
from .path import PathEntry
from .python import PythonVersion
from ..utils import ensure_path
@attr.s
class WindowsFinder(BaseFinder):
paths = attr.ib(default=attr.Factory(list))
version_list = attr.ib(default=attr.Factory(list))
versions = attr.ib()
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
version_matcher = operator.methodcaller(
"matches", major, minor=minor, patch=patch, pre=pre, dev=dev
)
py_filter = filter(
None, filter(lambda c: version_matcher(c), self.version_list)
)
version_sort = operator.attrgetter("version")
return next(
(c.comes_from for c in sorted(py_filter, key=version_sort, reverse=True)), None
)
@versions.default
def get_versions(self):
versions = defaultdict(PathEntry)
from pythonfinder._vendor.pep514tools import environment as pep514env
env_versions = pep514env.findall()
path = None
for version_object in env_versions:
path = ensure_path(version_object.info.install_path.__getattr__(""))
py_version = PythonVersion.from_windows_launcher(version_object)
self.version_list.append(py_version)
base_dir = PathEntry.create(
path,
is_root=True,
only_python=True,
pythons={py_version.comes_from.path: py_version},
)
versions[py_version.version_tuple[:5]] = base_dir
self.paths.append(base_dir)
return versions
@classmethod
def create(cls):
return cls()
+49 -224
View File
@@ -1,234 +1,59 @@
# -*- coding=utf-8 -*-
import os
import sys
import delegator
import platform
from packaging.version import parse as parse_version
from collections import defaultdict
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
import six
from .models import SystemPath
PYENV_INSTALLED = (bool(os.environ.get('PYENV_SHELL')) or bool(os.environ.get('PYENV_ROOT')))
PYENV_ROOT = os.environ.get('PYENV_ROOT', os.path.expanduser('~/.pyenv'))
IS_64BIT_OS = None
SYSTEM_ARCH = platform.architecture()[0]
class Finder(object):
def __init__(self, path=None, system=False):
self.path_prepend = path
self.system = system
self._system_path = None
self._windows_finder = None
if sys.maxsize > 2**32:
IS_64BIT_OS = (platform.machine() == 'AMD64')
else:
IS_64BIT_OS = False
@property
def system_path(self):
if not self._system_path:
self._system_path = SystemPath.create(
path=self.path_prepend, system=self.system
)
return self._system_path
@property
def windows_finder(self):
if os.name == "nt" and not self._windows_finder:
from .models import WindowsFinder
def shellquote(s):
"""Prepares a string for the shell (on Windows too!)
self._windows_finder = WindowsFinder()
return self._windows_finder
Only for use on grouped arguments (passed as a string to Popen)
"""
if s is None:
return None
# Additional escaping for windows paths
if os.name == 'nt':
s = "{}".format(s.replace("\\", "\\\\"))
def which(self, exe):
return self.system_path.which(exe)
return '"' + s.replace("'", "'\\''") + '"'
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
if (
major
and not minor
and not patch
and not pre
and not dev
and isinstance(major, six.string_types)
):
if "." in major:
from .models import PythonVersion
class PathFinder(object):
WHICH = {}
def __init__(self, path=None):
self.path = path if path else os.environ.get('PATH')
self._populate_which_dict()
@classmethod
def which(cls, cmd):
if not cls.WHICH:
cls._populate_which_dict()
return cls.WHICH.get(cmd, cmd)
@classmethod
def _populate_which_dict(cls):
for path in os.environ.get('PATH', '').split(os.pathsep):
path = os.path.expandvars(path)
files = os.listdir(path)
for fn in files:
full_path = os.sep.join([path, fn])
if os.access(full_path, os.X_OK) and not os.path.isdir(full_path):
if not cls.WHICH.get(fn):
cls.WHICH[fn] = full_path
base_exec = os.path.splitext(fn)[0]
if not cls.WHICH.get(base_exec):
cls.WHICH[base_exec] = full_path
class PythonFinder(PathFinder):
"""Find pythons given a specific version, path, or nothing."""
PYENV_VERSIONS = {}
PYTHON_VERSIONS = {}
PYTHON_PATHS = {}
MAX_PYTHON = {}
PYTHON_ARCHS = defaultdict(dict)
WHICH_PYTHON = {}
RUNTIMES = ['python', 'pypy', 'ipy', 'jython', 'pyston']
def __init__(self, path=None, version=None, full_version=None):
self.version = version
self.full_version = full_version
super(PythonFinder, self).__init__(path=path)
@classmethod
def from_line(cls, python):
if os.path.isabs(python) and os.access(python, os.X_OK):
return python
if python.startswith('py'):
windows_finder = python.split()
if len(windows_finder) > 1 and windows_finder[0] == 'py' and windows_finder[1].startswith('-'):
version = windows_finder[1].strip('-').split()[0]
return cls.from_version(version)
return cls.WHICH_PYTHON.get(python) or cls.which(python)
@classmethod
def from_version(cls, version, architecture=None):
guess = cls.PYTHON_VERSIONS.get(cls.MAX_PYTHON.get(version, version))
if guess:
return guess
if os.name == 'nt':
path = cls.from_windows_finder(version, architecture)
else:
parsed_version = parse_version(version)
full_version = parsed_version.base_version
if PYENV_INSTALLED:
path = cls.from_pyenv(full_version)
else:
path = cls._crawl_path_for_version(full_version)
if path and not isinstance(path, Path):
path = Path(path)
return path
@classmethod
def from_windows_finder(cls, version=None, arch=None):
if not cls.PYTHON_VERSIONS:
cls._populate_windows_python_versions()
if arch:
return cls.PYTHON_ARCHS[version][arch]
return cls.PYTHON_VERSIONS[version]
@classmethod
def _populate_windows_python_versions(cls):
from pythonfinder._vendor.pep514tools import environment
versions = environment.findall()
path = None
for version_object in versions:
path = Path(version_object.info.install_path.__getattr__('')).joinpath('python.exe')
version = version_object.info.sys_version
full_version = version_object.info.version
architecture = getattr(version_object, 'sys_architecture', SYSTEM_ARCH)
for v in [version, full_version, architecture]:
if not cls.PYTHON_VERSIONS.get(v):
cls.PYTHON_VERSIONS[v] = '{0}'.format(path)
cls.register_python(path, full_version, architecture)
@classmethod
def _populate_python_versions(cls):
import fnmatch
match_rules = ['*python', '*python?', '*python?.?', '*python?.?m']
runtime_execs = []
exts = list(filter(None, os.environ.get('PATHEXT', '').split(os.pathsep)))
for path in os.environ.get('PATH', '').split(os.pathsep):
path = os.path.expandvars(path)
from glob import glob
pythons = glob(os.sep.join([path, 'python*']))
execs = [match for rule in match_rules for match in fnmatch.filter(pythons, rule)]
for executable in execs:
exec_name = os.path.basename(executable)
if os.access(executable, os.X_OK):
runtime_execs.append(executable)
if not cls.WHICH_PYTHON.get(exec_name):
cls.WHICH_PYTHON[exec_name] = executable
for e in exts:
pext = executable + e
if os.access(pext, os.X_OK):
runtime_execs.append(pext)
for python in runtime_execs:
version_cmd = '{0} -c "import sys; print(sys.version.split()[0])"'.format(shellquote(python))
version = delegator.run(version_cmd).out.strip()
cls.register_python(python, version)
@classmethod
def _crawl_path_for_version(cls, version):
if not cls.PYTHON_VERSIONS:
cls._populate_python_versions()
return cls.PYTHON_VERSIONS.get(version)
@classmethod
def from_pyenv(cls, version):
if not cls.PYENV_VERSIONS:
cls.populate_pyenv_runtimes()
return cls.PYENV_VERSIONS[version]
@classmethod
def register_python(cls, path, full_version, pre=False, pyenv=False, arch=None):
if not arch:
import platform
arch, _ = platform.architecture()
parsed_version = parse_version(full_version)
if isinstance(parsed_version._version, str):
if arch == SYSTEM_ARCH or SYSTEM_ARCH.startswith(str(arch)):
cls.PYTHON_VERSIONS[parsed_version._version] = path
cls.MAX_PYTHON[parsed_version._version] = parsed_version._version
cls.PYTHON_VERSION[parsed_version._version] = parsed_version._version
cls.PYTHON_PATHS[path] = parsed_version._version
cls.PYTHON_ARCHS[parsed_version._version][arch] = path
return
pre = pre or parsed_version.is_prerelease
major_minor = '.'.join(['{0}'.format(v) for v in parsed_version._version.release[:2]])
major = '{0}'.format(parsed_version._version.release[0])
cls.PYTHON_PATHS[path] = full_version
if not pre and parsed_version > parse_version(cls.MAX_PYTHON.get(major_minor, '0.0.0')):
if major_minor != full_version:
if parsed_version > parse_version(cls.MAX_PYTHON.get(full_version, '0.0.0')):
cls.MAX_PYTHON[full_version] = parsed_version.base_version
cls.MAX_PYTHON[major_minor] = parsed_version.base_version
cls.PYTHON_VERSIONS[major_minor] = path
if arch == SYSTEM_ARCH or SYSTEM_ARCH.startswith(str(arch)):
if parsed_version > parse_version(cls.MAX_PYTHON.get(major, '0.0.0')):
cls.MAX_PYTHON[major] = parsed_version.base_version
cls.PYTHON_VERSIONS[major] = path
if not pyenv:
for v in [full_version, major_minor, major]:
if not cls.PYTHON_VERSIONS.get(v) or cls.MAX_PYTHON.get(v) == full_version:
if cls.MAX_PYTHON.get(v) == full_version and not (arch == SYSTEM_ARCH or SYSTEM_ARCH.startswith(str(arch))):
pass
else:
cls.PYTHON_VERSIONS[v] = path
if not cls.PYTHON_ARCHS.get(v, {}).get(arch):
cls.PYTHON_ARCHS[v][arch] = path
else:
for v in [full_version, major_minor, major]:
if (not cls.PYENV_VERSIONS.get(v) and (v == major and not pre) or v != major) or cls.MAX_PYTHON.get(v) == full_version:
cls.PYENV_VERSIONS[v] = path
if not cls.PYTHON_VERSIONS.get(full_version):
cls.PYTHON_VERSIONS[full_version] = path
if not cls.PYTHON_ARCHS.get(v, {}).get(arch):
cls.PYTHON_ARCHS[v][arch] = path
@classmethod
def populate_pyenv_runtimes(cls):
from glob import glob
search_path = os.sep.join(['{0}'.format(PYENV_ROOT), 'versions', '*'])
runtimes = ['pypy', 'ipy', 'jython', 'pyston']
for pyenv_path in glob(search_path):
parsed_version = parse_version(os.path.basename(pyenv_path))
if parsed_version.is_prerelease and cls.PYENV_VERSIONS.get(parsed_version.base_version):
continue
bin_path = os.sep.join([pyenv_path, 'bin'])
runtime = os.sep.join([bin_path, 'python'])
if not os.path.exists(runtime):
exes = [os.sep.join([bin_path, exe]) for exe in runtimes if os.path.exists(os.sep.join([bin_path, exe]))]
if exes:
runtime = exes[0]
cls.register_python(runtime, parsed_version.base_version, pre=parsed_version.is_prerelease, pyenv=True)
version_dict = PythonVersion.parse(major)
major = version_dict["major"]
minor = version_dict["minor"]
patch = version_dict["patch"]
pre = version_dict["is_prerelease"]
dev = version_dict["is_devrelease"]
if os.name == "nt":
match = self.windows_finder.find_python_version(
major, minor=minor, patch=patch, pre=pre, dev=dev
)
if match:
return match
return self.system_path.find_python_version(
major, minor=minor, patch=patch, pre=pre, dev=dev
)
+113
View File
@@ -0,0 +1,113 @@
# -*- coding=utf-8 -*-
import attr
import locale
import os
import subprocess
from fnmatch import fnmatch
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
PYTHON_IMPLEMENTATIONS = ("python", "ironpython", "jython", "pypy")
KNOWN_EXTS = {"exe", "py", "fish", "sh", ""}
KNOWN_EXTS = KNOWN_EXTS | set(
filter(None, os.environ.get("PATHEXT", "").split(os.pathsep))
)
def _run(cmd):
"""Use `subprocess.check_output` to get the output of a command and decode it.
:param list cmd: A list representing the command you want to run.
:returns: A 2-tuple of (output, error)
"""
encoding = locale.getdefaultlocale()[1] or "utf-8"
env = os.environ.copy()
c = subprocess.Popen(
cmd,
encoding=encoding,
env=env,
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
output, err = c.communicate()
return output.strip(), err.strip()
def get_python_version(path):
"""Get python version string using subprocess from a given path."""
version_cmd = [path, "-c", "import sys; print(sys.version.split()[0])"]
return _run(version_cmd)
def optional_instance_of(cls):
return attr.validators.optional(attr.validators.instance_of(cls))
def path_and_exists(path):
return attr.validators.instance_of(Path) and path.exists()
def path_is_executable(path):
return os.access(str(path), os.X_OK)
def path_is_known_executable(path):
return (
path_is_executable(path)
or os.access(str(path), os.R_OK)
and path.suffix in KNOWN_EXTS
)
def is_python_name(name):
rules = ["*python", "*python?", "*python?.?", "*python?.?m"]
match_rules = []
for rule in rules:
match_rules.extend(
[
"{0}.{1}".format(rule, ext) if ext else "{0}".format(rule)
for ext in KNOWN_EXTS
]
)
if not any(name.lower().startswith(py_name) for py_name in PYTHON_IMPLEMENTATIONS):
return False
return any(fnmatch(name, rule) for rule in match_rules)
def path_is_python(path):
return path_is_executable(path) and is_python_name(path.name)
def ensure_path(path):
"""Given a path (either a string or a Path object), expand variables and return a Path object.
:param path: A string or a :class:`~pathlib.Path` object.
:type path: str or :class:`~pathlib.Path`
:return: A fully expanded Path object.
:rtype: :class:`~pathlib.Path`
"""
if isinstance(path, Path):
return Path(os.path.expandvars(path.as_posix()))
return Path(os.path.expandvars(path))
def _filter_none(k, v):
if v:
return True
return False
def filter_pythons(path):
"""Return all valid pythons in a given path"""
if not isinstance(path, Path):
path = Path(str(path))
if not path.is_dir():
return path if path_is_python(path) else None
return filter(lambda x: path_is_python(x), path.iterdir())
+16 -16
View File
@@ -1,16 +1,16 @@
No-notice MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
No-notice MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+26 -26
View File
@@ -6,35 +6,35 @@ if sys.version_info[0] == 2:
else:
_chr = chr
def load(fin, translate=lambda t, x, v: v):
return loads(fin.read(), translate=translate, filename=getattr(fin, 'name', repr(fin)))
def load(fin, translate=lambda t, x, v: v, object_pairs_hook=dict):
return loads(fin.read(), translate=translate, object_pairs_hook=object_pairs_hook, filename=getattr(fin, 'name', repr(fin)))
def loads(s, filename='<string>', translate=lambda t, x, v: v):
def loads(s, filename='<string>', translate=lambda t, x, v: v, object_pairs_hook=dict):
if isinstance(s, bytes):
s = s.decode('utf-8')
s = s.replace('\r\n', '\n')
root = {}
tables = {}
root = object_pairs_hook()
tables = object_pairs_hook()
scope = root
src = _Source(s, filename=filename)
ast = _p_toml(src)
ast = _p_toml(src, object_pairs_hook=object_pairs_hook)
def error(msg):
raise TomlError(msg, pos[0], pos[1], filename)
def process_value(v):
def process_value(v, object_pairs_hook):
kind, text, value, pos = v
if kind == 'str' and value.startswith('\n'):
value = value[1:]
if kind == 'array':
if value and any(k != value[0][0] for k, t, v, p in value[1:]):
error('array-type-mismatch')
value = [process_value(item) for item in value]
value = [process_value(item, object_pairs_hook=object_pairs_hook) for item in value]
elif kind == 'table':
value = dict([(k, process_value(value[k])) for k in value])
value = object_pairs_hook([(k, process_value(value[k], object_pairs_hook=object_pairs_hook)) for k in value])
return translate(kind, text, value)
for kind, value, pos in ast:
@@ -42,7 +42,7 @@ def loads(s, filename='<string>', translate=lambda t, x, v: v):
k, v = value
if k in scope:
error('duplicate_keys. Key "{0}" was used more than once.'.format(k))
scope[k] = process_value(v)
scope[k] = process_value(v, object_pairs_hook=object_pairs_hook)
else:
is_table_array = (kind == 'table_array')
cur = tables
@@ -50,19 +50,19 @@ def loads(s, filename='<string>', translate=lambda t, x, v: v):
if isinstance(cur.get(name), list):
d, cur = cur[name][-1]
else:
d, cur = cur.setdefault(name, (None, {}))
d, cur = cur.setdefault(name, (None, object_pairs_hook()))
scope = {}
scope = object_pairs_hook()
name = value[-1]
if name not in cur:
if is_table_array:
cur[name] = [(scope, {})]
cur[name] = [(scope, object_pairs_hook())]
else:
cur[name] = (scope, {})
cur[name] = (scope, object_pairs_hook())
elif isinstance(cur[name], list):
if not is_table_array:
error('table_type_mismatch')
cur[name].append((scope, {}))
cur[name].append((scope, object_pairs_hook()))
else:
if is_table_array:
error('table_type_mismatch')
@@ -73,7 +73,7 @@ def loads(s, filename='<string>', translate=lambda t, x, v: v):
def merge_tables(scope, tables):
if scope is None:
scope = {}
scope = object_pairs_hook()
for k in tables:
if k in scope:
error('key_table_conflict')
@@ -225,7 +225,7 @@ _datetime_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d
_basicstr_ml_re = re.compile(r'(?:(?:|"|"")[^"\\\000-\011\013-\037])*')
_litstr_re = re.compile(r"[^'\000-\037]*")
_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\011\013-\037]))*")
def _p_value(s):
def _p_value(s, object_pairs_hook):
pos = s.pos()
if s.consume('true'):
@@ -283,7 +283,7 @@ def _p_value(s):
with s:
while True:
_p_ews(s)
items.append(_p_value(s))
items.append(_p_value(s, object_pairs_hook=object_pairs_hook))
s.commit()
_p_ews(s)
s.expect(',')
@@ -294,13 +294,13 @@ def _p_value(s):
if s.consume('{'):
_p_ws(s)
items = {}
items = object_pairs_hook()
if not s.consume('}'):
k = _p_key(s)
_p_ws(s)
s.expect('=')
_p_ws(s)
items[k] = _p_value(s)
items[k] = _p_value(s, object_pairs_hook=object_pairs_hook)
_p_ws(s)
while s.consume(','):
_p_ws(s)
@@ -308,14 +308,14 @@ def _p_value(s):
_p_ws(s)
s.expect('=')
_p_ws(s)
items[k] = _p_value(s)
items[k] = _p_value(s, object_pairs_hook=object_pairs_hook)
_p_ws(s)
s.expect('}')
return 'table', None, items, pos
s.fail()
def _p_stmt(s):
def _p_stmt(s, object_pairs_hook):
pos = s.pos()
if s.consume( '['):
is_array = s.consume('[')
@@ -335,19 +335,19 @@ def _p_stmt(s):
_p_ws(s)
s.expect('=')
_p_ws(s)
value = _p_value(s)
value = _p_value(s, object_pairs_hook=object_pairs_hook)
return 'kv', (key, value), pos
_stmtsep_re = re.compile(r'(?:[ \t]*(?:#[^\n]*)?\n)+[ \t]*')
def _p_toml(s):
def _p_toml(s, object_pairs_hook):
stmts = []
_p_ews(s)
with s:
stmts.append(_p_stmt(s))
stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook))
while True:
s.commit()
s.expect_re(_stmtsep_re)
stmts.append(_p_stmt(s))
stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook))
_p_ews(s)
s.expect_eof()
return stmts
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2017 Kenneth Reitz
Copyright 2018 Kenneth Reitz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+17 -2
View File
@@ -57,10 +57,10 @@ def check_compatibility(urllib3_version, chardet_version):
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
major, minor, patch = int(major), int(minor), int(patch)
# urllib3 >= 1.21.1, <= 1.22
# urllib3 >= 1.21.1, <= 1.23
assert major == 1
assert minor >= 21
assert minor <= 22
assert minor <= 23
# Check chardet for compatibility.
major, minor, patch = chardet_version.split('.')[:3]
@@ -71,6 +71,17 @@ def check_compatibility(urllib3_version, chardet_version):
assert patch >= 2
def _check_cryptography(cryptography_version):
# cryptography < 1.3.4
try:
cryptography_version = list(map(int, cryptography_version.split('.')))
except ValueError:
return
if cryptography_version < [1, 3, 4]:
warning = 'Old version of cryptography ({0}) may cause slowdown.'.format(cryptography_version)
warnings.warn(warning, RequestsDependencyWarning)
# Check imported dependencies for compatibility.
try:
check_compatibility(urllib3.__version__, chardet.__version__)
@@ -83,6 +94,10 @@ except (AssertionError, ValueError):
try:
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version)
except ImportError:
pass
+3 -3
View File
@@ -5,10 +5,10 @@
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'http://python-requests.org'
__version__ = '2.18.4'
__build__ = 0x021804
__version__ = '2.19.1'
__build__ = 0x021901
__author__ = 'Kenneth Reitz'
__author_email__ = 'me@kennethreitz.org'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2017 Kenneth Reitz'
__copyright__ = 'Copyright 2018 Kenneth Reitz'
__cake__ = u'\u2728 \U0001f370 \u2728'
+11 -6
View File
@@ -13,6 +13,7 @@ import socket
from urllib3.poolmanager import PoolManager, proxy_from_url
from urllib3.response import HTTPResponse
from urllib3.util import parse_url
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util.retry import Retry
from urllib3.exceptions import ClosedPoolError
@@ -28,13 +29,13 @@ from urllib3.exceptions import ResponseError
from .models import Response
from .compat import urlparse, basestring
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
prepend_scheme_if_needed, get_auth_from_url, urldefragauth,
select_proxy)
from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths,
get_encoding_from_headers, prepend_scheme_if_needed,
get_auth_from_url, urldefragauth, select_proxy)
from .structures import CaseInsensitiveDict
from .cookies import extract_cookies_to_jar
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
ProxyError, RetryError, InvalidSchema)
ProxyError, RetryError, InvalidSchema, InvalidProxyURL)
from .auth import _basic_auth_str
try:
@@ -219,7 +220,7 @@ class HTTPAdapter(BaseAdapter):
cert_loc = verify
if not cert_loc:
cert_loc = DEFAULT_CA_BUNDLE_PATH
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
if not cert_loc or not os.path.exists(cert_loc):
raise IOError("Could not find a suitable TLS CA certificate bundle, "
@@ -300,6 +301,10 @@ class HTTPAdapter(BaseAdapter):
if proxy:
proxy = prepend_scheme_if_needed(proxy, 'http')
proxy_url = parse_url(proxy)
if not proxy_url.host:
raise InvalidProxyURL("Please check proxy URL. It is malformed"
" and could be missing the host.")
proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_url(url)
else:
@@ -406,7 +411,7 @@ class HTTPAdapter(BaseAdapter):
self.cert_verify(conn, request.url, verify, cert)
url = self.request_url(request, proxies)
self.add_headers(request)
self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
chunked = not (request.body is None or 'Content-Length' in request.headers)
+1 -1
View File
@@ -20,7 +20,7 @@ def request(method, url, **kwargs):
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
+12
View File
@@ -153,6 +153,18 @@ class HTTPDigestAuth(AuthBase):
x = x.encode('utf-8')
return hashlib.sha1(x).hexdigest()
hash_utf8 = sha_utf8
elif _algorithm == 'SHA-256':
def sha256_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
return hashlib.sha256(x).hexdigest()
hash_utf8 = sha256_utf8
elif _algorithm == 'SHA-512':
def sha512_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
return hashlib.sha512(x).hexdigest()
hash_utf8 = sha512_utf8
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
+2
View File
@@ -43,6 +43,7 @@ if is_py2:
import cookielib
from Cookie import Morsel
from StringIO import StringIO
from collections import Callable, Mapping, MutableMapping
from urllib3.packages.ordered_dict import OrderedDict
@@ -60,6 +61,7 @@ elif is_py3:
from http.cookies import Morsel
from io import StringIO
from collections import OrderedDict
from collections.abc import Callable, Mapping, MutableMapping
builtin_str = str
str = str
+7 -3
View File
@@ -12,10 +12,9 @@ requests.utils imports from here, so be careful with imports.
import copy
import time
import calendar
import collections
from ._internal_utils import to_native_string
from .compat import cookielib, urlparse, urlunparse, Morsel
from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping
try:
import threading
@@ -169,7 +168,7 @@ class CookieConflictError(RuntimeError):
"""
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict
interface.
@@ -415,9 +414,14 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
def copy(self):
"""Return a copy of this RequestsCookieJar."""
new_cj = RequestsCookieJar()
new_cj.set_policy(self.get_policy())
new_cj.update(self)
return new_cj
def get_policy(self):
"""Return the CookiePolicy instance used."""
return self._policy
def _copy_cookie_jar(jar):
if jar is None:
+4
View File
@@ -85,6 +85,10 @@ class InvalidHeader(RequestException, ValueError):
"""The header value provided was somehow invalid."""
class InvalidProxyURL(InvalidURL):
"""The proxy URL provided is invalid."""
class ChunkedEncodingError(RequestException):
"""The server declared chunked encoding but sent an invalid chunk."""
+1 -1
View File
@@ -13,7 +13,7 @@ import chardet
from . import __version__ as requests_version
try:
from .packages.urllib3.contrib import pyopenssl
from urllib3.contrib import pyopenssl
except ImportError:
pyopenssl = None
OpenSSL = None
+12 -8
View File
@@ -7,7 +7,6 @@ requests.models
This module contains the primary objects that power Requests.
"""
import collections
import datetime
import sys
@@ -37,6 +36,7 @@ from .utils import (
stream_decode_response_unicode, to_key_val_list, parse_header_links,
iter_slices, guess_json_utf, super_len, check_header_validity)
from .compat import (
Callable, Mapping,
cookielib, urlunparse, urlsplit, urlencode, str, bytes,
is_py2, chardet, builtin_str, basestring)
from .compat import json as complexjson
@@ -155,8 +155,12 @@ class RequestEncodingMixin(object):
if isinstance(fp, (str, bytes, bytearray)):
fdata = fp
else:
elif hasattr(fp, 'read'):
fdata = fp.read()
elif fp is None:
continue
else:
fdata = fp
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
rf.make_multipart(content_type=ft)
@@ -174,10 +178,10 @@ class RequestHooksMixin(object):
if event not in self.hooks:
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
if isinstance(hook, collections.Callable):
if isinstance(hook, Callable):
self.hooks[event].append(hook)
elif hasattr(hook, '__iter__'):
self.hooks[event].extend(h for h in hook if isinstance(h, collections.Callable))
self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
def deregister_hook(self, event, hook):
"""Deregister a previously registered hook.
@@ -461,7 +465,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
is_stream = all([
hasattr(data, '__iter__'),
not isinstance(data, (basestring, list, tuple, collections.Mapping))
not isinstance(data, (basestring, list, tuple, Mapping))
])
try:
@@ -686,11 +690,11 @@ class Response(object):
@property
def ok(self):
"""Returns True if :attr:`status_code` is less than 400.
"""Returns True if :attr:`status_code` is less than 400, False if not.
This attribute checks if the status code of the response is between
400 and 600 to see if there was a client error or a server error. If
the status code, is between 200 and 400, this will return True. This
the status code is between 200 and 400, this will return True. This
is **not** a check to see if the response code is ``200 OK``.
"""
try:
@@ -820,7 +824,7 @@ class Response(object):
if self.status_code == 0 or self.raw is None:
self._content = None
else:
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b''
self._content_consumed = True
# don't need to release the connection; that's been handled by urllib3
+11 -7
View File
@@ -8,13 +8,12 @@ This module provides a Session object to manage and persist settings across
requests (cookies, auth, proxies).
"""
import os
import platform
import sys
import time
from collections import Mapping
from datetime import timedelta
from .auth import _basic_auth_str
from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse
from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping
from .cookies import (
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
@@ -38,8 +37,8 @@ from .status_codes import codes
from .models import REDIRECT_STATI
# Preferred clock, based on which one is more accurate on a given system.
if platform.system() == 'Windows':
try: # Python 3.3+
if sys.platform == 'win32':
try: # Python 3.4+
preferred_clock = time.perf_counter
except AttributeError: # Earlier than Python 3.
preferred_clock = time.clock
@@ -123,6 +122,7 @@ class SessionRedirectMixin(object):
hist = [] # keep track of history
url = self.get_redirect_target(resp)
previous_fragment = urlparse(req.url).fragment
while url:
prepared_request = req.copy()
@@ -147,8 +147,12 @@ class SessionRedirectMixin(object):
parsed_rurl = urlparse(resp.url)
url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url)
# The scheme should be lower case...
# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
parsed = urlparse(url)
if parsed.fragment == '' and previous_fragment:
parsed = parsed._replace(fragment=previous_fragment)
elif parsed.fragment:
previous_fragment = parsed.fragment
url = parsed.geturl()
# Facilitate relative 'location' headers, as allowed by RFC 7231.
@@ -696,7 +700,7 @@ class Session(SessionRedirectMixin):
"""
for (prefix, adapter) in self.adapters.items():
if url.lower().startswith(prefix):
if url.lower().startswith(prefix.lower()):
return adapter
# Nothing matches :-/
+34 -5
View File
@@ -1,5 +1,22 @@
# -*- coding: utf-8 -*-
"""
The ``codes`` object defines a mapping from common names for HTTP statuses
to their numerical codes, accessible either as attributes or as dictionary
items.
>>> requests.codes['temporary_redirect']
307
>>> requests.codes.teapot
418
>>> requests.codes['\o/']
200
Some codes have multiple names, and both upper- and lower-case versions of
the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
``codes.okay`` all correspond to the HTTP status code 200.
"""
from .structures import LookupDict
_codes = {
@@ -84,8 +101,20 @@ _codes = {
codes = LookupDict(name='status_codes')
for code, titles in _codes.items():
for title in titles:
setattr(codes, title, code)
if not title.startswith(('\\', '/')):
setattr(codes, title.upper(), code)
def _init():
for code, titles in _codes.items():
for title in titles:
setattr(codes, title, code)
if not title.startswith(('\\', '/')):
setattr(codes, title.upper(), code)
def doc(code):
names = ', '.join('``%s``' % n for n in _codes[code])
return '* %d: %s' % (code, names)
global __doc__
__doc__ = (__doc__ + '\n' +
'\n'.join(doc(code) for code in sorted(_codes))
if __doc__ is not None else None)
_init()
+4 -6
View File
@@ -7,16 +7,14 @@ requests.structures
Data structures that power Requests.
"""
import collections
from .compat import OrderedDict
from .compat import OrderedDict, Mapping, MutableMapping
class CaseInsensitiveDict(collections.MutableMapping):
class CaseInsensitiveDict(MutableMapping):
"""A case-insensitive ``dict``-like object.
Implements all methods and operations of
``collections.MutableMapping`` as well as dict's ``copy``. Also
``MutableMapping`` as well as dict's ``copy``. Also
provides ``lower_items``.
All keys are expected to be strings. The structure remembers the
@@ -71,7 +69,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
)
def __eq__(self, other):
if isinstance(other, collections.Mapping):
if isinstance(other, Mapping):
other = CaseInsensitiveDict(other)
else:
return NotImplemented
+94 -22
View File
@@ -8,17 +8,17 @@ This module provides utility functions that are used within Requests
that are also useful for external consumption.
"""
import cgi
import codecs
import collections
import contextlib
import io
import os
import platform
import re
import socket
import struct
import sys
import tempfile
import warnings
import zipfile
from .__version__ import __version__
from . import certs
@@ -28,7 +28,7 @@ from .compat import parse_http_list as _parse_list_header
from .compat import (
quote, urlparse, bytes, str, OrderedDict, unquote, getproxies,
proxy_bypass, urlunparse, basestring, integer_types, is_py3,
proxy_bypass_environment, getproxies_environment)
proxy_bypass_environment, getproxies_environment, Mapping)
from .cookies import cookiejar_from_dict
from .structures import CaseInsensitiveDict
from .exceptions import (
@@ -39,19 +39,25 @@ NETRC_FILES = ('.netrc', '_netrc')
DEFAULT_CA_BUNDLE_PATH = certs.where()
if platform.system() == 'Windows':
if sys.platform == 'win32':
# provide a proxy_bypass version on Windows without DNS lookups
def proxy_bypass_registry(host):
if is_py3:
import winreg
else:
import _winreg as winreg
try:
if is_py3:
import winreg
else:
import _winreg as winreg
except ImportError:
return False
try:
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
proxyEnable = winreg.QueryValueEx(internetSettings,
'ProxyEnable')[0]
# ProxyEnable could be REG_SZ or REG_DWORD, normalizing it
proxyEnable = int(winreg.QueryValueEx(internetSettings,
'ProxyEnable')[0])
# ProxyOverride is almost always a string
proxyOverride = winreg.QueryValueEx(internetSettings,
'ProxyOverride')[0]
except OSError:
@@ -216,6 +222,38 @@ def guess_filename(obj):
return os.path.basename(name)
def extract_zipped_paths(path):
"""Replace nonexistent paths that look like they refer to a member of a zip
archive with the location of an extracted copy of the target, or else
just return the provided path unchanged.
"""
if os.path.exists(path):
# this is already a valid path, no need to do anything further
return path
# find the first valid part of the provided path and treat that as a zip archive
# assume the rest of the path is the name of a member in the archive
archive, member = os.path.split(path)
while archive and not os.path.exists(archive):
archive, prefix = os.path.split(archive)
member = '/'.join([prefix, member])
if not zipfile.is_zipfile(archive):
return path
zip_file = zipfile.ZipFile(archive)
if member not in zip_file.namelist():
return path
# we have a valid zip archive and a valid member of that archive
tmp = tempfile.gettempdir()
extracted_path = os.path.join(tmp, *member.split('/'))
if not os.path.exists(extracted_path):
extracted_path = zip_file.extract(member, path=tmp)
return extracted_path
def from_key_val_list(value):
"""Take an object and test to see if it can be represented as a
dictionary. Unless it can not be represented as such, return an
@@ -262,7 +300,7 @@ def to_key_val_list(value):
if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples')
if isinstance(value, collections.Mapping):
if isinstance(value, Mapping):
value = value.items()
return list(value)
@@ -407,6 +445,31 @@ def get_encodings_from_content(content):
xml_re.findall(content))
def _parse_content_type_header(header):
"""Returns content type and parameters from given header
:param header: string
:return: tuple containing content type and dictionary of
parameters
"""
tokens = header.split(';')
content_type, params = tokens[0].strip(), tokens[1:]
params_dict = {}
items_to_strip = "\"' "
for param in params:
param = param.strip()
if param:
key, value = param, True
index_of_equals = param.find("=")
if index_of_equals != -1:
key = param[:index_of_equals].strip(items_to_strip)
value = param[index_of_equals + 1:].strip(items_to_strip)
params_dict[key] = value
return content_type, params_dict
def get_encoding_from_headers(headers):
"""Returns encodings from given HTTP Header Dict.
@@ -419,7 +482,7 @@ def get_encoding_from_headers(headers):
if not content_type:
return None
content_type, params = cgi.parse_header(content_type)
content_type, params = _parse_content_type_header(content_type)
if 'charset' in params:
return params['charset'].strip("'\"")
@@ -632,6 +695,8 @@ def should_bypass_proxies(url, no_proxy):
:rtype: bool
"""
# Prioritize lowercase environment variables over uppercase
# to keep a consistent behaviour with other http projects (curl, wget).
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
# First check whether no_proxy is defined. If it is, check that the URL
@@ -639,28 +704,31 @@ def should_bypass_proxies(url, no_proxy):
no_proxy_arg = no_proxy
if no_proxy is None:
no_proxy = get_proxy('no_proxy')
netloc = urlparse(url).netloc
parsed = urlparse(url)
if no_proxy:
# We need to check whether we match here. We need to see if we match
# the end of the netloc, both with and without the port.
# the end of the hostname, both with and without the port.
no_proxy = (
host for host in no_proxy.replace(' ', '').split(',') if host
)
ip = netloc.split(':')[0]
if is_ipv4_address(ip):
if is_ipv4_address(parsed.hostname):
for proxy_ip in no_proxy:
if is_valid_cidr(proxy_ip):
if address_in_network(ip, proxy_ip):
if address_in_network(parsed.hostname, proxy_ip):
return True
elif ip == proxy_ip:
elif parsed.hostname == proxy_ip:
# If no_proxy ip was defined in plain IP notation instead of cidr notation &
# matches the IP of the index
return True
else:
host_with_port = parsed.hostname
if parsed.port:
host_with_port += ':{0}'.format(parsed.port)
for host in no_proxy:
if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
if parsed.hostname.endswith(host) or host_with_port.endswith(host):
# The URL does match something in no_proxy, so we don't want
# to apply the proxies on this URL.
return True
@@ -673,7 +741,7 @@ def should_bypass_proxies(url, no_proxy):
# legitimate problems.
with set_environ('no_proxy', no_proxy_arg):
try:
bypass = proxy_bypass(netloc)
bypass = proxy_bypass(parsed.hostname)
except (TypeError, socket.gaierror):
bypass = False
@@ -743,7 +811,7 @@ def default_headers():
def parse_header_links(value):
"""Return a dict of parsed link headers proxies.
"""Return a list of parsed link headers proxies.
i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg"
@@ -754,6 +822,10 @@ def parse_header_links(value):
replace_chars = ' \'"'
value = value.strip(replace_chars)
if not value:
return links
for val in re.split(', *<', value):
try:
url, params = val.split(';', 1)
+3 -21
View File
@@ -1,21 +1,3 @@
The MIT License (MIT)
Copyright 2018 Dan Ryan.
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.
This software is made available under the terms of *either* of the licenses
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
under the terms of *both* these licenses.
+177
View File
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+23
View File
@@ -0,0 +1,23 @@
Copyright (c) Kenneth Reitz and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-14
View File
@@ -1,14 +0,0 @@
# Taken from pip: https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/src/pip/_vendor/Makefile
all: clean vendor
clean:
@# Delete vendored items
find . -maxdepth 1 -mindepth 1 -type d -exec rm -rf {} \;
vendor:
@# Install vendored libraries
pip install -t . -r vendor.txt
@# Cleanup .egg-info directories
rm -rf *.egg-info
rm -rf *.dist-info
@@ -1 +0,0 @@
pipfile
+12 -7
View File
@@ -187,7 +187,7 @@ class FileRequirement(BaseRequirement):
if parsed_url.scheme == "file" and parsed_url.path:
# This is a "file://" URI. Use url_to_path and path_to_url to
# ensure the path is absolute. Also we need to build relpath.
path = Path(url_to_path(unquote(urllib_parse.urlunsplit(parsed_url)))).as_posix()
path = Path(url_to_path(urllib_parse.urlunsplit(parsed_url))).as_posix()
try:
relpath = get_converted_relative_path(path)
except ValueError:
@@ -198,17 +198,17 @@ class FileRequirement(BaseRequirement):
path = None
relpath = None
# Cut the fragment, but otherwise this is fixed_line.
uri = unquote(urllib_parse.urlunsplit(
uri = urllib_parse.urlunsplit(
parsed_url._replace(scheme=original_scheme, fragment="")
))
)
if added_ssh_scheme:
uri = strip_ssh_from_git_uri(uri)
# Re-attach VCS prefix to build a Link.
link = Link(unquote(
link = Link(
urllib_parse.urlunsplit(parsed_url._replace(scheme=original_scheme))
))
)
return LinkInfo(vcs_type, prefer, relpath, path, uri, link)
@@ -228,7 +228,12 @@ class FileRequirement(BaseRequirement):
return self.link.egg_fragment
elif self.link and self.link.is_wheel:
return Wheel(self.link.filename).name
if self._uri_scheme != "uri" and self.path and self.setup_path and self.setup_path.exists():
if (
self._uri_scheme != "uri"
and self.path
and self.setup_path
and self.setup_path.exists()
):
from distutils.core import run_setup
try:
@@ -357,7 +362,7 @@ class FileRequirement(BaseRequirement):
else:
# URI is not currently a valid key in pipfile entries
# see https://github.com/pypa/pipfile/issues/110
uri_scheme = 'file'
uri_scheme = "file"
if not uri:
uri = path_to_url(path)
+1 -1
View File
@@ -84,7 +84,7 @@ def strip_ssh_from_git_uri(uri):
def add_ssh_scheme_to_git_uri(uri):
"""Cleans VCS uris from pip format"""
"""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:
-3
View File
@@ -34,9 +34,6 @@ log = setup_logger()
def is_vcs(pipfile_entry):
import requirements
from .models.utils import add_ssh_scheme_to_git_uri
"""Determine if dictionary entry from Pipfile is for a vcs dependency."""
if hasattr(pipfile_entry, "keys"):
return any(key for key in pipfile_entry.keys() if key in VCS_LIST)
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2012, Ben Hoyt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Ben Hoyt nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+680
View File
@@ -0,0 +1,680 @@
"""scandir, a better directory iterator and faster os.walk(), now in the Python 3.5 stdlib
scandir() is a generator version of os.listdir() that returns an
iterator over files in a directory, and also exposes the extra
information most OSes provide while iterating files in a directory
(such as type and stat information).
This module also includes a version of os.walk() that uses scandir()
to speed it up significantly.
See README.md or https://github.com/benhoyt/scandir for rationale and
docs, or read PEP 471 (https://www.python.org/dev/peps/pep-0471/) for
more details on its inclusion into Python 3.5
scandir is released under the new BSD 3-clause license. See
LICENSE.txt for the full license text.
"""
from __future__ import division
from errno import ENOENT
from os import listdir, lstat, stat, strerror
from os.path import join, islink
from stat import S_IFDIR, S_IFLNK, S_IFREG
import collections
import os
import sys
try:
import _scandir
except ImportError:
_scandir = None
try:
import ctypes
except ImportError:
ctypes = None
if _scandir is None and ctypes is None:
import warnings
warnings.warn("scandir can't find the compiled _scandir C module "
"or ctypes, using slow generic fallback")
__version__ = '1.7'
__all__ = ['scandir', 'walk']
# Windows FILE_ATTRIBUTE constants for interpreting the
# FIND_DATA.dwFileAttributes member
FILE_ATTRIBUTE_ARCHIVE = 32
FILE_ATTRIBUTE_COMPRESSED = 2048
FILE_ATTRIBUTE_DEVICE = 64
FILE_ATTRIBUTE_DIRECTORY = 16
FILE_ATTRIBUTE_ENCRYPTED = 16384
FILE_ATTRIBUTE_HIDDEN = 2
FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768
FILE_ATTRIBUTE_NORMAL = 128
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192
FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072
FILE_ATTRIBUTE_OFFLINE = 4096
FILE_ATTRIBUTE_READONLY = 1
FILE_ATTRIBUTE_REPARSE_POINT = 1024
FILE_ATTRIBUTE_SPARSE_FILE = 512
FILE_ATTRIBUTE_SYSTEM = 4
FILE_ATTRIBUTE_TEMPORARY = 256
FILE_ATTRIBUTE_VIRTUAL = 65536
IS_PY3 = sys.version_info >= (3, 0)
if IS_PY3:
unicode = str # Because Python <= 3.2 doesn't have u'unicode' syntax
class GenericDirEntry(object):
__slots__ = ('name', '_stat', '_lstat', '_scandir_path', '_path')
def __init__(self, scandir_path, name):
self._scandir_path = scandir_path
self.name = name
self._stat = None
self._lstat = None
self._path = None
@property
def path(self):
if self._path is None:
self._path = join(self._scandir_path, self.name)
return self._path
def stat(self, follow_symlinks=True):
if follow_symlinks:
if self._stat is None:
self._stat = stat(self.path)
return self._stat
else:
if self._lstat is None:
self._lstat = lstat(self.path)
return self._lstat
def is_dir(self, follow_symlinks=True):
try:
st = self.stat(follow_symlinks=follow_symlinks)
except OSError as e:
if e.errno != ENOENT:
raise
return False # Path doesn't exist or is a broken symlink
return st.st_mode & 0o170000 == S_IFDIR
def is_file(self, follow_symlinks=True):
try:
st = self.stat(follow_symlinks=follow_symlinks)
except OSError as e:
if e.errno != ENOENT:
raise
return False # Path doesn't exist or is a broken symlink
return st.st_mode & 0o170000 == S_IFREG
def is_symlink(self):
try:
st = self.stat(follow_symlinks=False)
except OSError as e:
if e.errno != ENOENT:
raise
return False # Path doesn't exist or is a broken symlink
return st.st_mode & 0o170000 == S_IFLNK
def inode(self):
st = self.stat(follow_symlinks=False)
return st.st_ino
def __str__(self):
return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
__repr__ = __str__
def _scandir_generic(path=unicode('.')):
"""Like os.listdir(), but yield DirEntry objects instead of returning
a list of names.
"""
for name in listdir(path):
yield GenericDirEntry(path, name)
if IS_PY3 and sys.platform == 'win32':
def scandir_generic(path=unicode('.')):
if isinstance(path, bytes):
raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead")
return _scandir_generic(path)
scandir_generic.__doc__ = _scandir_generic.__doc__
else:
scandir_generic = _scandir_generic
scandir_c = None
scandir_python = None
if sys.platform == 'win32':
if ctypes is not None:
from ctypes import wintypes
# Various constants from windows.h
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
ERROR_FILE_NOT_FOUND = 2
ERROR_NO_MORE_FILES = 18
IO_REPARSE_TAG_SYMLINK = 0xA000000C
# Numer of seconds between 1601-01-01 and 1970-01-01
SECONDS_BETWEEN_EPOCHS = 11644473600
kernel32 = ctypes.windll.kernel32
# ctypes wrappers for (wide string versions of) FindFirstFile,
# FindNextFile, and FindClose
FindFirstFile = kernel32.FindFirstFileW
FindFirstFile.argtypes = [
wintypes.LPCWSTR,
ctypes.POINTER(wintypes.WIN32_FIND_DATAW),
]
FindFirstFile.restype = wintypes.HANDLE
FindNextFile = kernel32.FindNextFileW
FindNextFile.argtypes = [
wintypes.HANDLE,
ctypes.POINTER(wintypes.WIN32_FIND_DATAW),
]
FindNextFile.restype = wintypes.BOOL
FindClose = kernel32.FindClose
FindClose.argtypes = [wintypes.HANDLE]
FindClose.restype = wintypes.BOOL
Win32StatResult = collections.namedtuple('Win32StatResult', [
'st_mode',
'st_ino',
'st_dev',
'st_nlink',
'st_uid',
'st_gid',
'st_size',
'st_atime',
'st_mtime',
'st_ctime',
'st_atime_ns',
'st_mtime_ns',
'st_ctime_ns',
'st_file_attributes',
])
def filetime_to_time(filetime):
"""Convert Win32 FILETIME to time since Unix epoch in seconds."""
total = filetime.dwHighDateTime << 32 | filetime.dwLowDateTime
return total / 10000000 - SECONDS_BETWEEN_EPOCHS
def find_data_to_stat(data):
"""Convert Win32 FIND_DATA struct to stat_result."""
# First convert Win32 dwFileAttributes to st_mode
attributes = data.dwFileAttributes
st_mode = 0
if attributes & FILE_ATTRIBUTE_DIRECTORY:
st_mode |= S_IFDIR | 0o111
else:
st_mode |= S_IFREG
if attributes & FILE_ATTRIBUTE_READONLY:
st_mode |= 0o444
else:
st_mode |= 0o666
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT and
data.dwReserved0 == IO_REPARSE_TAG_SYMLINK):
st_mode ^= st_mode & 0o170000
st_mode |= S_IFLNK
st_size = data.nFileSizeHigh << 32 | data.nFileSizeLow
st_atime = filetime_to_time(data.ftLastAccessTime)
st_mtime = filetime_to_time(data.ftLastWriteTime)
st_ctime = filetime_to_time(data.ftCreationTime)
# Some fields set to zero per CPython's posixmodule.c: st_ino, st_dev,
# st_nlink, st_uid, st_gid
return Win32StatResult(st_mode, 0, 0, 0, 0, 0, st_size,
st_atime, st_mtime, st_ctime,
int(st_atime * 1000000000),
int(st_mtime * 1000000000),
int(st_ctime * 1000000000),
attributes)
class Win32DirEntryPython(object):
__slots__ = ('name', '_stat', '_lstat', '_find_data', '_scandir_path', '_path', '_inode')
def __init__(self, scandir_path, name, find_data):
self._scandir_path = scandir_path
self.name = name
self._stat = None
self._lstat = None
self._find_data = find_data
self._path = None
self._inode = None
@property
def path(self):
if self._path is None:
self._path = join(self._scandir_path, self.name)
return self._path
def stat(self, follow_symlinks=True):
if follow_symlinks:
if self._stat is None:
if self.is_symlink():
# It's a symlink, call link-following stat()
self._stat = stat(self.path)
else:
# Not a symlink, stat is same as lstat value
if self._lstat is None:
self._lstat = find_data_to_stat(self._find_data)
self._stat = self._lstat
return self._stat
else:
if self._lstat is None:
# Lazily convert to stat object, because it's slow
# in Python, and often we only need is_dir() etc
self._lstat = find_data_to_stat(self._find_data)
return self._lstat
def is_dir(self, follow_symlinks=True):
is_symlink = self.is_symlink()
if follow_symlinks and is_symlink:
try:
return self.stat().st_mode & 0o170000 == S_IFDIR
except OSError as e:
if e.errno != ENOENT:
raise
return False
elif is_symlink:
return False
else:
return (self._find_data.dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY != 0)
def is_file(self, follow_symlinks=True):
is_symlink = self.is_symlink()
if follow_symlinks and is_symlink:
try:
return self.stat().st_mode & 0o170000 == S_IFREG
except OSError as e:
if e.errno != ENOENT:
raise
return False
elif is_symlink:
return False
else:
return (self._find_data.dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY == 0)
def is_symlink(self):
return (self._find_data.dwFileAttributes &
FILE_ATTRIBUTE_REPARSE_POINT != 0 and
self._find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK)
def inode(self):
if self._inode is None:
self._inode = lstat(self.path).st_ino
return self._inode
def __str__(self):
return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
__repr__ = __str__
def win_error(error, filename):
exc = WindowsError(error, ctypes.FormatError(error))
exc.filename = filename
return exc
def _scandir_python(path=unicode('.')):
"""Like os.listdir(), but yield DirEntry objects instead of returning
a list of names.
"""
# Call FindFirstFile and handle errors
if isinstance(path, bytes):
is_bytes = True
filename = join(path.decode('mbcs', 'strict'), '*.*')
else:
is_bytes = False
filename = join(path, '*.*')
data = wintypes.WIN32_FIND_DATAW()
data_p = ctypes.byref(data)
handle = FindFirstFile(filename, data_p)
if handle == INVALID_HANDLE_VALUE:
error = ctypes.GetLastError()
if error == ERROR_FILE_NOT_FOUND:
# No files, don't yield anything
return
raise win_error(error, path)
# Call FindNextFile in a loop, stopping when no more files
try:
while True:
# Skip '.' and '..' (current and parent directory), but
# otherwise yield (filename, stat_result) tuple
name = data.cFileName
if name not in ('.', '..'):
if is_bytes:
name = name.encode('mbcs', 'replace')
yield Win32DirEntryPython(path, name, data)
data = wintypes.WIN32_FIND_DATAW()
data_p = ctypes.byref(data)
success = FindNextFile(handle, data_p)
if not success:
error = ctypes.GetLastError()
if error == ERROR_NO_MORE_FILES:
break
raise win_error(error, path)
finally:
if not FindClose(handle):
raise win_error(ctypes.GetLastError(), path)
if IS_PY3:
def scandir_python(path=unicode('.')):
if isinstance(path, bytes):
raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead")
return _scandir_python(path)
scandir_python.__doc__ = _scandir_python.__doc__
else:
scandir_python = _scandir_python
if _scandir is not None:
scandir_c = _scandir.scandir
DirEntry_c = _scandir.DirEntry
if _scandir is not None:
scandir = scandir_c
DirEntry = DirEntry_c
elif ctypes is not None:
scandir = scandir_python
DirEntry = Win32DirEntryPython
else:
scandir = scandir_generic
DirEntry = GenericDirEntry
# Linux, OS X, and BSD implementation
elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.platform:
have_dirent_d_type = (sys.platform != 'sunos5')
if ctypes is not None and have_dirent_d_type:
import ctypes.util
DIR_p = ctypes.c_void_p
# Rather annoying how the dirent struct is slightly different on each
# platform. The only fields we care about are d_name and d_type.
class Dirent(ctypes.Structure):
if sys.platform.startswith('linux'):
_fields_ = (
('d_ino', ctypes.c_ulong),
('d_off', ctypes.c_long),
('d_reclen', ctypes.c_ushort),
('d_type', ctypes.c_byte),
('d_name', ctypes.c_char * 256),
)
else:
_fields_ = (
('d_ino', ctypes.c_uint32), # must be uint32, not ulong
('d_reclen', ctypes.c_ushort),
('d_type', ctypes.c_byte),
('d_namlen', ctypes.c_byte),
('d_name', ctypes.c_char * 256),
)
DT_UNKNOWN = 0
DT_DIR = 4
DT_REG = 8
DT_LNK = 10
Dirent_p = ctypes.POINTER(Dirent)
Dirent_pp = ctypes.POINTER(Dirent_p)
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
opendir = libc.opendir
opendir.argtypes = [ctypes.c_char_p]
opendir.restype = DIR_p
readdir_r = libc.readdir_r
readdir_r.argtypes = [DIR_p, Dirent_p, Dirent_pp]
readdir_r.restype = ctypes.c_int
closedir = libc.closedir
closedir.argtypes = [DIR_p]
closedir.restype = ctypes.c_int
file_system_encoding = sys.getfilesystemencoding()
class PosixDirEntry(object):
__slots__ = ('name', '_d_type', '_stat', '_lstat', '_scandir_path', '_path', '_inode')
def __init__(self, scandir_path, name, d_type, inode):
self._scandir_path = scandir_path
self.name = name
self._d_type = d_type
self._inode = inode
self._stat = None
self._lstat = None
self._path = None
@property
def path(self):
if self._path is None:
self._path = join(self._scandir_path, self.name)
return self._path
def stat(self, follow_symlinks=True):
if follow_symlinks:
if self._stat is None:
if self.is_symlink():
self._stat = stat(self.path)
else:
if self._lstat is None:
self._lstat = lstat(self.path)
self._stat = self._lstat
return self._stat
else:
if self._lstat is None:
self._lstat = lstat(self.path)
return self._lstat
def is_dir(self, follow_symlinks=True):
if (self._d_type == DT_UNKNOWN or
(follow_symlinks and self.is_symlink())):
try:
st = self.stat(follow_symlinks=follow_symlinks)
except OSError as e:
if e.errno != ENOENT:
raise
return False
return st.st_mode & 0o170000 == S_IFDIR
else:
return self._d_type == DT_DIR
def is_file(self, follow_symlinks=True):
if (self._d_type == DT_UNKNOWN or
(follow_symlinks and self.is_symlink())):
try:
st = self.stat(follow_symlinks=follow_symlinks)
except OSError as e:
if e.errno != ENOENT:
raise
return False
return st.st_mode & 0o170000 == S_IFREG
else:
return self._d_type == DT_REG
def is_symlink(self):
if self._d_type == DT_UNKNOWN:
try:
st = self.stat(follow_symlinks=False)
except OSError as e:
if e.errno != ENOENT:
raise
return False
return st.st_mode & 0o170000 == S_IFLNK
else:
return self._d_type == DT_LNK
def inode(self):
return self._inode
def __str__(self):
return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
__repr__ = __str__
def posix_error(filename):
errno = ctypes.get_errno()
exc = OSError(errno, strerror(errno))
exc.filename = filename
return exc
def scandir_python(path=unicode('.')):
"""Like os.listdir(), but yield DirEntry objects instead of returning
a list of names.
"""
if isinstance(path, bytes):
opendir_path = path
is_bytes = True
else:
opendir_path = path.encode(file_system_encoding)
is_bytes = False
dir_p = opendir(opendir_path)
if not dir_p:
raise posix_error(path)
try:
result = Dirent_p()
while True:
entry = Dirent()
if readdir_r(dir_p, entry, result):
raise posix_error(path)
if not result:
break
name = entry.d_name
if name not in (b'.', b'..'):
if not is_bytes:
name = name.decode(file_system_encoding)
yield PosixDirEntry(path, name, entry.d_type, entry.d_ino)
finally:
if closedir(dir_p):
raise posix_error(path)
if _scandir is not None:
scandir_c = _scandir.scandir
DirEntry_c = _scandir.DirEntry
if _scandir is not None:
scandir = scandir_c
DirEntry = DirEntry_c
elif ctypes is not None:
scandir = scandir_python
DirEntry = PosixDirEntry
else:
scandir = scandir_generic
DirEntry = GenericDirEntry
# Some other system -- no d_type or stat information
else:
scandir = scandir_generic
DirEntry = GenericDirEntry
def _walk(top, topdown=True, onerror=None, followlinks=False):
"""Like Python 3.5's implementation of os.walk() -- faster than
the pre-Python 3.5 version as it uses scandir() internally.
"""
dirs = []
nondirs = []
# We may not have read permission for top, in which case we can't
# get a list of the files the directory contains. os.walk
# always suppressed the exception then, rather than blow up for a
# minor reason when (say) a thousand readable directories are still
# left to visit. That logic is copied here.
try:
scandir_it = scandir(top)
except OSError as error:
if onerror is not None:
onerror(error)
return
while True:
try:
try:
entry = next(scandir_it)
except StopIteration:
break
except OSError as error:
if onerror is not None:
onerror(error)
return
try:
is_dir = entry.is_dir()
except OSError:
# If is_dir() raises an OSError, consider that the entry is not
# a directory, same behaviour than os.path.isdir().
is_dir = False
if is_dir:
dirs.append(entry.name)
else:
nondirs.append(entry.name)
if not topdown and is_dir:
# Bottom-up: recurse into sub-directory, but exclude symlinks to
# directories if followlinks is False
if followlinks:
walk_into = True
else:
try:
is_symlink = entry.is_symlink()
except OSError:
# If is_symlink() raises an OSError, consider that the
# entry is not a symbolic link, same behaviour than
# os.path.islink().
is_symlink = False
walk_into = not is_symlink
if walk_into:
for entry in walk(entry.path, topdown, onerror, followlinks):
yield entry
# Yield before recursion if going top down
if topdown:
yield top, dirs, nondirs
# Recurse into sub-directories
for name in dirs:
new_path = join(top, name)
# Issue #23605: os.path.islink() is used instead of caching
# entry.is_symlink() result during the loop on os.scandir() because
# the caller can replace the directory entry during the "yield"
# above.
if followlinks or not islink(new_path):
for entry in walk(new_path, topdown, onerror, followlinks):
yield entry
else:
# Yield after recursion if going bottom up
yield top, dirs, nondirs
if IS_PY3 or sys.platform != 'win32':
walk = _walk
else:
# Fix for broken unicode handling on Windows on Python 2.x, see:
# https://github.com/benhoyt/scandir/issues/54
file_system_encoding = sys.getfilesystemencoding()
def walk(top, topdown=True, onerror=None, followlinks=False):
if isinstance(top, bytes):
top = top.decode(file_system_encoding)
return _walk(top, topdown, onerror, followlinks)
+20 -1
View File
@@ -6,9 +6,11 @@ import collections
import re
__version__ = '2.7.8'
__version__ = '2.8.0'
__author__ = 'Kostiantyn Rybnikov'
__author_email__ = 'k-bx@k-bx.com'
__maintainer__ = 'Sebastien Celles'
__maintainer_email__ = "s.celles@gmail.com"
_REGEX = re.compile(
r"""
@@ -106,6 +108,12 @@ class VersionInfo(collections.namedtuple(
return NotImplemented
return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0
def __str__(self):
return format_version(*self)
def __hash__(self):
return hash(tuple(self))
def _to_dict(obj):
if isinstance(obj, VersionInfo):
@@ -358,3 +366,14 @@ def bump_build(version, token='build'):
)
return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'],
verinfo['prerelease'], verinfo['build'])
def finalize_version(version):
"""Remove any prerelease and build metadata from the version
:param version: version string
:return: the finalized version string
:rtype: str
"""
verinfo = parse(version)
return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'])
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2010-2015 Benjamin Peterson
Copyright (c) 2010-2017 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
+39 -16
View File
@@ -1,6 +1,4 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# Copyright (c) 2010-2015 Benjamin Peterson
# Copyright (c) 2010-2017 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
@@ -20,6 +18,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Utilities for writing code that runs on Python 2 and 3"""
from __future__ import absolute_import
import functools
@@ -29,7 +29,7 @@ import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.10.0"
__version__ = "1.11.0"
# Useful for very coarse version differentiation.
@@ -241,6 +241,7 @@ _moved_attributes = [
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
MovedAttribute("getoutput", "commands", "subprocess"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
@@ -262,10 +263,11 @@ _moved_attributes = [
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
@@ -337,10 +339,12 @@ _urllib_parse_moved_attributes = [
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote", "urllib", "urllib.parse"),
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
MovedAttribute("urlencode", "urllib", "urllib.parse"),
MovedAttribute("splitquery", "urllib", "urllib.parse"),
MovedAttribute("splittag", "urllib", "urllib.parse"),
MovedAttribute("splituser", "urllib", "urllib.parse"),
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
@@ -416,6 +420,8 @@ _urllib_request_moved_attributes = [
MovedAttribute("URLopener", "urllib", "urllib.request"),
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
setattr(Module_six_moves_urllib_request, attr.name, attr)
@@ -679,11 +685,15 @@ if PY3:
exec_ = getattr(moves.builtins, "exec")
def reraise(tp, value, tb=None):
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
try:
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
finally:
value = None
tb = None
else:
def exec_(_code_, _globs_=None, _locs_=None):
@@ -699,19 +709,28 @@ else:
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
try:
raise tp, value, tb
finally:
tb = None
""")
if sys.version_info[:2] == (3, 2):
exec_("""def raise_from(value, from_value):
if from_value is None:
raise value
raise value from from_value
try:
if from_value is None:
raise value
raise value from from_value
finally:
value = None
""")
elif sys.version_info[:2] > (3, 2):
exec_("""def raise_from(value, from_value):
raise value from from_value
try:
raise value from from_value
finally:
value = None
""")
else:
def raise_from(value, from_value):
@@ -802,10 +821,14 @@ def with_metaclass(meta, *bases):
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
class metaclass(type):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
@classmethod
def __prepare__(cls, name, this_bases):
return meta.__prepare__(name, bases)
return type.__new__(metaclass, 'temporary_class', (), {})
Vendored Regular → Executable
+1 -1
View File
@@ -32,7 +32,7 @@ except ImportError:
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
__license__ = 'MIT'
__version__ = '1.22'
__version__ = '1.23'
__all__ = (
'HTTPConnectionPool',
Vendored Regular → Executable
+18 -5
View File
@@ -1,5 +1,8 @@
from __future__ import absolute_import
from collections import Mapping, MutableMapping
try:
from collections.abc import Mapping, MutableMapping
except ImportError:
from collections import Mapping, MutableMapping
try:
from threading import RLock
except ImportError: # Platform-specific: No threads available
@@ -15,6 +18,7 @@ try: # Python 2.7+
from collections import OrderedDict
except ImportError:
from .packages.ordered_dict import OrderedDict
from .exceptions import InvalidHeader
from .packages.six import iterkeys, itervalues, PY3
@@ -305,13 +309,22 @@ class HTTPHeaderDict(MutableMapping):
# python2.7 does not expose a proper API for exporting multiheaders
# efficiently. This function re-reads raw lines from the message
# object and extracts the multiheaders properly.
obs_fold_continued_leaders = (' ', '\t')
headers = []
for line in message.headers:
if line.startswith((' ', '\t')):
key, value = headers[-1]
headers[-1] = (key, value + '\r\n' + line.rstrip())
continue
if line.startswith(obs_fold_continued_leaders):
if not headers:
# We received a header line that starts with OWS as described
# in RFC-7230 S3.2.4. This indicates a multiline header, but
# there exists no previous header to which we can attach it.
raise InvalidHeader(
'Header continuation with no previous header: %s' % line
)
else:
key, value = headers[-1]
headers[-1] = (key, value + ' ' + line.strip())
continue
key, value = line.split(':', 1)
headers.append((key, value.strip()))
Vendored Regular → Executable
+35 -5
View File
@@ -56,10 +56,11 @@ port_by_scheme = {
'https': 443,
}
# When updating RECENT_DATE, move it to
# within two years of the current date, and no
# earlier than 6 months ago.
RECENT_DATE = datetime.date(2016, 1, 1)
# When updating RECENT_DATE, move it to within two years of the current date,
# and not less than 6 months ago.
# Example: if Today is 2018-01-01, then RECENT_DATE should be any date on or
# after 2016-01-01 (today - 2 years) AND before 2017-07-01 (today - 6 months)
RECENT_DATE = datetime.date(2017, 6, 30)
class DummyConnection(object):
@@ -124,6 +125,35 @@ class HTTPConnection(_HTTPConnection, object):
# Superclass also sets self.source_address in Python 2.7+.
_HTTPConnection.__init__(self, *args, **kw)
@property
def host(self):
"""
Getter method to remove any trailing dots that indicate the hostname is an FQDN.
In general, SSL certificates don't include the trailing dot indicating a
fully-qualified domain name, and thus, they don't validate properly when
checked against a domain name that includes the dot. In addition, some
servers may not expect to receive the trailing dot when provided.
However, the hostname with trailing dot is critical to DNS resolution; doing a
lookup with the trailing dot will properly only resolve the appropriate FQDN,
whereas a lookup without a trailing dot will search the system's search domain
list. Thus, it's important to keep the original host around for use only in
those cases where it's appropriate (i.e., when doing DNS lookup to establish the
actual TCP connection across which we're going to send HTTP requests).
"""
return self._dns_host.rstrip('.')
@host.setter
def host(self, value):
"""
Setter for the `host` property.
We assume that only urllib3 uses the _dns_host attribute; httplib itself
only uses `host`, and it seems reasonable that other libraries follow suit.
"""
self._dns_host = value
def _new_conn(self):
""" Establish a socket connection and set nodelay settings on it.
@@ -138,7 +168,7 @@ class HTTPConnection(_HTTPConnection, object):
try:
conn = connection.create_connection(
(self.host, self.port), self.timeout, **extra_kw)
(self._dns_host, self.port), self.timeout, **extra_kw)
except SocketTimeout as e:
raise ConnectTimeoutError(
Vendored Regular → Executable
+14 -13
View File
@@ -40,13 +40,10 @@ from .util.request import set_file_position
from .util.response import assert_header_parsing
from .util.retry import Retry
from .util.timeout import Timeout
from .util.url import get_host, Url
from .util.url import get_host, Url, NORMALIZABLE_SCHEMES
from .util.queue import LifoQueue
if six.PY2:
# Queue is imported for side effects on MS Windows
import Queue as _unused_module_Queue # noqa: F401
xrange = six.moves.xrange
log = logging.getLogger(__name__)
@@ -62,13 +59,13 @@ class ConnectionPool(object):
"""
scheme = None
QueueCls = queue.LifoQueue
QueueCls = LifoQueue
def __init__(self, host, port=None):
if not host:
raise LocationValueError("No host specified.")
self.host = _ipv6_host(host).lower()
self.host = _ipv6_host(host, self.scheme)
self._proxy_host = host.lower()
self.port = port
@@ -204,8 +201,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
Return a fresh :class:`HTTPConnection`.
"""
self.num_connections += 1
log.debug("Starting new HTTP connection (%d): %s",
self.num_connections, self.host)
log.debug("Starting new HTTP connection (%d): %s:%s",
self.num_connections, self.host, self.port or "80")
conn = self.ConnectionCls(host=self.host, port=self.port,
timeout=self.timeout.connect_timeout,
@@ -411,6 +408,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
"""
Close all pooled connections and disable the pool.
"""
if self.pool is None:
return
# Disable access to the pool
old_pool, self.pool = self.pool, None
@@ -434,7 +433,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
# TODO: Add optional support for socket.gethostbyname checking.
scheme, host, port = get_host(url)
host = _ipv6_host(host).lower()
host = _ipv6_host(host, self.scheme)
# Use explicit default port for comparison when none is given
if self.port and not port:
@@ -820,8 +819,8 @@ class HTTPSConnectionPool(HTTPConnectionPool):
Return a fresh :class:`httplib.HTTPSConnection`.
"""
self.num_connections += 1
log.debug("Starting new HTTPS connection (%d): %s",
self.num_connections, self.host)
log.debug("Starting new HTTPS connection (%d): %s:%s",
self.num_connections, self.host, self.port or "443")
if not self.ConnectionCls or self.ConnectionCls is DummyConnection:
raise SSLError("Can't connect to HTTPS URL because the SSL "
@@ -886,7 +885,7 @@ def connection_from_url(url, **kw):
return HTTPConnectionPool(host, port=port, **kw)
def _ipv6_host(host):
def _ipv6_host(host, scheme):
"""
Process IPv6 address literals
"""
@@ -902,4 +901,6 @@ def _ipv6_host(host):
# percent sign might be URIencoded, convert it back into ASCII
if host.startswith('[') and host.endswith(']'):
host = host.replace('%25', '%').strip('[]')
if scheme in NORMALIZABLE_SCHEMES:
host = host.lower()
return host
Vendored Regular → Executable
View File
View File
View File
+5 -2
View File
@@ -111,6 +111,9 @@ def _cert_array_from_pem(pem_bundle):
Given a bundle of certs in PEM format, turns them into a CFArray of certs
that can be used to validate a cert chain.
"""
# Normalize the PEM bundle's line endings.
pem_bundle = pem_bundle.replace(b"\r\n", b"\n")
der_certs = [
base64.b64decode(match.group(1))
for match in _PEM_CERTS_RE.finditer(pem_bundle)
@@ -183,8 +186,8 @@ def _temporary_keychain():
# some random bytes to password-protect the keychain we're creating, so we
# ask for 40 random bytes.
random_bytes = os.urandom(40)
filename = base64.b64encode(random_bytes[:8]).decode('utf-8')
password = base64.b64encode(random_bytes[8:]) # Must be valid UTF-8
filename = base64.b16encode(random_bytes[:8]).decode('utf-8')
password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8
tempdirectory = tempfile.mkdtemp()
keychain_path = os.path.join(tempdirectory, filename).encode('utf-8')
Vendored Regular → Executable
+10 -1
View File
@@ -236,15 +236,24 @@ class AppEngineManager(RequestMethods):
encodings.remove('chunked')
urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings)
return HTTPResponse(
original_response = HTTPResponse(
# In order for decoding to work, we must present the content as
# a file-like object.
body=BytesIO(urlfetch_resp.content),
msg=urlfetch_resp.header_msg,
headers=urlfetch_resp.headers,
status=urlfetch_resp.status_code,
**response_kw
)
return HTTPResponse(
body=BytesIO(urlfetch_resp.content),
headers=urlfetch_resp.headers,
status=urlfetch_resp.status_code,
original_response=original_response,
**response_kw
)
def _get_absolute_timeout(self, timeout):
if timeout is Timeout.DEFAULT_TIMEOUT:
return None # Defer to URLFetch's default.
Vendored Regular → Executable
View File
Vendored Regular → Executable
+12 -10
View File
@@ -47,6 +47,12 @@ import OpenSSL.SSL
from cryptography import x509
from cryptography.hazmat.backends.openssl import backend as openssl_backend
from cryptography.hazmat.backends.openssl.x509 import _Certificate
try:
from cryptography.x509 import UnsupportedExtension
except ImportError:
# UnsupportedExtension is gone in cryptography >= 2.1.0
class UnsupportedExtension(Exception):
pass
from socket import timeout, error as SocketError
from io import BytesIO
@@ -199,7 +205,7 @@ def get_subj_alt_name(peer_cert):
except x509.ExtensionNotFound:
# No such extension, return the empty list.
return []
except (x509.DuplicateExtension, x509.UnsupportedExtension,
except (x509.DuplicateExtension, UnsupportedExtension,
x509.UnsupportedGeneralNameType, UnicodeError) as e:
# A problem has been found with the quality of the certificate. Assume
# no SAN field is present.
@@ -267,8 +273,7 @@ class WrappedSocket(object):
else:
raise
except OpenSSL.SSL.WantReadError:
rd = util.wait_for_read(self.socket, self.socket.gettimeout())
if not rd:
if not util.wait_for_read(self.socket, self.socket.gettimeout()):
raise timeout('The read operation timed out')
else:
return self.recv(*args, **kwargs)
@@ -289,8 +294,7 @@ class WrappedSocket(object):
else:
raise
except OpenSSL.SSL.WantReadError:
rd = util.wait_for_read(self.socket, self.socket.gettimeout())
if not rd:
if not util.wait_for_read(self.socket, self.socket.gettimeout()):
raise timeout('The read operation timed out')
else:
return self.recv_into(*args, **kwargs)
@@ -303,8 +307,7 @@ class WrappedSocket(object):
try:
return self.connection.send(data)
except OpenSSL.SSL.WantWriteError:
wr = util.wait_for_write(self.socket, self.socket.gettimeout())
if not wr:
if not util.wait_for_write(self.socket, self.socket.gettimeout()):
raise timeout()
continue
except OpenSSL.SSL.SysCallError as e:
@@ -418,7 +421,7 @@ class PyOpenSSLContext(object):
self._ctx.load_verify_locations(BytesIO(cadata))
def load_cert_chain(self, certfile, keyfile=None, password=None):
self._ctx.use_certificate_file(certfile)
self._ctx.use_certificate_chain_file(certfile)
if password is not None:
self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password)
self._ctx.use_privatekey_file(keyfile or certfile)
@@ -440,8 +443,7 @@ class PyOpenSSLContext(object):
try:
cnx.do_handshake()
except OpenSSL.SSL.WantReadError:
rd = util.wait_for_read(sock, sock.gettimeout())
if not rd:
if not util.wait_for_read(sock, sock.gettimeout()):
raise timeout('select timed out')
continue
except OpenSSL.SSL.Error as e:

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