Update deps pip-shims and requirementslib

This commit is contained in:
Frost Ming
2021-11-04 16:04:53 +08:00
parent c5cc0af9b8
commit a3ad42afa7
33 changed files with 841 additions and 1726 deletions
+5
View File
@@ -0,0 +1,5 @@
Switch the dependency resolver from ``pip-tools`` to `pip`.
Update vendor libraries:
- Update ``requirementslib`` from ``1.5.16`` to ``1.6.1``
- Update ``pip-shims`` from ``0.5.6`` to ``0.6.0``
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Hynek Schlawack
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.
-87
View File
@@ -1,87 +0,0 @@
python-dotenv
Copyright (c) 2014, Saurabh Kumar
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 python-dotenv 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 OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
django-dotenv-rw
Copyright (c) 2013, Ted Tieken
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 django-dotenv 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 OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Original django-dotenv
Copyright (c) 2013, Jacob Kaplan-Moss
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 django-dotenv 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 OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-177
View File
@@ -1,177 +0,0 @@
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
@@ -1,23 +0,0 @@
Copyright (c) Donald Stufft 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.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2018, Dan Ryan <dan@danryan.co>
Copyright (c) 2018-2021, Dan Ryan <dan@danryan.co> and Frost Ming
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
+3 -6
View File
@@ -25,19 +25,16 @@ import sys
from . import shims
__version__ = "0.5.3"
__version__ = "0.6.0"
if "pip_shims" in sys.modules:
# mainly to keep a reference to the old module on hand so it doesn't get
# weakref'd away
if __name__ != "pip_shims":
del sys.modules["pip_shims"]
if __name__ in sys.modules:
old_module = sys.modules[__name__]
old_module = sys.modules["pip_shims"]
module = sys.modules[__name__] = sys.modules["pip_shims"] = shims._new()
module = sys.modules["pip_shims"] = shims._new()
module.shims = shims
module.__dict__.update(
{
+35 -21
View File
@@ -1,8 +1,6 @@
# -*- coding=utf-8 -*-
"""
Backports and helper functionality to support using new functionality.
"""
from __future__ import absolute_import, print_function
import atexit
import contextlib
@@ -13,7 +11,6 @@ import re
import sys
import types
import six
from packaging import specifiers
from .environment import MYPY_RUNNING
@@ -27,19 +24,14 @@ from .utils import (
)
if sys.version_info[:2] < (3, 5):
from pipenv.vendor.vistir.compat import TemporaryDirectory
from backports.tempfile import TemporaryDirectory
else:
from tempfile import TemporaryDirectory
if six.PY3:
from contextlib import ExitStack
else:
from pipenv.vendor.contextlib2 import ExitStack
from contextlib import ExitStack
if MYPY_RUNNING:
from optparse import Values
from requests import Session
from typing import (
Any,
Callable,
@@ -55,7 +47,10 @@ if MYPY_RUNNING:
TypeVar,
Union,
)
from .utils import TShimmedPath, TShim, TShimmedFunc
from requests import Session
from .utils import TShim, TShimmedFunc, TShimmedPath
TFinder = TypeVar("TFinder")
TResolver = TypeVar("TResolver")
@@ -662,11 +657,16 @@ def get_package_finder(
if python_versions:
py_version_info_python = max(python_versions)
py_version_info = tuple([int(part) for part in py_version_info_python])
target_python = target_python_builder(
platform=platform,
abi=abi,
implementation=implementation,
py_version_info=py_version_info,
target_python_args = {
"platform": platform,
"platforms": [platform] if platform else None,
"abi": abi,
"abis": [abi] if abi else None,
"implementation": implementation,
"py_version_info": py_version_info,
}
target_python = call_function_with_correct_args(
target_python_builder, **target_python_args
)
build_kwargs["target_python"] = target_python
elif any(
@@ -762,11 +762,13 @@ def shim_unpack(
unpack_kwargs["only_download"] = only_download
if session is not None and "session" in required_args:
unpack_kwargs["session"] = session
if downloader_provider is not None and any(arg in required_args for arg in ("download", "downloader")):
if (
"download" in required_args or "downloader" in required_args
) and downloader_provider is not None:
arg_name = "download" if "download" in required_args else "downloader"
assert session is not None
assert progress_bar is not None
arg = {"download", "downloader"}.intersection(required_args).pop()
unpack_kwargs[arg] = downloader_provider(session, progress_bar)
unpack_kwargs[arg_name] = downloader_provider(session, progress_bar)
return unpack_fn(**unpack_kwargs) # type: ignore
@@ -920,6 +922,8 @@ def make_preparer(
preparer_args["finder"] = finder
if downloader_is_required:
preparer_args["downloader"] = downloader_provider(session, progress_bar)
if "in_tree_build" in required_args:
preparer_args["in_tree_build"] = True
req_tracker_fn = nullcontext if not req_tracker_fn else req_tracker_fn
with req_tracker_fn() as tracker_ctx:
if "req_tracker" in required_args:
@@ -1318,7 +1322,7 @@ def resolve( # noqa:C901
build_location_kwargs = {
"build_dir": kwargs["build_dir"],
"autodelete": True,
"parallel_builds": False
"parallel_builds": False,
}
call_function_with_correct_args(ireq.build_location, **build_location_kwargs)
if reqset_provider is None:
@@ -1419,6 +1423,8 @@ def build_wheel( # noqa:C901
cache_dir=None, # type: Optional[str]
use_user_site=False, # type: bool
use_pep517=None, # type: Optional[bool]
verify=False, # type: bool
editable=False, # type: bool
format_control_provider=None, # type: Optional[TShimmedFunc]
wheel_cache_provider=None, # type: Optional[TShimmedFunc]
preparer_provider=None, # type: Optional[TShimmedFunc]
@@ -1526,7 +1532,15 @@ def build_wheel( # noqa:C901
if req and not reqset and not output_dir:
output_dir = get_ireq_output_path(wheel_cache, req)
if not reqset and build_one_provider:
yield build_one_provider(req, output_dir, build_options, global_options)
build_one_kwargs = {
"req": req,
"output_dir": output_dir,
"verify": verify,
"editable": editable,
"build_options": build_options,
"global_options": global_options,
}
yield call_function_with_correct_args(build_one_provider, **build_one_kwargs)
elif build_many_provider:
yield build_many_provider(
reqset, wheel_cache, build_options, global_options, check_binary_allowed
+4 -19
View File
@@ -12,8 +12,7 @@ import operator
import sys
import types
import weakref
import six
from collections.abc import Mapping, Sequence
from . import compat
from .environment import BASE_IMPORT_PATH, MYPY_RUNNING, get_pip_version
@@ -36,18 +35,6 @@ from .utils import (
suppress_setattr,
)
# format: off
six.add_move(
six.MovedAttribute("Sequence", "collections", "collections.abc")
) # type: ignore # noqa
six.add_move(
six.MovedAttribute("Mapping", "collections", "collections.abc")
) # type: ignore # noqa
from six.moves import Sequence, Mapping # type: ignore # noqa # isort:skip
# format: on
if MYPY_RUNNING:
import packaging.version
@@ -393,7 +380,7 @@ class ShimmedPath(object):
item_value = make_method(item_value)(item_name)
elif "cls" not in callable_args and creating_classmethods:
item_value = make_classmethod(item_value)(item_name)
elif isinstance(item_value, six.string_types):
elif isinstance(item_value, str):
module_path, name = split_package(item_value)
module = cls._import_module(module_path)
item_value = getattr(module, name, None)
@@ -442,8 +429,6 @@ class ShimmedPath(object):
if not methods and not classmethods:
return provided
classname = provided.__name__
if six.PY2:
classname = classname.encode(sys.getdefaultencoding())
type_ = type(classname, (provided,), {})
if classmethods:
@@ -640,7 +625,7 @@ class ShimmedPathCollection(object):
self.pre_shim_functions = [] # type: List[Callable]
self.aliases = [] # type: List[List[str]]
if paths is not None:
if isinstance(paths, six.string_types):
if isinstance(paths, str):
self.create_path(paths, version_start=lookup_current_pip_version())
else:
self.paths.update(set(paths))
@@ -819,7 +804,7 @@ get_installed_distributions = ShimmedPathCollection(
"get_installed_distributions", ImportTypes.FUNCTION
)
get_installed_distributions.create_path(
"utils.misc.get_installed_distributions", "10", "9999"
"utils.misc.get_installed_distributions", "10", "21.2.999"
)
get_installed_distributions.create_path("utils.get_installed_distributions", "7", "9.0.3")
+6 -18
View File
@@ -8,21 +8,13 @@ import contextlib
import copy
import inspect
import sys
from collections.abc import Callable
from functools import wraps
import packaging.version
import six
from .environment import MYPY_RUNNING
# format: off
six.add_move(
six.MovedAttribute("Callable", "collections", "collections.abc")
) # type: ignore # noqa
from six.moves import Callable # type: ignore # isort:skip # noqa
# format: on
if MYPY_RUNNING:
from types import ModuleType
from typing import (
@@ -109,7 +101,7 @@ def memoize(obj):
def _parse(version):
# type: (str) -> Tuple[int, ...]
if isinstance(version, STRING_TYPES):
return tuple((int(i) for i in version.split(".")))
return tuple(int(i) for i in version.split("."))
return version
@@ -117,7 +109,7 @@ def _parse(version):
def parse_version(version):
# type: (str) -> packaging.version._BaseVersion
if not isinstance(version, STRING_TYPES):
raise TypeError("Can only derive versions from string, got {0!r}".format(version))
raise TypeError("Can only derive versions from string, got {!r}".format(version))
return packaging.version.parse(version)
@@ -196,12 +188,8 @@ def set_default_kwargs(basecls, method, *args, **default_kwargs):
prepended_defaults = prepended_defaults + (default_kwargs[arg],)
if not prepended_defaults:
return basecls
if six.PY2 and inspect.ismethod(target_method):
new_defaults = prepended_defaults + target_func.__defaults__
target_method.__func__.__defaults__ = new_defaults
else:
new_defaults = prepended_defaults + target_method.__defaults__
target_method.__defaults__ = new_defaults
new_defaults = prepended_defaults + target_method.__defaults__
target_method.__defaults__ = new_defaults
setattr(basecls, method, target_method)
return basecls
@@ -227,7 +215,7 @@ def ensure_function(parent, funcname, func):
if parent_is_module:
module = parent.__name__
elif parent_is_class:
qualname = "{0}.{1}".format(parent.__name__, qualname)
qualname = "{}.{}".format(parent.__name__, qualname)
module = getattr(parent, "__module__", None)
else:
module = getattr(parent, "__module__", None)
-54
View File
@@ -1,54 +0,0 @@
Copyright 2017- Paul Ganssle <paul@ganssle.io>
Copyright 2017- dateutil contributors (see AUTHORS file)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
The above license applies to all contributions after 2017-12-01, as well as
all contributions that have been re-licensed (see AUTHORS file for the list of
contributors who have re-licensed their code).
--------------------------------------------------------------------------------
dateutil - Extensions to the standard Python datetime module.
Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
Copyright (c) 2012-2014 - Tomi Pieviläinen <tomi.pievilainen@iki.fi>
Copyright (c) 2014-2016 - Yaron de Leeuw <me@jarondl.net>
Copyright (c) 2015- - Paul Ganssle <paul@ganssle.io>
Copyright (c) 2015- - dateutil contributors (see AUTHORS file)
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 the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The above BSD License Applies to all code, even that also covered by Apache 2.0.
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016 Steve Dower
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.
-20
View File
@@ -1,20 +0,0 @@
Copyright (c) 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.
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright 2019 Dan Ryan.
Copyright 2019-2021 Dan Ryan and Frost Ming.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -3
View File
@@ -4,13 +4,11 @@ from __future__ import absolute_import, print_function
import logging
import warnings
from vistir.compat import ResourceWarning
from .models.lockfile import Lockfile
from .models.pipfile import Pipfile
from .models.requirements import Requirement
__version__ = "1.5.16"
__version__ = "1.6.1"
logger = logging.getLogger(__name__)
+6 -3
View File
@@ -1,8 +1,9 @@
# -*- coding=utf-8 -*-
from __future__ import print_function, absolute_import
from __future__ import absolute_import, print_function
import os
from appdirs import user_cache_dir
from platformdirs import user_cache_dir
def is_type_checking():
@@ -13,5 +14,7 @@ def is_type_checking():
return TYPE_CHECKING
REQUIREMENTSLIB_CACHE_DIR = os.getenv("REQUIREMENTSLIB_CACHE_DIR", user_cache_dir("pipenv"))
REQUIREMENTSLIB_CACHE_DIR = os.getenv(
"REQUIREMENTSLIB_CACHE_DIR", user_cache_dir("pipenv")
)
MYPY_RUNNING = os.environ.get("MYPY_RUNNING", is_type_checking())
+1 -15
View File
@@ -5,20 +5,6 @@ import errno
import os
import sys
import six
from vistir.compat import FileNotFoundError
if six.PY2:
class FileExistsError(OSError):
def __init__(self, *args, **kwargs):
self.errno = errno.EEXIST
super(FileExistsError, self).__init__(*args, **kwargs)
else:
from six.moves.builtins import FileExistsError
class RequirementError(Exception):
pass
@@ -44,7 +30,7 @@ class FileCorruptException(OSError):
if not backup_path and args:
args = reversed(args)
backup_path = args.pop()
if not isinstance(backup_path, six.string_types) or not os.path.exists(
if not isinstance(backup_path, str) or not os.path.exists(
os.path.abspath(os.path.dirname(backup_path))
):
args.append(backup_path)
+62 -58
View File
@@ -6,18 +6,15 @@ import copy
import hashlib
import json
import os
import pathlib
import sys
import vistir
from appdirs import user_cache_dir
from pip_shims.shims import FAVORITE_HASH, SafeFileCache
from packaging.requirements import Requirement
from pip_shims.shims import FAVORITE_HASH, SafeFileCache
from platformdirs import user_cache_dir
from .utils import as_tuple, key_from_req, lookup_table, get_pinned_version
from ..exceptions import FileExistsError
from .utils import as_tuple, get_pinned_version, key_from_req, lookup_table
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
@@ -29,64 +26,64 @@ class CorruptCacheError(Exception):
def __str__(self):
lines = [
'The dependency cache seems to have been corrupted.',
'Inspect, or delete, the following file:',
' {}'.format(self.path),
"The dependency cache seems to have been corrupted.",
"Inspect, or delete, the following file:",
" {}".format(self.path),
]
return os.linesep.join(lines)
def read_cache_file(cache_file_path):
with open(cache_file_path, 'r') as cache_file:
with open(cache_file_path, "r") as cache_file:
try:
doc = json.load(cache_file)
except ValueError:
raise CorruptCacheError(cache_file_path)
# Check version and load the contents
assert doc['__format__'] == 1, 'Unknown cache file format'
return doc['dependencies']
assert doc["__format__"] == 1, "Unknown cache file format"
return doc["dependencies"]
class DependencyCache(object):
"""
Creates a new persistent dependency cache for the current Python version.
The cache file is written to the appropriate user cache dir for the
current platform, i.e.
"""Creates a new persistent dependency cache for the current Python
version. The cache file is written to the appropriate user cache dir for
the current platform, i.e.
~/.cache/pip-tools/depcache-pyX.Y.json
Where X.Y indicates the Python version.
"""
def __init__(self, cache_dir=None):
if cache_dir is None:
cache_dir = CACHE_DIR
if not vistir.compat.Path(CACHE_DIR).absolute().is_dir():
if not pathlib.Path(CACHE_DIR).absolute().is_dir():
try:
vistir.path.mkdir_p(os.path.abspath(cache_dir))
except (FileExistsError, OSError):
except OSError:
pass
py_version = '.'.join(str(digit) for digit in sys.version_info[:2])
cache_filename = 'depcache-py{}.json'.format(py_version)
py_version = ".".join(str(digit) for digit in sys.version_info[:2])
cache_filename = "depcache-py{}.json".format(py_version)
self._cache_file = os.path.join(cache_dir, cache_filename)
self._cache = None
@property
def cache(self):
"""
The dictionary that is the actual in-memory cache. This property
lazily loads the cache from disk.
"""The dictionary that is the actual in-memory cache.
This property lazily loads the cache from disk.
"""
if self._cache is None:
self.read_cache()
return self._cache
def as_cache_key(self, ireq):
"""
Given a requirement, return its cache key. This behavior is a little weird in order to allow backwards
compatibility with cache files. For a requirement without extras, this will return, for example:
"""Given a requirement, return its cache key. This behavior is a little
weird in order to allow backwards compatibility with cache files. For a
requirement without extras, this will return, for example:
("ipython", "2.1.0")
@@ -112,10 +109,10 @@ class DependencyCache(object):
def write_cache(self):
"""Writes the cache to disk as JSON."""
doc = {
'__format__': 1,
'dependencies': self._cache,
"__format__": 1,
"dependencies": self._cache,
}
with open(self._cache_file, 'w') as f:
with open(self._cache_file, "w") as f:
json.dump(doc, f, sort_keys=True)
def clear(self):
@@ -149,20 +146,20 @@ class DependencyCache(object):
return self.cache.get(pkgname, {}).get(pkgversion_and_extras, default)
def reverse_dependencies(self, ireqs):
"""
Returns a lookup table of reverse dependencies for all the given ireqs.
"""Returns a lookup table of reverse dependencies for all the given
ireqs.
Since this is all static, it only works if the dependency cache
contains the complete data, otherwise you end up with a partial view.
This is typically no problem if you use this function after the entire
dependency tree is resolved.
contains the complete data, otherwise you end up with a partial
view. This is typically no problem if you use this function
after the entire dependency tree is resolved.
"""
ireqs_as_cache_values = [self.as_cache_key(ireq) for ireq in ireqs]
return self._reverse_dependencies(ireqs_as_cache_values)
def _reverse_dependencies(self, cache_keys):
"""
Returns a lookup table of reverse dependencies for all the given cache keys.
"""Returns a lookup table of reverse dependencies for all the given
cache keys.
Example input:
@@ -177,35 +174,39 @@ class DependencyCache(object):
'flake8': [],
'mccabe': ['flake8'],
'pyflakes': ['flake8']}
"""
# First, collect all the dependencies into a sequence of (parent, child) tuples, like [('flake8', 'pep8'),
# ('flake8', 'mccabe'), ...]
return lookup_table((key_from_req(Requirement(dep_name)), name)
for name, version_and_extras in cache_keys
for dep_name in self.cache[name][version_and_extras])
return lookup_table(
(key_from_req(Requirement(dep_name)), name)
for name, version_and_extras in cache_keys
for dep_name in self.cache[name][version_and_extras]
)
class HashCache(SafeFileCache):
"""Caches hashes of PyPI artifacts so we do not need to re-download them.
Hashes are only cached when the URL appears to contain a hash in it and the
cache key includes the hash value returned from the server). This ought to
avoid ssues where the location on the server changes.
Hashes are only cached when the URL appears to contain a hash in it
and the cache key includes the hash value returned from the server).
This ought to avoid ssues where the location on the server changes.
"""
def __init__(self, *args, **kwargs):
session = kwargs.pop("session", None)
if not session:
import requests
session = requests.session()
atexit.register(session.close)
cache_dir = kwargs.pop('cache_dir', CACHE_DIR)
cache_dir = kwargs.pop("cache_dir", CACHE_DIR)
self.session = session
kwargs.setdefault('directory', os.path.join(cache_dir, 'hash-cache'))
kwargs.setdefault("directory", os.path.join(cache_dir, "hash-cache"))
super(HashCache, self).__init__(*args, **kwargs)
def get_hash(self, location):
from pip_shims import VcsSupport
# if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it
hash_value = None
vcs = VcsSupport()
@@ -216,13 +217,17 @@ class HashCache(SafeFileCache):
can_hash = new_location.hash
if can_hash:
# hash url WITH fragment
hash_value = self._get_file_hash(new_location.url) if not new_location.url.startswith("ssh") else None
hash_value = (
self._get_file_hash(new_location.url)
if not new_location.url.startswith("ssh")
else None
)
if not hash_value:
hash_value = self._get_file_hash(new_location)
hash_value = hash_value.encode('utf8')
hash_value = hash_value.encode("utf8")
if can_hash:
self.set(new_location.url, hash_value)
return hash_value.decode('utf8')
return hash_value.decode("utf8")
def _get_file_hash(self, location):
h = hashlib.new(FAVORITE_HASH)
@@ -242,6 +247,7 @@ class _JSONCache(object):
Where X.Y indicates the Python version.
"""
filename_format = None
def __init__(self, cache_dir=CACHE_DIR):
@@ -287,21 +293,19 @@ class _JSONCache(object):
return name, "{}{}".format(version, extras_string)
def read_cache(self):
"""Reads the cached contents into memory.
"""
"""Reads the cached contents into memory."""
if os.path.exists(self._cache_file):
self._cache = read_cache_file(self._cache_file)
else:
self._cache = {}
def write_cache(self):
"""Writes the cache to disk as JSON.
"""
"""Writes the cache to disk as JSON."""
doc = {
'__format__': 1,
'dependencies': self._cache,
"__format__": 1,
"dependencies": self._cache,
}
with open(self._cache_file, 'w') as f:
with open(self._cache_file, "w") as f:
json.dump(doc, f, sort_keys=True)
def clear(self):
@@ -336,6 +340,6 @@ class _JSONCache(object):
class RequiresPythonCache(_JSONCache):
"""Cache a candidate's Requires-Python information.
"""
"""Cache a candidate's Requires-Python information."""
filename_format = "pyreqcache-py{python_version}.json"
+38 -33
View File
@@ -5,6 +5,8 @@ import contextlib
import copy
import functools
import os
from contextlib import ExitStack
from json import JSONDecodeError
import attr
import packaging.markers
@@ -12,7 +14,7 @@ import packaging.version
import pip_shims.shims
import requests
from packaging.utils import canonicalize_name
from vistir.compat import JSONDecodeError, fs_str
from vistir.compat import fs_str
from vistir.contextmanagers import cd, temp_environ
from vistir.path import create_tracked_tempdir
@@ -32,32 +34,28 @@ from .utils import (
version_from_ireq,
)
try:
from contextlib import ExitStack
except ImportError:
from contextlib2 import ExitStack
if MYPY_RUNNING:
from typing import (
Any,
Dict,
List,
Generator,
List,
Optional,
Union,
Set,
Text,
Tuple,
TypeVar,
Text,
Set,
Union,
)
from pip_shims.shims import (
InstallRequirement,
InstallationCandidate,
PackageFinder,
Command,
)
from packaging.requirements import Requirement as PackagingRequirement
from packaging.markers import Marker
from packaging.requirements import Requirement as PackagingRequirement
from pip_shims.shims import (
Command,
InstallationCandidate,
InstallRequirement,
PackageFinder,
)
TRequirement = TypeVar("TRequirement")
RequirementType = TypeVar(
@@ -88,8 +86,8 @@ def _get_filtered_versions(ireq, versions, prereleases):
def find_all_matches(finder, ireq, pre=False):
# type: (PackageFinder, InstallRequirement, bool) -> List[InstallationCandidate]
"""Find all matching dependencies using the supplied finder and the
given ireq.
"""Find all matching dependencies using the supplied finder and the given
ireq.
:param finder: A package finder for discovering matching candidates.
:type finder: :class:`~pip._internal.index.PackageFinder`
@@ -130,7 +128,8 @@ class AbstractDependency(object):
@property
def version_set(self):
"""Return the set of versions for the candidates in this abstract dependency.
"""Return the set of versions for the candidates in this abstract
dependency.
:return: A set of matching versions
:rtype: set(str)
@@ -141,8 +140,8 @@ class AbstractDependency(object):
return set(packaging.version.parse(version_from_ireq(c)) for c in self.candidates)
def compatible_versions(self, other):
"""Find compatible version numbers between this abstract
dependency and another one.
"""Find compatible version numbers between this abstract dependency and
another one.
:param other: An abstract dependency to compare with.
:type other: :class:`~requirementslib.models.dependency.AbstractDependency`
@@ -230,8 +229,9 @@ class AbstractDependency(object):
@classmethod
def from_requirement(cls, requirement, parent=None):
"""Creates a new :class:`~requirementslib.models.dependency.AbstractDependency`
from a :class:`~requirementslib.models.requirements.Requirement` object.
"""Creates a new
:class:`~requirementslib.models.dependency.AbstractDependency` from a
:class:`~requirementslib.models.requirements.Requirement` object.
This class is used to find all candidates matching a given set of specifiers
and a given requirement.
@@ -358,7 +358,8 @@ def get_dependencies(ireq, sources=None, parent=None):
def get_dependencies_from_wheel_cache(ireq):
# type: (pip_shims.shims.InstallRequirement) -> Optional[Set[pip_shims.shims.InstallRequirement]]
"""Retrieves dependencies for the given install requirement from the wheel cache.
"""Retrieves dependencies for the given install requirement from the wheel
cache.
:param ireq: A single InstallRequirement
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
@@ -384,7 +385,8 @@ def _marker_contains_extra(ireq):
def get_dependencies_from_json(ireq):
"""Retrieves dependencies for the given install requirement from the json api.
"""Retrieves dependencies for the given install requirement from the json
api.
:param ireq: A single InstallRequirement
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
@@ -433,7 +435,8 @@ def get_dependencies_from_json(ireq):
def get_dependencies_from_cache(ireq):
"""Retrieves dependencies for the given install requirement from the dependency cache.
"""Retrieves dependencies for the given install requirement from the
dependency cache.
:param ireq: A single InstallRequirement
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
@@ -474,7 +477,8 @@ def is_python(section):
def get_dependencies_from_index(dep, sources=None, pip_options=None, wheel_cache=None):
"""Retrieves dependencies for the given install requirement from the pip resolver.
"""Retrieves dependencies for the given install requirement from the pip
resolver.
:param dep: A single InstallRequirement
:type dep: :class:`~pip._internal.req.req_install.InstallRequirement`
@@ -506,8 +510,8 @@ def get_dependencies_from_index(dep, sources=None, pip_options=None, wheel_cache
return requirements
def get_pip_options(args=[], sources=None, pip_command=None):
"""Build a pip command from a list of sources
def get_pip_options(args=None, sources=None, pip_command=None):
"""Build a pip command from a list of sources.
:param args: positional arguments passed through to the pip parser
:param sources: A list of pipfile-formatted sources, defaults to None
@@ -523,7 +527,7 @@ def get_pip_options(args=[], sources=None, pip_command=None):
if not sources:
sources = [{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}]
_ensure_dir(CACHE_DIR)
pip_args = args
pip_args = args or []
pip_args = prepare_pip_source_args(sources, pip_args)
pip_options, _ = pip_command.parser.parse_args(pip_args)
pip_options.cache_dir = CACHE_DIR
@@ -532,7 +536,7 @@ def get_pip_options(args=[], sources=None, pip_command=None):
def get_finder(sources=None, pip_command=None, pip_options=None):
# type: (List[Dict[S, Union[S, bool]]], Optional[Command], Any) -> PackageFinder
"""Get a package finder for looking up candidates to install
"""Get a package finder for looking up candidates to install.
:param sources: A list of pipfile-formatted sources, defaults to None
:param sources: list[dict], optional
@@ -567,7 +571,8 @@ def start_resolver(finder=None, session=None, wheel_cache=None):
:param :class:`~requests.Session` session: A session instance
:param :class:`~pip._internal.cache.WheelCache` wheel_cache: A pip WheelCache instance
:return: A 3-tuple of finder, preparer, resolver
:rtype: (:class:`~pip._internal.operations.prepare.RequirementPreparer`, :class:`~pip._internal.resolve.Resolver`)
:rtype: (:class:`~pip._internal.operations.prepare.RequirementPreparer`,
:class:`~pip._internal.resolve.Resolver`)
"""
pip_command = get_pip_command()
+10 -8
View File
@@ -4,11 +4,11 @@ from __future__ import absolute_import, print_function
import copy
import itertools
import os
from json import JSONDecodeError
from pathlib import Path
import attr
import plette.lockfiles
import six
from vistir.compat import FileNotFoundError, JSONDecodeError, Path
from ..exceptions import LockfileCorruptException, MissingParameter, PipfileNotFound
from ..utils import is_editable, is_vcs, merge_items
@@ -16,11 +16,11 @@ from .project import ProjectFile
from .requirements import Requirement
from .utils import optional_instance_of
DEFAULT_NEWLINES = six.text_type("\n")
DEFAULT_NEWLINES = "\n"
def preferred_newlines(f):
if isinstance(f.newlines, six.text_type):
if isinstance(f.newlines, str):
return f.newlines
return DEFAULT_NEWLINES
@@ -36,7 +36,7 @@ class Lockfile(object):
_dev_requirements = attr.ib(default=attr.Factory(list), type=list)
projectfile = attr.ib(validator=is_projectfile, type=ProjectFile)
_lockfile = attr.ib(validator=is_lockfile, type=plette.lockfiles.Lockfile)
newlines = attr.ib(default=DEFAULT_NEWLINES, type=six.text_type)
newlines = attr.ib(default=DEFAULT_NEWLINES, type=str)
@path.default
def _get_path(self):
@@ -118,7 +118,8 @@ class Lockfile(object):
@classmethod
def read_projectfile(cls, path):
"""Read the specified project file and provide an interface for writing/updating.
"""Read the specified project file and provide an interface for
writing/updating.
:param str path: Path to the target file.
:return: A project file with the model and location for interaction
@@ -261,7 +262,8 @@ class Lockfile(object):
return self._lockfile.default
def get_requirements(self, dev=True, only=False):
"""Produces a generator which generates requirements from the desired section.
"""Produces a generator which generates requirements from the desired
section.
:param bool dev: Indicates whether to use dev requirements, defaults to False
:return: Requirements from the relevant the relevant pipfile
@@ -297,7 +299,7 @@ class Lockfile(object):
self.projectfile.write()
def as_requirements(self, include_hashes=False, dev=False):
"""Returns a list of requirements in pip-style format"""
"""Returns a list of requirements in pip-style format."""
lines = []
section = self.dev_requirements if dev else self.requirements
for req in section:
+21 -26
View File
@@ -2,25 +2,22 @@
import itertools
import operator
import re
from collections.abc import Mapping, Set
from functools import lru_cache, reduce
import attr
import distlib.markers
import packaging.version
import six
from packaging.markers import InvalidMarker, Marker
from packaging.specifiers import Specifier, SpecifierSet
from vistir.compat import Mapping, Set, lru_cache
from vistir.misc import dedup
from ..environment import MYPY_RUNNING
from ..exceptions import RequirementError
from .utils import filter_none, validate_markers
from six.moves import reduce # isort:skip
if MYPY_RUNNING:
from typing import Optional, List, Type, Any, Tuple, Union, AnyStr, Text, Iterator
from typing import Any, AnyStr, Iterator, List, Optional, Text, Tuple, Type, Union
STRING_TYPE = Union[str, bytes, Text]
@@ -130,7 +127,7 @@ def _tuplize_version(version):
@lru_cache(maxsize=1024)
def _format_version(version):
# type: (Tuple[int, ...]) -> STRING_TYPE
if not isinstance(version, six.string_types):
if not isinstance(version, str):
return ".".join(str(i) for i in version)
return version
@@ -212,7 +209,8 @@ def _group_by_op(specs):
# TODO: rename this to something meaningful
def normalize_specifier_set(specs):
# type: (Union[str, SpecifierSet]) -> Optional[Set[Specifier]]
"""Given a specifier set, a string, or an iterable, normalize the specifiers
"""Given a specifier set, a string, or an iterable, normalize the
specifiers.
.. note:: This function exists largely to deal with ``pyzmq`` which handles
the ``requires_python`` specifier incorrectly, using ``3.7*`` rather than
@@ -228,7 +226,7 @@ def normalize_specifier_set(specs):
if isinstance(specs, set):
return specs
# when we aren't dealing with a string at all, we can normalize this as usual
elif not isinstance(specs, six.string_types):
elif not isinstance(specs, str):
return {_format_pyspec(spec) for spec in specs}
spec_list = []
for spec in specs.split(","):
@@ -368,10 +366,11 @@ def _strip_pyversion(elements):
def _strip_marker_elem(elem_name, elements):
"""Remove the supplied element from the marker.
This is not a comprehensive implementation, but relies on an important
characteristic of metadata generation: The element's operand is always
associated with an "and" operator. This means that we can simply remove the
operand and the "and" operator associated with it.
This is not a comprehensive implementation, but relies on an
important characteristic of metadata generation: The element's
operand is always associated with an "and" operator. This means that
we can simply remove the operand and the "and" operator associated
with it.
"""
extra_indexes = []
@@ -425,8 +424,8 @@ def get_without_extra(marker):
def get_without_pyversion(marker):
"""Built a new marker without the `python_version` part.
This could return `None` if the `python_version` section is the only section in the
marker.
This could return `None` if the `python_version` section is the only
section in the marker.
"""
return _get_stripped_marker(marker, _strip_pyversion)
@@ -444,7 +443,7 @@ def _markers_collect_extras(markers, collection):
def _markers_collect_pyversions(markers, collection):
local_collection = []
marker_format_str = "{0}"
for i, el in enumerate(reversed(markers)):
for el in reversed(markers):
if isinstance(el, tuple) and el[0].value == "python_version":
new_marker = str(gen_marker(el))
local_collection.append(marker_format_str.format(new_marker))
@@ -490,8 +489,7 @@ def get_contained_extras(marker):
@lru_cache(maxsize=1024)
def get_contained_pyversions(marker):
"""Collect all `python_version` operands from a marker.
"""
"""Collect all `python_version` operands from a marker."""
collection = []
if not marker:
@@ -522,8 +520,7 @@ def get_contained_pyversions(marker):
@lru_cache(maxsize=128)
def contains_extra(marker):
"""Check whehter a marker contains an "extra == ..." operand.
"""
"""Check whehter a marker contains an "extra == ..." operand."""
if not marker:
return False
marker = _ensure_marker(marker)
@@ -532,8 +529,7 @@ def contains_extra(marker):
@lru_cache(maxsize=128)
def contains_pyversion(marker):
"""Check whether a marker contains a python_version operand.
"""
"""Check whether a marker contains a python_version operand."""
if not marker:
return False
@@ -543,8 +539,8 @@ def contains_pyversion(marker):
def _split_specifierset_str(specset_str, prefix="=="):
# type: (str, str) -> Set[Specifier]
"""
Take a specifierset string and split it into a list to join for specifier sets
"""Take a specifierset string and split it into a list to join for
specifier sets.
:param str specset_str: A string containing python versions, often comma separated
:param str prefix: A prefix to use when generating the specifier set
@@ -564,8 +560,7 @@ def _split_specifierset_str(specset_str, prefix="=="):
def _get_specifiers_from_markers(marker_item):
"""
Given a marker item, get specifiers from the version marker
"""Given a marker item, get specifiers from the version marker.
:param :class:`~packaging.markers.Marker` marker_sequence: A marker describing a version constraint
:return: A set of specifiers corresponding to the marker constraint
+24 -18
View File
@@ -8,6 +8,8 @@ import operator
import os
import zipfile
from collections import defaultdict
from functools import reduce
from typing import Sequence
import attr
import dateutil.parser
@@ -15,7 +17,6 @@ import distlib.metadata
import distlib.wheel
import packaging.version
import requests
import six
import vistir
from packaging.markers import Marker
from packaging.requirements import Requirement as PackagingRequirement
@@ -35,12 +36,6 @@ from .markers import (
from .requirements import Requirement
from .utils import filter_dict, get_pinned_version, is_pinned_requirement
# fmt: off
from six.moves import Sequence # type: ignore # isort:skip
from six.moves import reduce # type: ignore # isort:skip
# fmt: on # isort:skip
ch = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
ch.setFormatter(formatter)
@@ -64,7 +59,9 @@ if MYPY_RUNNING:
TypeVar,
Union,
)
from attr import Attribute # noqa
from .setup_info import SetupInfo
TAttrsClass = TypeVar("TAttrsClass")
@@ -318,7 +315,9 @@ class Dependency(object):
return cls(
name=req.name,
specifier=req.specifier,
extras=tuple(sorted(set(req.extras))) if req.extras is not None else req.extras,
extras=tuple(sorted(set(req.extras)))
if req.extras is not None
else req.extras,
requirement=req,
from_extras=from_extras,
python_version=python_version,
@@ -364,7 +363,9 @@ class Dependency(object):
return cls(
name=info.name,
specifier=req.specifier,
extras=tuple(sorted(set(req.extras))) if req.extras is not None else req.extras,
extras=tuple(sorted(set(req.extras)))
if req.extras is not None
else req.extras,
requirement=req,
from_extras=None,
python_version=SpecifierSet(requires_python_str),
@@ -625,15 +626,15 @@ class ReleaseUrl(object):
# type: (TReleaseUrlDict, Optional[str]) -> "ReleaseUrl"
valid_digest_keys = set("{0}_digest".format(k) for k in VALID_ALGORITHMS.keys())
digest_keys = set(release_dict.keys()) & valid_digest_keys
creation_kwargs = {} # type: Dict[str, Union[bool, int, str, Digest, TDigestDict]]
creation_kwargs = {
k: v for k, v in release_dict.items() if k not in digest_keys
}
creation_kwargs = (
{}
) # type: Dict[str, Union[bool, int, str, Digest, TDigestDict]]
creation_kwargs = {k: v for k, v in release_dict.items() if k not in digest_keys}
if name is not None:
creation_kwargs["name"] = name
for k in digest_keys:
digest = release_dict[k]
if not isinstance(digest, six.string_types):
if not isinstance(digest, str):
raise TypeError("Digests must be strings, got {!r}".format(digest))
creation_kwargs[k] = Digest.create(k.replace("_digest", ""), digest)
release_url = cls(**filter_dict(creation_kwargs)) # type: ignore
@@ -827,7 +828,8 @@ def get_releases_from_package(releases, name=None):
@attr.s(frozen=True)
class ReleaseCollection(object):
releases = attr.ib(
factory=list, converter=instance_check_converter(list, get_releases_from_package), # type: ignore
factory=list,
converter=instance_check_converter(list, get_releases_from_package), # type: ignore
) # type: List[Release]
def __iter__(self):
@@ -894,7 +896,7 @@ def convert_releases_to_collection(releases, name=None):
def split_keywords(value):
# type: (Union[str, List]) -> List[str]
if value and isinstance(value, six.string_types):
if value and isinstance(value, str):
return value.split(",")
elif isinstance(value, list):
return value
@@ -1027,7 +1029,9 @@ class Package(object):
@urls.default
def _get_urls_collection(self):
return functools.partial(convert_release_urls_to_collection, urls=[], name=self.name)
return functools.partial(
convert_release_urls_to_collection, urls=[], name=self.name
)
@property
def name(self):
@@ -1173,7 +1177,9 @@ class Package(object):
try:
version = next(iter(specset.filter((r.version for r in sorted_releases))))
except StopIteration:
logger.info("No version of {0} matches specifier: {1}".format(sub_dep, specset))
logger.info(
"No version of {0} matches specifier: {1}".format(sub_dep, specset)
)
logger.info(
"Available versions: {0}".format(
" ".join([r.version for r in sorted_releases])
+8 -11
View File
@@ -5,13 +5,12 @@ from __future__ import absolute_import, print_function, unicode_literals
import copy
import itertools
import os
import sys
from pathlib import Path
import attr
import plette.models.base
import plette.pipfiles
import tomlkit
from vistir.compat import FileNotFoundError, Path
from ..environment import MYPY_RUNNING
from ..exceptions import RequirementError
@@ -21,7 +20,7 @@ from .requirements import Requirement
from .utils import get_url_name, optional_instance_of, tomlkit_value_to_python
if MYPY_RUNNING:
from typing import Union, Any, Dict, Iterable, Mapping, List, Text
from typing import Any, Dict, Iterable, List, Mapping, Text, Union
package_type = Dict[Text, Dict[Text, Union[List[Text], Text]]]
source_type = Dict[Text, Union[Text, bool]]
@@ -68,8 +67,8 @@ class PipfileLoader(plette.pipfiles.Pipfile):
@classmethod
def ensure_package_sections(cls, data):
# type: (tomlkit.toml_document.TOMLDocument[Text, Any]) -> tomlkit.toml_document.TOMLDocument[Text, Any]
"""
Ensure that all pipfile package sections are present in the given toml document
"""Ensure that all pipfile package sections are present in the given
toml document.
:param :class:`~tomlkit.toml_document.TOMLDocument` data: The toml document to
ensure package sections are present on
@@ -256,7 +255,8 @@ class Pipfile(object):
@classmethod
def read_projectfile(cls, path):
# type: (Text) -> ProjectFile
"""Read the specified project file and provide an interface for writing/updating.
"""Read the specified project file and provide an interface for
writing/updating.
:param Text path: Path to the target file.
:return: A project file with the model and location for interaction
@@ -268,8 +268,7 @@ class Pipfile(object):
@classmethod
def load_projectfile(cls, path, create=False):
# type: (Text, bool) -> ProjectFile
"""
Given a path, load or create the necessary pipfile.
"""Given a path, load or create the necessary pipfile.
:param Text path: Path to the project root or pipfile
:param bool create: Whether to create the pipfile if not found, defaults to True
@@ -294,8 +293,7 @@ class Pipfile(object):
@classmethod
def load(cls, path, create=False):
# type: (Text, bool) -> Pipfile
"""
Given a path, load or create the necessary pipfile.
"""Given a path, load or create the necessary pipfile.
:param Text path: Path to the project root or pipfile
:param bool create: Whether to create the pipfile if not found, defaults to True
@@ -378,7 +376,6 @@ class Pipfile(object):
@property
def build_backend(self):
# type: () -> Text
pyproject = self.path.parent.joinpath("pyproject.toml")
if not self.build_system:
self._read_pyproject()
return self.build_system.get("build-backend", None)
+3 -6
View File
@@ -11,9 +11,7 @@ import packaging.markers
import packaging.utils
import plette
import plette.models
import six
import tomlkit
from vistir.compat import FileNotFoundError
SectionDifference = collections.namedtuple("SectionDifference", ["inthis", "inthat"])
FileDifference = collections.namedtuple("FileDifference", ["default", "develop"])
@@ -39,15 +37,14 @@ DEFAULT_NEWLINES = "\n"
def preferred_newlines(f):
if isinstance(f.newlines, six.text_type):
if isinstance(f.newlines, str):
return f.newlines
return DEFAULT_NEWLINES
@attr.s
class ProjectFile(object):
"""A file in the Pipfile project.
"""
"""A file in the Pipfile project."""
location = attr.ib()
line_ending = attr.ib()
@@ -74,7 +71,7 @@ class ProjectFile(object):
self.model.dump(f)
def dumps(self):
strio = six.StringIO()
strio = io.StringIO()
self.model.dump(strio)
return strio.getvalue()
+36 -38
View File
@@ -8,10 +8,13 @@ import os
import sys
from contextlib import contextmanager
from distutils.sysconfig import get_python_lib
from functools import lru_cache
from pathlib import Path
from urllib import parse as urllib_parse
from urllib.parse import unquote
import attr
import pip_shims
import six
from cached_property import cached_property
from packaging.markers import Marker
from packaging.requirements import Requirement as PackagingRequirement
@@ -22,9 +25,6 @@ from packaging.specifiers import (
SpecifierSet,
)
from packaging.utils import canonicalize_name
from six.moves.urllib import parse as urllib_parse
from six.moves.urllib.parse import unquote
from vistir.compat import FileNotFoundError, Path, lru_cache
from vistir.contextmanagers import temp_path
from vistir.misc import dedup
from vistir.path import (
@@ -47,9 +47,7 @@ from ..utils import (
is_vcs,
strip_ssh_from_git_uri,
)
from .markers import (
normalize_marker_str,
)
from .markers import normalize_marker_str
from .setup_info import (
SetupInfo,
_prepare_wheel_building_kwargs,
@@ -87,34 +85,36 @@ from .utils import (
if MYPY_RUNNING:
from typing import (
Optional,
TypeVar,
List,
Dict,
Union,
Any,
Tuple,
AnyStr,
Dict,
FrozenSet,
Generator,
List,
Optional,
Sequence,
Set,
AnyStr,
Text,
Generator,
FrozenSet,
Tuple,
TypeVar,
Union,
)
from pip_shims.shims import (
Link,
InstallRequirement,
PackageFinder,
InstallationCandidate,
InstallRequirement,
Link,
PackageFinder,
)
RequirementType = TypeVar(
"RequirementType", covariant=True, bound=PackagingRequirement
)
F = TypeVar("F", "FileRequirement", "VCSRequirement", covariant=True)
from six.moves.urllib.parse import SplitResult
from .vcs import VCSRepository
from urllib.parse import SplitResult
from .dependencies import AbstractDependency
from .vcs import VCSRepository
NON_STRING_ITERABLE = Union[List, Set, Tuple]
STRING_TYPE = Union[str, bytes, Text]
@@ -446,7 +446,7 @@ class Line(object):
def specifiers(self, specifiers):
# type: (Union[Text, str, SpecifierSet]) -> None
if not isinstance(specifiers, SpecifierSet):
if isinstance(specifiers, six.string_types):
if isinstance(specifiers, str):
specifiers = SpecifierSet(specifiers)
else:
raise TypeError("Must pass a string or a SpecifierSet")
@@ -887,7 +887,7 @@ class Line(object):
# type: () -> Dict[Any, Any]
if self.is_local and self.path and is_installable_dir(self.path):
if self.setup_py:
return ast_parse_setup_py(self.setup_py)
return ast_parse_setup_py(self.setup_py, raising=False)
return {}
@vcsrepo.setter
@@ -1004,7 +1004,7 @@ class Line(object):
parsed_setup_py = self.parsed_setup_py
if parsed_setup_py:
name = parsed_setup_py.get("name", "")
if name and isinstance(name, six.string_types):
if name and isinstance(name, str):
return name
return None
@@ -1374,7 +1374,7 @@ class NamedRequirement(object):
creation_args["version"] = version # type: ignore
req = init_requirement("{0}{1}".format(name, version))
if req and extras and req.extras and isinstance(req.extras, tuple):
if isinstance(extras, six.string_types):
if isinstance(extras, str):
req.extras = (extras) + tuple(["{0}".format(xtra) for xtra in req.extras])
elif isinstance(extras, (tuple, list)):
req.extras += tuple(extras)
@@ -1648,7 +1648,7 @@ class FileRequirement(object):
elif (
getattr(self, "req", None)
and self.req is not None
and getattr(self.req, "url")
and getattr(self.req, "url", None)
):
return self.req.url
elif self.link is not None:
@@ -1713,7 +1713,7 @@ class FileRequirement(object):
elif (
getattr(self, "req", None)
and self.req is not None
and (getattr(self.req, "url") and self.req.url is not None)
and (getattr(self.req, "url", None) and self.req.url is not None)
):
uri = self.req.url
if uri and is_file_url(uri):
@@ -1765,7 +1765,7 @@ class FileRequirement(object):
uri = pipfile.get("uri")
fil = pipfile.get("file")
path = pipfile.get("path")
if path and isinstance(path, six.string_types):
if path and isinstance(path, str):
if isinstance(path, Path) and not path.is_absolute():
path = get_converted_relative_path(path.as_posix())
elif not os.path.isabs(path):
@@ -1790,7 +1790,7 @@ class FileRequirement(object):
if not uri:
uri = pip_shims.shims.path_to_url(path)
link_info = None # type: Optional[LinkInfo]
if uri and isinstance(uri, six.string_types):
if uri and isinstance(uri, str):
link_info = cls.get_link_from_line(uri)
else:
raise ValueError(
@@ -1826,7 +1826,7 @@ class FileRequirement(object):
else:
if link:
line = link.url
elif uri and isinstance(uri, six.string_types):
elif uri and isinstance(uri, str):
line = uri
else:
raise ValueError(
@@ -2259,7 +2259,7 @@ class VCSRequirement(FileRequirement):
if key in VCS_LIST and key in pipfile_keys:
creation_args["vcs"] = key
target = pipfile[key]
if isinstance(target, six.string_types):
if isinstance(target, str):
drive, path = os.path.splitdrive(target)
if (
not drive
@@ -2305,9 +2305,7 @@ class VCSRequirement(FileRequirement):
self._parsed_line.is_direct_url and self._parsed_line.line_with_prefix
):
return self._parsed_line.line_with_prefix
elif getattr(self, "_base_line", None) and (
isinstance(self._base_line, six.string_types)
):
elif getattr(self, "_base_line", None) and (isinstance(self._base_line, str)):
base = self._base_line
else:
base = getattr(self, "link", self.get_link()).url
@@ -2439,7 +2437,7 @@ class Requirement(object):
new_hashes = set() # type: Set[STRING_TYPE]
if self.hashes is not None:
new_hashes |= set(self.hashes)
if isinstance(hashes, six.string_types):
if isinstance(hashes, str):
new_hashes.add(hashes)
else:
new_hashes |= set(hashes)
@@ -2462,7 +2460,7 @@ class Requirement(object):
def hashes_as_pip(self):
# type: () -> STRING_TYPE
hashes = self.get_hashes_as_pip()
assert isinstance(hashes, six.string_types)
assert isinstance(hashes, str)
return hashes
@property
@@ -2972,8 +2970,8 @@ class Requirement(object):
from .dependencies import (
AbstractDependency,
get_dependencies,
get_abstract_dependencies,
get_dependencies,
)
if not self.abstract_dep:
@@ -3003,7 +3001,7 @@ class Requirement(object):
:rtype: list[ :class:`~pip._internal.index.InstallationCandidate` ]
"""
from .dependencies import get_finder, find_all_matches
from .dependencies import find_all_matches, get_finder
if not finder:
_, finder = get_finder(sources=sources)
+15 -13
View File
@@ -2,7 +2,6 @@
from contextlib import contextmanager
import attr
import six
from pip_shims.shims import Wheel
from .cache import HashCache
@@ -62,8 +61,8 @@ class DependencyResolver(object):
return list(self.pinned_deps.values())
def add_abstract_dep(self, dep):
"""Add an abstract dependency by either creating a new entry or
merging with an old one.
"""Add an abstract dependency by either creating a new entry or merging
with an old one.
:param dep: An abstract dependency to add
:type dep: :class:`~requirementslib.models.dependency.AbstractDependency`
@@ -85,10 +84,12 @@ class DependencyResolver(object):
self.dep_dict[dep.name] = dep
def pin_deps(self):
"""Pins the current abstract dependencies and adds them to the history dict.
"""Pins the current abstract dependencies and adds them to the history
dict.
Adds any new dependencies to the abstract dependencies already present by
merging them together to form new, compatible abstract dependencies.
Adds any new dependencies to the abstract dependencies already
present by merging them together to form new, compatible
abstract dependencies.
"""
for name in list(self.dep_dict.keys()):
@@ -122,7 +123,8 @@ class DependencyResolver(object):
break
def resolve(self, root_nodes, max_rounds=20):
"""Resolves dependencies using a backtracking resolver and multiple endpoints.
"""Resolves dependencies using a backtracking resolver and multiple
endpoints.
Note: this resolver caches aggressively.
Runs for *max_rounds* or until any two pinning rounds yield the same outcome.
@@ -141,11 +143,11 @@ class DependencyResolver(object):
# Coerce input into AbstractDependency instances.
# We accept str, Requirement, and AbstractDependency as input.
from .dependencies import AbstractDependency
from ..utils import log
from .dependencies import AbstractDependency
for dep in root_nodes:
if isinstance(dep, six.string_types):
if isinstance(dep, str):
dep = AbstractDependency.from_string(dep)
elif not isinstance(dep, AbstractDependency):
dep = AbstractDependency.from_requirement(dep)
@@ -227,11 +229,11 @@ class DependencyResolver(object):
@contextmanager
def allow_all_wheels(self):
"""
Monkey patches pip.Wheel to allow wheels from all platforms and Python versions.
"""Monkey patches pip.Wheel to allow wheels from all platforms and
Python versions.
This also saves the candidate cache and set a new one, or else the results from the
previous non-patched calls will interfere.
This also saves the candidate cache and set a new one, or else
the results from the previous non-patched calls will interfere.
"""
def _wheel_supported(self, tags=None):
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -1,11 +1,13 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
from urllib.parse import quote
from urllib.parse import unquote as url_unquote
from urllib.parse import unquote_plus
import attr
import pip_shims.shims
from orderedmultidict import omdict
from six.moves.urllib.parse import quote, unquote_plus, unquote as url_unquote
from urllib3 import util as urllib3_util
from urllib3.util import parse_url as urllib3_parse
from urllib3.util.url import Url
@@ -14,9 +16,9 @@ from ..utils import is_installable_file
from .utils import extras_to_string, parse_extras
if MYPY_RUNNING:
from typing import Dict, List, Optional, Text, Tuple, TypeVar, Union
from typing import Dict, Optional, Text, Tuple, TypeVar, Union
from pip_shims.shims import Link
from vistir.compat import Path
_T = TypeVar("_T")
STRING_TYPE = Union[bytes, str, Text]
+70 -89
View File
@@ -7,9 +7,10 @@ import re
import string
import sys
from collections import defaultdict
from functools import lru_cache
from itertools import chain, groupby
from pathlib import Path
import six
import tomlkit
from attr import validators
from packaging.markers import InvalidMarker, Marker, Op, Value, Variable
@@ -20,7 +21,6 @@ from tomlkit.container import Container
from tomlkit.items import AoT, Array, Bool, InlineTable, Item, String, Table
from urllib3 import util as urllib3_util
from urllib3.util import parse_url as urllib3_parse
from vistir.compat import lru_cache
from vistir.misc import dedup
from vistir.path import is_valid_url
@@ -28,33 +28,31 @@ from ..environment import MYPY_RUNNING
from ..utils import SCHEME_LIST, VCS_LIST, is_star
if MYPY_RUNNING:
from typing import Iterable # noqa
from typing import (
Any,
AnyStr,
Dict,
List,
Match,
Optional,
Sequence,
Set,
Text,
Tuple,
TypeVar,
Union,
)
from attr import _ValidatorType # noqa
from packaging.markers import Marker as PkgResourcesMarker
from packaging.markers import Op as PkgResourcesOp
from packaging.markers import Value as PkgResourcesValue
from packaging.markers import Variable as PkgResourcesVariable
from packaging.requirements import Requirement as PackagingRequirement
from pip_shims.shims import Link
from pkg_resources import Requirement as PkgResourcesRequirement
from pkg_resources.extern.packaging.markers import (
Op as PkgResourcesOp,
Variable as PkgResourcesVariable,
Value as PkgResourcesValue,
Marker as PkgResourcesMarker,
)
from typing import (
Union,
Optional,
List,
Set,
Any,
TypeVar,
Tuple,
Sequence,
Dict,
Text,
AnyStr,
Match,
Iterable, # noqa
)
from urllib3.util.url import Url
from vistir.compat import Path
_T = TypeVar("_T")
TMarker = Union[Marker, PkgResourcesMarker]
@@ -115,9 +113,9 @@ def optional_instance_of(cls):
def create_link(link):
# type: (AnyStr) -> Link
if not isinstance(link, six.string_types):
if not isinstance(link, str):
raise TypeError("must provide a string to instantiate a new link")
from pip_shims.shims import Link
from pip_shims.shims import Link # noqa: F811
return Link(link)
@@ -178,14 +176,13 @@ def tomlkit_dict_to_python(toml_dict):
def get_url_name(url):
# type: (AnyStr) -> AnyStr
"""
Given a url, derive an appropriate name to use in a pipfile.
"""Given a url, derive an appropriate name to use in a pipfile.
:param str url: A url to derive a string from
:returns: The name of the corresponding pipfile entry
:rtype: Text
"""
if not isinstance(url, six.string_types):
if not isinstance(url, str):
raise TypeError("Expected a string, got {0!r}".format(url))
return urllib3_util.parse_url(url).host
@@ -193,7 +190,7 @@ def get_url_name(url):
def init_requirement(name):
# type: (AnyStr) -> TRequirement
if not isinstance(name, six.string_types):
if not isinstance(name, str):
raise TypeError("must supply a name to generate a requirement")
from pkg_resources import Requirement
@@ -207,13 +204,13 @@ def init_requirement(name):
def extras_to_string(extras):
# type: (Iterable[S]) -> S
"""Turn a list of extras into a string
"""Turn a list of extras into a string.
:param List[str]] extras: a list of extras to format
:return: A string of extras
:rtype: str
"""
if isinstance(extras, six.string_types):
if isinstance(extras, str):
if extras.startswith("["):
return extras
else:
@@ -225,7 +222,7 @@ def extras_to_string(extras):
def parse_extras(extras_str):
# type: (AnyStr) -> List[AnyStr]
"""Turn a string of extras into a parsed extras list
"""Turn a string of extras into a parsed extras list.
:param str extras_str: An extras string
:return: A sorted list of extras
@@ -240,7 +237,7 @@ def parse_extras(extras_str):
def specs_to_string(specs):
# type: (List[Union[STRING_TYPE, Specifier]]) -> AnyStr
"""Turn a list of specifier tuples into a string
"""Turn a list of specifier tuples into a string.
:param List[Union[Specifier, str]] specs: a list of specifiers to format
:return: A string of specifiers
@@ -248,7 +245,7 @@ def specs_to_string(specs):
"""
if specs:
if isinstance(specs, six.string_types):
if isinstance(specs, str):
return specs
try:
extras = ",".join(["".join(spec) for spec in specs])
@@ -288,8 +285,7 @@ def build_vcs_uri(
def _get_parsed_url(url):
# type: (S) -> Url
"""
This is a stand-in function for `urllib3.util.parse_url`
"""This is a stand-in function for `urllib3.util.parse_url`
The orignal function doesn't handle special characters very well, this simply splits
out the authentication section, creates the parsed url, then puts the authentication
@@ -311,7 +307,7 @@ def _get_parsed_url(url):
def convert_direct_url_to_url(direct_url):
# type: (AnyStr) -> AnyStr
"""Converts direct URLs to standard, link-style URLs
"""Converts direct URLs to standard, link-style URLs.
Given a direct url as defined by *PEP 508*, convert to a :class:`~pip_shims.shims.Link`
compatible URL by moving the name and extras into an **egg_fragment**.
@@ -352,8 +348,7 @@ def convert_direct_url_to_url(direct_url):
def convert_url_to_direct_url(url, name=None):
# type: (AnyStr, Optional[AnyStr]) -> AnyStr
"""
Converts normal link-style URLs to direct urls.
"""Converts normal link-style URLs to direct urls.
Given a :class:`~pip_shims.shims.Link` compatible URL, convert to a direct url as
defined by *PEP 508* by extracting the name and extras from the **egg_fragment**.
@@ -366,7 +361,7 @@ def convert_url_to_direct_url(url, name=None):
:raises ValueError: Raised when the URL can't be parsed or a name can't be found.
:raises TypeError: When a non-string input is provided.
"""
if not isinstance(url, six.string_types):
if not isinstance(url, str):
raise TypeError(
"Expected a string to convert to a direct url, got {0!r}".format(url)
)
@@ -410,15 +405,14 @@ def get_version(pipfile_entry):
return ""
return pipfile_entry.get("version", "").strip().lstrip("(").rstrip(")")
if isinstance(pipfile_entry, six.string_types):
if isinstance(pipfile_entry, str):
return pipfile_entry.strip().lstrip("(").rstrip(")")
return ""
def strip_extras_markers_from_requirement(req):
# type: (TRequirement) -> TRequirement
"""
Strips extras markers from requirement instances.
"""Strips extras markers from requirement instances.
Given a :class:`~packaging.requirements.Requirement` instance with markers defining
*extra == 'name'*, strip out the extras from the markers and return the cleaned
@@ -485,9 +479,8 @@ def get_default_pyproject_backend():
def get_pyproject(path):
# type: (Union[STRING_TYPE, Path]) -> Optional[Tuple[List[STRING_TYPE], STRING_TYPE]]
"""
Given a base path, look for the corresponding ``pyproject.toml`` file and return its
build_requires and build_backend.
"""Given a base path, look for the corresponding ``pyproject.toml`` file
and return its build_requires and build_backend.
:param AnyStr path: The root path of the project, should be a directory (will be truncated)
:return: A 2 tuple of build requirements and the build backend
@@ -495,7 +488,6 @@ def get_pyproject(path):
"""
if not path:
return
from vistir.compat import Path
if not isinstance(path, Path):
path = Path(path)
@@ -510,7 +502,7 @@ def get_pyproject(path):
backend = get_default_pyproject_backend()
else:
pyproject_data = {}
with io.open(pp_toml.as_posix(), encoding="utf-8") as fh:
with open(pp_toml.as_posix(), encoding="utf-8") as fh:
pyproject_data = tomlkit.loads(fh.read())
build_system = pyproject_data.get("build-system", None)
if build_system is None:
@@ -530,7 +522,7 @@ def get_pyproject(path):
def split_markers_from_line(line):
# type: (AnyStr) -> Tuple[AnyStr, Optional[AnyStr]]
"""Split markers from a dependency"""
"""Split markers from a dependency."""
quote_chars = ["'", '"']
line_quote = next(
iter(quote for quote in quote_chars if line.startswith(quote)), None
@@ -562,15 +554,14 @@ def split_vcs_method_from_uri(uri):
def split_ref_from_uri(uri):
# type: (AnyStr) -> Tuple[AnyStr, Optional[AnyStr]]
"""
Given a path or URI, check for a ref and split it from the path if it is present,
returning a tuple of the original input and the ref or None.
"""Given a path or URI, check for a ref and split it from the path if it is
present, returning a tuple of the original input and the ref or None.
:param AnyStr uri: The path or URI to split
:returns: A 2-tuple of the path or URI and the ref
:rtype: Tuple[AnyStr, Optional[AnyStr]]
"""
if not isinstance(uri, six.string_types):
if not isinstance(uri, str):
raise TypeError("Expected a string, received {0!r}".format(uri))
parsed = _get_parsed_url(uri)
path = parsed.path if parsed.path else ""
@@ -738,8 +729,7 @@ def get_pinned_version(ireq):
def is_pinned_requirement(ireq):
"""
Returns whether an InstallRequirement is a "pinned" requirement.
"""Returns whether an InstallRequirement is a "pinned" requirement.
An InstallRequirement is considered pinned if:
@@ -763,9 +753,8 @@ def is_pinned_requirement(ireq):
def as_tuple(ireq):
"""
Pulls out the (name: str, version:str, extras:(str)) tuple from the pinned InstallRequirement.
"""
"""Pulls out the (name: str, version:str, extras:(str)) tuple from the
pinned InstallRequirement."""
if not is_pinned_requirement(ireq):
raise TypeError("Expected a pinned InstallRequirement, got {}".format(ireq))
@@ -777,24 +766,19 @@ def as_tuple(ireq):
def full_groupby(iterable, key=None):
"""
Like groupby(), but sorts the input on the group key first.
"""
"""Like groupby(), but sorts the input on the group key first."""
return groupby(sorted(iterable, key=key), key=key)
def flat_map(fn, collection):
"""
Map a function over a collection and flatten the result by one-level
"""
"""Map a function over a collection and flatten the result by one-level."""
return chain.from_iterable(map(fn, collection))
def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False):
"""
Builds a dict-based lookup table (index) elegantly.
"""Builds a dict-based lookup table (index) elegantly.
Supports building normal and unique lookup tables. For example:
@@ -855,7 +839,7 @@ def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False):
def name_from_req(req):
"""Get the name of the requirement"""
"""Get the name of the requirement."""
if hasattr(req, "project_name"):
# from pkg_resources, such as installed dists for pip-sync
return req.project_name
@@ -867,8 +851,7 @@ def name_from_req(req):
def make_install_requirement(
name, version=None, extras=None, markers=None, constraint=False
):
"""
Generates an :class:`~pip._internal.req.req_install.InstallRequirement`.
"""Generates an :class:`~pip._internal.req.req_install.InstallRequirement`.
Create an InstallRequirement from the supplied metadata.
@@ -903,7 +886,8 @@ def make_install_requirement(
def version_from_ireq(ireq):
"""version_from_ireq Extract the version from a supplied :class:`~pip._internal.req.req_install.InstallRequirement`
"""version_from_ireq Extract the version from a supplied
:class:`~pip._internal.req.req_install.InstallRequirement`
:param ireq: An InstallRequirement
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
@@ -924,7 +908,8 @@ def _get_requires_python(candidate):
def clean_requires_python(candidates):
"""Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes."""
"""Get a cleaned list of all the candidates with valid specifiers in the
`requires_python` attributes."""
all_candidates = []
sys_version = ".".join(map(str, sys.version_info[:3]))
from packaging.version import parse as parse_version
@@ -985,25 +970,24 @@ def normalize_name(pkg):
:rtype: AnyStr
"""
assert isinstance(pkg, six.string_types)
assert isinstance(pkg, str)
return pkg.replace("_", "-").lower()
def get_name_variants(pkg):
# type: (STRING_TYPE) -> Set[STRING_TYPE]
"""
Given a packager name, get the variants of its name for both the canonicalized
and "safe" forms.
"""Given a packager name, get the variants of its name for both the
canonicalized and "safe" forms.
:param AnyStr pkg: The package to lookup
:returns: A list of names.
:rtype: Set
"""
if not isinstance(pkg, six.string_types):
if not isinstance(pkg, str):
raise TypeError("must provide a string to derive package names")
from pkg_resources import safe_name
from packaging.utils import canonicalize_name
from pkg_resources import safe_name
pkg = pkg.lower()
names = {safe_name(pkg), canonicalize_name(pkg), pkg.replace("-", "_")}
@@ -1012,29 +996,26 @@ def get_name_variants(pkg):
def read_source(path, encoding="utf-8"):
# type: (S, S) -> S
"""
Read a source file and get the contents with proper encoding for Python 2/3.
"""Read a source file and get the contents with proper encoding for Python
2/3.
:param AnyStr path: the file path
:param AnyStr encoding: the encoding that defaults to UTF-8
:returns: The contents of the source file
:rtype: AnyStr
"""
if six.PY3:
with open(path, "r", encoding=encoding) as fp:
return fp.read()
else:
with open(path, "r") as fp:
return fp.read()
with open(path, "r", encoding=encoding) as fp:
return fp.read()
def expand_env_variables(line):
# type: (AnyStr) -> AnyStr
"""Expand the env vars in a line following pip's standard.
https://pip.pypa.io/en/stable/reference/pip_install/#id10
https://pip.pypa.io/en/stable/reference/pip_install/#id10.
Matches environment variable-style values in '${MY_VARIABLE_1}' with the
variable name consisting of only uppercase letters, digits or the '_'
Matches environment variable-style values in '${MY_VARIABLE_1}' with
the variable name consisting of only uppercase letters, digits or
the '_'
"""
def replace_with_env(match):
+4 -7
View File
@@ -7,7 +7,6 @@ import sys
import attr
import pip_shims
import six
from ..environment import MYPY_RUNNING
from .url import URI
@@ -124,13 +123,11 @@ class VCSRepository(object):
# set the default to not write stdout, the first option sets this value
new_defaults = [False] + list(run_command_defaults)[1:]
new_defaults = tuple(new_defaults)
if six.PY3:
try:
pip_vcs.VersionControl.run_command.__defaults__ = new_defaults
except AttributeError:
pip_vcs.VersionControl.run_command.__func__.__defaults__ = new_defaults
else:
try:
pip_vcs.VersionControl.run_command.__defaults__ = new_defaults
except AttributeError:
pip_vcs.VersionControl.run_command.__func__.__defaults__ = new_defaults
sys.modules[target_module] = pip_vcs
cls.DEFAULT_RUN_ARGS = new_defaults
return new_defaults
+37 -50
View File
@@ -4,37 +4,20 @@ from __future__ import absolute_import, print_function
import logging
import os
import sys
from collections.abc import ItemsView, Mapping, Sequence, Set
from pathlib import Path
from urllib.parse import urlparse, urlsplit, urlunparse
import pip_shims.shims
import six
import six.moves
import tomlkit
import vistir
from six.moves.urllib.parse import urlparse, urlsplit, urlunparse
from vistir.compat import Path, fs_decode
from vistir.compat import fs_decode
from vistir.path import ensure_mkdir_p, is_valid_url
from .environment import MYPY_RUNNING
# fmt: off
six.add_move( # type: ignore
six.MovedAttribute("Mapping", "collections", "collections.abc") # type: ignore
) # noqa # isort:skip
six.add_move( # type: ignore
six.MovedAttribute("Sequence", "collections", "collections.abc") # type: ignore
) # noqa # isort:skip
six.add_move( # type: ignore
six.MovedAttribute("Set", "collections", "collections.abc") # type: ignore
) # noqa # isort:skip
six.add_move( # type: ignore
six.MovedAttribute("ItemsView", "collections", "collections.abc") # type: ignore
) # noqa
from six.moves import ItemsView, Mapping, Sequence, Set # type: ignore # noqa # isort:skip
# fmt: on
if MYPY_RUNNING:
from typing import Dict, Any, Optional, Union, Tuple, List, Iterable, Text, TypeVar
from typing import Any, Dict, Iterable, List, Optional, Text, Tuple, TypeVar, Union
STRING_TYPE = Union[bytes, str, Text]
S = TypeVar("S", bytes, str, Text)
@@ -104,8 +87,8 @@ def is_installable_dir(path):
def strip_ssh_from_git_uri(uri):
# type: (S) -> S
"""Return git+ssh:// formatted URI to git+git@ format"""
if isinstance(uri, six.string_types):
"""Return git+ssh:// formatted URI to git+git@ format."""
if isinstance(uri, str):
if "git+ssh://" in uri:
parsed = urlparse(uri)
# split the path on the first separating / so we can put the first segment
@@ -121,8 +104,8 @@ def strip_ssh_from_git_uri(uri):
def add_ssh_scheme_to_git_uri(uri):
# type: (S) -> S
"""Cleans VCS uris from pipenv.patched.notpip format"""
if isinstance(uri, six.string_types):
"""Cleans VCS uris from pipenv.patched.notpip format."""
if isinstance(uri, str):
# Add scheme for parsing purposes, this is also what pip does
if uri.startswith("git+") and "://" not in uri:
uri = uri.replace("git+", "git+ssh://", 1)
@@ -140,7 +123,7 @@ def is_vcs(pipfile_entry):
if isinstance(pipfile_entry, Mapping):
return any(key for key in pipfile_entry.keys() if key in VCS_LIST)
elif isinstance(pipfile_entry, six.string_types):
elif isinstance(pipfile_entry, str):
if not is_valid_url(pipfile_entry) and pipfile_entry.startswith("git+"):
pipfile_entry = add_ssh_scheme_to_git_uri(pipfile_entry)
@@ -153,21 +136,21 @@ def is_editable(pipfile_entry):
# type: (PipfileType) -> bool
if isinstance(pipfile_entry, Mapping):
return pipfile_entry.get("editable", False) is True
if isinstance(pipfile_entry, six.string_types):
if isinstance(pipfile_entry, str):
return pipfile_entry.startswith("-e ")
return False
def is_star(val):
# type: (PipfileType) -> bool
return (isinstance(val, six.string_types) and val == "*") or (
return (isinstance(val, str) and val == "*") or (
isinstance(val, Mapping) and val.get("version", "") == "*"
)
def convert_entry_to_path(path):
# type: (Dict[S, Union[S, bool, Tuple[S], List[S]]]) -> S
"""Convert a pipfile entry to a string"""
"""Convert a pipfile entry to a string."""
if not isinstance(path, Mapping):
raise TypeError("expecting a mapping, received {0!r}".format(path))
@@ -187,7 +170,7 @@ def convert_entry_to_path(path):
def is_installable_file(path):
# type: (PipfileType) -> bool
"""Determine if a path can potentially be installed"""
"""Determine if a path can potentially be installed."""
from packaging import specifiers
if isinstance(path, Mapping):
@@ -211,7 +194,7 @@ def is_installable_file(path):
or (len(parsed.scheme) == 1 and os.name == "nt")
)
if parsed.scheme and parsed.scheme == "file":
path = vistir.compat.fs_decode(vistir.path.url_to_path(path))
path = fs_decode(vistir.path.url_to_path(path))
normalized_path = vistir.path.normalize_path(path)
if is_local and not os.path.exists(normalized_path):
return False
@@ -230,9 +213,10 @@ def is_installable_file(path):
def get_dist_metadata(dist):
import pkg_resources
from email.parser import FeedParser
import pkg_resources
if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata(
"METADATA"
):
@@ -343,10 +327,8 @@ _REMAP_EXIT = object()
class PathAccessError(KeyError, IndexError, TypeError):
"""An amalgamation of KeyError, IndexError, and TypeError,
representing what can occur when looking up a path in a nested
object.
"""
"""An amalgamation of KeyError, IndexError, and TypeError, representing
what can occur when looking up a path in a nested object."""
def __init__(self, exc, seg, path):
self.exc = exc
@@ -368,6 +350,7 @@ class PathAccessError(KeyError, IndexError, TypeError):
def get_path(root, path, default=_UNSET):
"""Retrieve a value from a nested object via a tuple representing the
lookup path.
>>> root = {'a': {'b': {'c': [[1], [2], [3]]}}}
>>> get_path(root, ('a', 'b', 'c', 2, 0))
3
@@ -391,7 +374,7 @@ def get_path(root, path, default=_UNSET):
default: The value to be returned should any
``PathAccessError`` exceptions be raised.
"""
if isinstance(path, six.string_types):
if isinstance(path, str):
path = path.split(".")
cur = root
try:
@@ -426,8 +409,12 @@ _orig_default_visit = default_visit
# Modified from https://github.com/mahmoud/boltons/blob/master/boltons/iterutils.py
def dict_path_enter(path, key, value):
if isinstance(value, six.string_types):
if isinstance(value, str):
return value, False
elif isinstance(value, (tomlkit.items.Table, tomlkit.items.InlineTable)):
return value.__class__(
tomlkit.container.Container(), value.trivia, False
), ItemsView(value)
elif isinstance(value, (Mapping, dict)):
return value.__class__(), ItemsView(value)
elif isinstance(value, tomlkit.items.Array):
@@ -479,16 +466,16 @@ def dict_path_exit(path, key, old_parent, new_parent, new_items):
def remap(
root, visit=default_visit, enter=dict_path_enter, exit=dict_path_exit, **kwargs
):
"""The remap ("recursive map") function is used to traverse and
transform nested structures. Lists, tuples, sets, and dictionaries
are just a few of the data structures nested into heterogenous
tree-like structures that are so common in programming.
Unfortunately, Python's built-in ways to manipulate collections
are almost all flat. List comprehensions may be fast and succinct,
but they do not recurse, making it tedious to apply quick changes
or complex transforms to real-world data.
remap goes where list comprehensions cannot.
Here's an example of removing all Nones from some data:
"""The remap ("recursive map") function is used to traverse and transform
nested structures. Lists, tuples, sets, and dictionaries are just a few of
the data structures nested into heterogenous tree-like structures that are
so common in programming. Unfortunately, Python's built-in ways to
manipulate collections are almost all flat. List comprehensions may be fast
and succinct, but they do not recurse, making it tedious to apply quick
changes or complex transforms to real-world data. remap goes where list
comprehensions cannot. Here's an example of removing all Nones from some
data:
>>> from pprint import pprint
>>> reviews = {'Star Trek': {'TNG': 10, 'DS9': 8.5, 'ENT': None},
... 'Babylon 5': 6, 'Dr. Who': None}
@@ -644,7 +631,7 @@ def merge_items(target_list, sourced=False):
try:
cur_val = get_path(ret, path + (key,))
except KeyError as ke:
except KeyError:
pass
else:
new_parent = cur_val
+2 -2
View File
@@ -26,7 +26,7 @@ packaging==21.0
parse==1.19.0
pep517==0.11.0
pexpect==4.8.0
pip-shims==0.5.3
pip-shims==0.6.0
pipdeptree==2.0.0
pipreqs==0.4.10
plette[validation]==0.2.3
@@ -36,7 +36,7 @@ python-dateutil==2.8.2
python-dotenv==0.19.0
pythonfinder==1.2.8
requests==2.26.0
requirementslib==1.5.16
requirementslib==1.6.1
shellingham==1.4.0
six==1.16.0
termcolor==1.1.0
@@ -1,42 +0,0 @@
diff --git a/pipenv/vendor/pip_shims/__init__.py b/pipenv/vendor/pip_shims/__init__.py
index 2af4166e..598b9ad8 100644
--- a/pipenv/vendor/pip_shims/__init__.py
+++ b/pipenv/vendor/pip_shims/__init__.py
@@ -11,10 +11,13 @@ __version__ = "0.5.3"
if "pip_shims" in sys.modules:
# mainly to keep a reference to the old module on hand so it doesn't get
# weakref'd away
- old_module = sys.modules["pip_shims"]
+ if __name__ != "pip_shims":
+ del sys.modules["pip_shims"]
+if __name__ in sys.modules:
+ old_module = sys.modules[__name__]
-module = sys.modules["pip_shims"] = shims._new()
+module = sys.modules[__name__] = sys.modules["pip_shims"] = shims._new()
module.shims = shims
module.__dict__.update(
{
diff --git a/pipenv/vendor/pip_shims/compat.py b/pipenv/vendor/pip_shims/compat.py
index ed99d970..63061a6a 100644
--- a/pipenv/vendor/pip_shims/compat.py
+++ b/pipenv/vendor/pip_shims/compat.py
@@ -25,14 +25,14 @@ from .utils import (
)
if sys.version_info[:2] < (3, 5):
- from backports.tempfile import TemporaryDirectory
+ from pipenv.vendor.vistir.compat import TemporaryDirectory
else:
from tempfile import TemporaryDirectory
if six.PY3:
from contextlib import ExitStack
else:
- from contextlib2 import ExitStack
+ from pipenv.vendor.contextlib2 import ExitStack
if MYPY_RUNNING: