mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Update requirementslib to 1.1.2
Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
+21
-3
@@ -1,3 +1,21 @@
|
||||
This software is made available under the terms of *either* of the licenses
|
||||
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
|
||||
under the terms of *both* these licenses.
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2018 Dan Ryan.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-177
@@ -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
@@ -1,23 +0,0 @@
|
||||
Copyright (c) Kenneth Reitz and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
__version__ = "1.0.11"
|
||||
__version__ = '1.1.2'
|
||||
|
||||
|
||||
from .exceptions import RequirementError
|
||||
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import importlib
|
||||
import six
|
||||
|
||||
# Use these imports as compatibility imports
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse, unquote
|
||||
except ImportError:
|
||||
from urlparse import urlparse, unquote
|
||||
|
||||
if six.PY2:
|
||||
|
||||
class FileNotFoundError(IOError):
|
||||
pass
|
||||
|
||||
|
||||
else:
|
||||
|
||||
class FileNotFoundError(FileNotFoundError):
|
||||
pass
|
||||
|
||||
|
||||
def do_import(module_path, subimport=None, old_path=None):
|
||||
internal = "pip._internal.{0}".format(module_path)
|
||||
old_path = old_path or module_path
|
||||
pip9 = "pip.{0}".format(old_path)
|
||||
try:
|
||||
_tmp = importlib.import_module(internal)
|
||||
except ImportError:
|
||||
_tmp = importlib.import_module(pip9)
|
||||
if subimport:
|
||||
return getattr(_tmp, subimport, _tmp)
|
||||
return _tmp
|
||||
|
||||
|
||||
InstallRequirement = do_import("req.req_install", "InstallRequirement")
|
||||
user_cache_dir = do_import("utils.appdirs", "user_cache_dir")
|
||||
FAVORITE_HASH = do_import("utils.hashes", "FAVORITE_HASH")
|
||||
is_file_url = do_import("download", "is_file_url")
|
||||
url_to_path = do_import("download", "url_to_path")
|
||||
path_to_url = do_import("download", "path_to_url")
|
||||
is_archive_file = do_import("download", "is_archive_file")
|
||||
_strip_extras = do_import("req.req_install", "_strip_extras")
|
||||
Link = do_import("index", "Link")
|
||||
Wheel = do_import("wheel", "Wheel")
|
||||
is_installable_file = do_import("utils.misc", "is_installable_file", old_path="utils")
|
||||
is_installable_dir = do_import("utils.misc", "is_installable_dir", old_path="utils")
|
||||
make_abstract_dist = do_import(
|
||||
"operations.prepare", "make_abstract_dist", old_path="req.req_set"
|
||||
)
|
||||
VcsSupport = do_import("vcs", "VcsSupport")
|
||||
@@ -1 +0,0 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
@@ -1,3 +0,0 @@
|
||||
This software is made available under the terms of *either* of the licenses
|
||||
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
|
||||
under the terms of *both* these licenses.
|
||||
@@ -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
|
||||
@@ -1,23 +0,0 @@
|
||||
Copyright (c) Kenneth Reitz and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,21 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__all__ = [
|
||||
"__title__", "__summary__", "__uri__", "__version__", "__author__",
|
||||
"__email__", "__license__", "__copyright__",
|
||||
]
|
||||
|
||||
__title__ = "pipfile"
|
||||
__summary__ = ""
|
||||
__uri__ = "https://github.com/pypa/pipfile"
|
||||
|
||||
__version__ = "0.0.2"
|
||||
|
||||
__author__ = "Kenneth Reitz and individual contributors"
|
||||
__email__ = "me@kennethreitz.org"
|
||||
|
||||
__license__ = "BSD or Apache License, Version 2.0"
|
||||
__copyright__ = "Copyright 2017 %s" % __author__
|
||||
@@ -1,11 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from .__about__ import (
|
||||
__author__, __copyright__, __email__, __license__, __summary__, __title__,
|
||||
__uri__, __version__
|
||||
)
|
||||
|
||||
from .api import load, Pipfile
|
||||
@@ -1,230 +0,0 @@
|
||||
import toml
|
||||
|
||||
import codecs
|
||||
import json
|
||||
import hashlib
|
||||
import platform
|
||||
import six
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
DEFAULT_SOURCE = {
|
||||
u'url': u'https://pypi.org/simple',
|
||||
u'verify_ssl': True,
|
||||
u'name': u'pypi',
|
||||
}
|
||||
|
||||
|
||||
def format_full_version(info):
|
||||
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
|
||||
kind = info.releaselevel
|
||||
if kind != 'final':
|
||||
version += kind[0] + str(info.serial)
|
||||
return version
|
||||
|
||||
|
||||
def walk_up(bottom):
|
||||
"""mimic os.walk, but walk 'up' instead of down the directory tree.
|
||||
From: https://gist.github.com/zdavkeos/1098474
|
||||
"""
|
||||
|
||||
bottom = os.path.realpath(bottom)
|
||||
|
||||
# get files in current dir
|
||||
try:
|
||||
names = os.listdir(bottom)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
dirs, nondirs = [], []
|
||||
for name in names:
|
||||
if os.path.isdir(os.path.join(bottom, name)):
|
||||
dirs.append(name)
|
||||
else:
|
||||
nondirs.append(name)
|
||||
|
||||
yield bottom, dirs, nondirs
|
||||
|
||||
new_path = os.path.realpath(os.path.join(bottom, '..'))
|
||||
|
||||
# see if we are at the top
|
||||
if new_path == bottom:
|
||||
return
|
||||
|
||||
for x in walk_up(new_path):
|
||||
yield x
|
||||
|
||||
|
||||
class PipfileParser(object):
|
||||
def __init__(self, filename='Pipfile'):
|
||||
self.filename = filename
|
||||
self.sources = []
|
||||
self.groups = {
|
||||
'default': [],
|
||||
'develop': []
|
||||
}
|
||||
self.group_stack = ['default']
|
||||
self.requirements = []
|
||||
|
||||
def __repr__(self):
|
||||
return '<PipfileParser path={0!r}'.format(self.filename)
|
||||
|
||||
def inject_environment_variables(self, d):
|
||||
"""
|
||||
Recursively injects environment variables into TOML values
|
||||
"""
|
||||
|
||||
if not d:
|
||||
return d
|
||||
if isinstance(d, six.string_types):
|
||||
return os.path.expandvars(d)
|
||||
for k, v in d.items():
|
||||
if isinstance(v, six.string_types):
|
||||
d[k] = os.path.expandvars(v)
|
||||
elif isinstance(v, dict):
|
||||
d[k] = self.inject_environment_variables(v)
|
||||
elif isinstance(v, list):
|
||||
d[k] = [self.inject_environment_variables(e) for e in v]
|
||||
|
||||
return d
|
||||
|
||||
def parse(self, inject_env=True):
|
||||
# Open the Pipfile.
|
||||
with open(self.filename) as f:
|
||||
content = f.read()
|
||||
|
||||
# Load the default configuration.
|
||||
default_config = {
|
||||
u'source': [DEFAULT_SOURCE],
|
||||
u'packages': {},
|
||||
u'requires': {},
|
||||
u'dev-packages': {}
|
||||
}
|
||||
|
||||
config = {}
|
||||
config.update(default_config)
|
||||
|
||||
# Deserialize the TOML, and parse for Environment Variables
|
||||
parsed = toml.loads(content)
|
||||
|
||||
if inject_env:
|
||||
injected_toml = self.inject_environment_variables(parsed)
|
||||
|
||||
# Load the Pipfile's configuration.
|
||||
config.update(injected_toml)
|
||||
else:
|
||||
config.update(parsed)
|
||||
|
||||
# Structure the data for output.
|
||||
data = {
|
||||
'_meta': {
|
||||
'sources': config['source'],
|
||||
'requires': config['requires']
|
||||
},
|
||||
}
|
||||
|
||||
# TODO: Validate given data here.
|
||||
self.groups['default'] = config['packages']
|
||||
self.groups['develop'] = config['dev-packages']
|
||||
|
||||
# Update the data structure with group information.
|
||||
data.update(self.groups)
|
||||
return data
|
||||
|
||||
|
||||
class Pipfile(object):
|
||||
def __init__(self, filename):
|
||||
super(Pipfile, self).__init__()
|
||||
self.filename = filename
|
||||
self.data = None
|
||||
|
||||
@staticmethod
|
||||
def find(max_depth=3):
|
||||
"""Returns the path of a Pipfile in parent directories."""
|
||||
i = 0
|
||||
for c, d, f in walk_up(os.getcwd()):
|
||||
i += 1
|
||||
|
||||
if i < max_depth:
|
||||
if 'Pipfile':
|
||||
p = os.path.join(c, 'Pipfile')
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
raise RuntimeError('No Pipfile found!')
|
||||
|
||||
@classmethod
|
||||
def load(klass, filename, inject_env=True):
|
||||
"""Load a Pipfile from a given filename."""
|
||||
p = PipfileParser(filename=filename)
|
||||
pipfile = klass(filename=filename)
|
||||
pipfile.data = p.parse(inject_env=inject_env)
|
||||
return pipfile
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
"""Returns the SHA256 of the pipfile's data."""
|
||||
content = json.dumps(self.data, sort_keys=True, separators=(",", ":"))
|
||||
return hashlib.sha256(content.encode("utf8")).hexdigest()
|
||||
|
||||
@property
|
||||
def contents(self):
|
||||
"""Returns the contents of the pipfile."""
|
||||
with codecs.open(self.filename, 'r', 'utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
def lock(self):
|
||||
"""Returns a JSON representation of the Pipfile."""
|
||||
data = self.data
|
||||
data['_meta']['hash'] = {"sha256": self.hash}
|
||||
data['_meta']['pipfile-spec'] = 6
|
||||
return json.dumps(data, indent=4, separators=(',', ': '))
|
||||
|
||||
def assert_requirements(self):
|
||||
""""Asserts PEP 508 specifiers."""
|
||||
|
||||
# Support for 508's implementation_version.
|
||||
if hasattr(sys, 'implementation'):
|
||||
implementation_version = format_full_version(sys.implementation.version)
|
||||
else:
|
||||
implementation_version = "0"
|
||||
|
||||
# Default to cpython for 2.7.
|
||||
if hasattr(sys, 'implementation'):
|
||||
implementation_name = sys.implementation.name
|
||||
else:
|
||||
implementation_name = 'cpython'
|
||||
|
||||
lookup = {
|
||||
'os_name': os.name,
|
||||
'sys_platform': sys.platform,
|
||||
'platform_machine': platform.machine(),
|
||||
'platform_python_implementation': platform.python_implementation(),
|
||||
'platform_release': platform.release(),
|
||||
'platform_system': platform.system(),
|
||||
'platform_version': platform.version(),
|
||||
'python_version': platform.python_version()[:3],
|
||||
'python_full_version': platform.python_version(),
|
||||
'implementation_name': implementation_name,
|
||||
'implementation_version': implementation_version
|
||||
}
|
||||
|
||||
# Assert each specified requirement.
|
||||
for marker, specifier in self.data['_meta']['requires'].items():
|
||||
|
||||
if marker in lookup:
|
||||
try:
|
||||
assert lookup[marker] == specifier
|
||||
except AssertionError:
|
||||
raise AssertionError('Specifier {!r} does not match {!r}.'.format(marker, specifier))
|
||||
|
||||
|
||||
def load(pipfile_path=None, inject_env=True):
|
||||
"""Loads a pipfile from a given path.
|
||||
If none is provided, one will try to be found.
|
||||
"""
|
||||
|
||||
if pipfile_path is None:
|
||||
pipfile_path = Pipfile.find()
|
||||
|
||||
return Pipfile.load(filename=pipfile_path, inject_env=inject_env)
|
||||
+9
@@ -1,5 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import errno
|
||||
import six
|
||||
|
||||
|
||||
if six.PY2:
|
||||
class FileExistsError(OSError):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.errno = errno.EEXIST
|
||||
super(FileExistsError, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RequirementError(Exception):
|
||||
|
||||
+2
-1
@@ -2,9 +2,10 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
__all__ = ["Requirement", "Lockfile", "Pipfile", "RequirementError"]
|
||||
__all__ = ["Requirement", "Lockfile", "Pipfile", "DependencyResolver"]
|
||||
|
||||
|
||||
from .requirements import Requirement
|
||||
from .lockfile import Lockfile
|
||||
from .pipfile import Pipfile
|
||||
from .resolvers import DependencyResolver
|
||||
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import six
|
||||
import sys
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import requests
|
||||
import vistir
|
||||
|
||||
from appdirs import user_cache_dir
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
from pip_shims.shims import (
|
||||
FAVORITE_HASH, Link, SafeFileCache, VcsSupport, is_file_url, url_to_path
|
||||
)
|
||||
from .utils import as_tuple, key_from_req, lookup_table
|
||||
|
||||
|
||||
if six.PY2:
|
||||
from ..exceptions import FileExistsError
|
||||
|
||||
|
||||
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
|
||||
|
||||
|
||||
# Pip-tools cache implementation
|
||||
class CorruptCacheError(Exception):
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def __str__(self):
|
||||
lines = [
|
||||
'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:
|
||||
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']
|
||||
|
||||
|
||||
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.
|
||||
|
||||
~/.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():
|
||||
try:
|
||||
vistir.path.mkdir_p(os.path.abspath(cache_dir))
|
||||
except (FileExistsError, OSError):
|
||||
pass
|
||||
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
|
||||
("ipython", "2.1.0")
|
||||
|
||||
For a requirement with extras, the extras will be comma-separated and appended to the version, inside brackets,
|
||||
like so:
|
||||
|
||||
("ipython", "2.1.0[nbconvert,notebook]")
|
||||
"""
|
||||
name, version, extras = as_tuple(ireq)
|
||||
if not extras:
|
||||
extras_string = ""
|
||||
else:
|
||||
extras_string = "[{}]".format(",".join(extras))
|
||||
return name, "{}{}".format(version, extras_string)
|
||||
|
||||
def read_cache(self):
|
||||
"""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."""
|
||||
doc = {
|
||||
'__format__': 1,
|
||||
'dependencies': self._cache,
|
||||
}
|
||||
with open(self._cache_file, 'w') as f:
|
||||
json.dump(doc, f, sort_keys=True)
|
||||
|
||||
def clear(self):
|
||||
self._cache = {}
|
||||
self.write_cache()
|
||||
|
||||
def __contains__(self, ireq):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
return pkgversion_and_extras in self.cache.get(pkgname, {})
|
||||
|
||||
def __getitem__(self, ireq):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
return self.cache[pkgname][pkgversion_and_extras]
|
||||
|
||||
def __setitem__(self, ireq, values):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
self.cache.setdefault(pkgname, {})
|
||||
self.cache[pkgname][pkgversion_and_extras] = values
|
||||
self.write_cache()
|
||||
|
||||
def __delitem__(self, ireq):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
try:
|
||||
del self.cache[pkgname][pkgversion_and_extras]
|
||||
except KeyError:
|
||||
return
|
||||
self.write_cache()
|
||||
|
||||
def get(self, ireq, default=None):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
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.
|
||||
|
||||
Example input:
|
||||
|
||||
[('pep8', '1.5.7'),
|
||||
('flake8', '2.4.0'),
|
||||
('mccabe', '0.3'),
|
||||
('pyflakes', '0.8.1')]
|
||||
|
||||
Example output:
|
||||
|
||||
{'pep8': ['flake8'],
|
||||
'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])
|
||||
|
||||
|
||||
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."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
session = kwargs.pop('session', requests.session())
|
||||
self.session = session
|
||||
kwargs.setdefault('directory', os.path.join(CACHE_DIR, 'hash-cache'))
|
||||
super(HashCache, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_hash(self, location):
|
||||
# if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it
|
||||
hash_value = None
|
||||
vcs = VcsSupport()
|
||||
orig_scheme = location.scheme
|
||||
new_location = copy.deepcopy(location)
|
||||
if orig_scheme in vcs.all_schemes:
|
||||
new_location.url = new_location.url.split("+", 1)[-1]
|
||||
can_hash = new_location.hash
|
||||
if can_hash:
|
||||
# hash url WITH fragment
|
||||
hash_value = self.get(new_location.url)
|
||||
if not hash_value:
|
||||
hash_value = self._get_file_hash(new_location)
|
||||
hash_value = hash_value.encode('utf8')
|
||||
if can_hash:
|
||||
self.set(new_location.url, hash_value)
|
||||
return hash_value.decode('utf8')
|
||||
|
||||
def _get_file_hash(self, location):
|
||||
h = hashlib.new(FAVORITE_HASH)
|
||||
with open_local_or_remote_file(location, self.session) as fp:
|
||||
for chunk in iter(lambda: fp.read(8096), b""):
|
||||
h.update(chunk)
|
||||
return ":".join([FAVORITE_HASH, h.hexdigest()])
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_local_or_remote_file(link, session):
|
||||
"""
|
||||
Open local or remote file for reading.
|
||||
|
||||
:type link: pip._internal.index.Link
|
||||
:type session: requests.Session
|
||||
:raises ValueError: If link points to a local directory.
|
||||
:return: a context manager to the opened file-like object
|
||||
"""
|
||||
if isinstance(link, Link):
|
||||
url = link.url_without_fragment
|
||||
else:
|
||||
url = link
|
||||
|
||||
if is_file_url(link):
|
||||
# Local URL
|
||||
local_path = url_to_path(url)
|
||||
if os.path.isdir(local_path):
|
||||
raise ValueError("Cannot open directory for read: {}".format(url))
|
||||
else:
|
||||
with open(local_path, 'rb') as local_file:
|
||||
yield local_file
|
||||
else:
|
||||
# Remote URL
|
||||
headers = {"Accept-Encoding": "identity"}
|
||||
response = session.get(url, headers=headers, stream=True)
|
||||
try:
|
||||
yield response.raw
|
||||
finally:
|
||||
response.close()
|
||||
@@ -0,0 +1,650 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
|
||||
import contextlib
|
||||
import copy
|
||||
import functools
|
||||
import os
|
||||
|
||||
import attr
|
||||
import packaging.markers
|
||||
import packaging.version
|
||||
import requests
|
||||
|
||||
from first import first
|
||||
from packaging.utils import canonicalize_name
|
||||
|
||||
from pip_shims import (
|
||||
FormatControl, InstallRequirement, PackageFinder, RequirementPreparer,
|
||||
RequirementSet, RequirementTracker, Resolver, WheelCache, pip_version
|
||||
)
|
||||
from vistir.compat import JSONDecodeError, TemporaryDirectory, fs_str
|
||||
from vistir.contextmanagers import cd, temp_environ
|
||||
from vistir.misc import partialclass
|
||||
|
||||
from ..utils import get_pip_command, prepare_pip_source_args, _ensure_dir
|
||||
from .cache import CACHE_DIR, DependencyCache
|
||||
from .utils import (
|
||||
clean_requires_python, fix_requires_python_marker, format_requirement,
|
||||
full_groupby, is_pinned_requirement, key_from_ireq,
|
||||
make_install_requirement, name_from_req, version_from_ireq
|
||||
)
|
||||
|
||||
|
||||
PKGS_DOWNLOAD_DIR = fs_str(os.path.join(CACHE_DIR, "pkgs"))
|
||||
WHEEL_DOWNLOAD_DIR = fs_str(os.path.join(CACHE_DIR, "wheels"))
|
||||
|
||||
DEPENDENCY_CACHE = DependencyCache()
|
||||
WHEEL_CACHE = WheelCache(CACHE_DIR, FormatControl(set(), set()))
|
||||
|
||||
|
||||
def _get_filtered_versions(ireq, versions, prereleases):
|
||||
return set(ireq.specifier.filter(versions, prereleases=prereleases))
|
||||
|
||||
|
||||
def find_all_matches(finder, ireq, pre=False):
|
||||
"""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`
|
||||
:param ireq: An install requirement.
|
||||
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A list of matching candidates.
|
||||
:rtype: list[:class:`~pip._internal.index.InstallationCandidate`]
|
||||
"""
|
||||
candidates = clean_requires_python(finder.find_all_candidates(ireq.name))
|
||||
versions = {candidate.version for candidate in candidates}
|
||||
allowed_versions = _get_filtered_versions(ireq, versions, pre)
|
||||
if not pre and not allowed_versions:
|
||||
allowed_versions = _get_filtered_versions(ireq, versions, True)
|
||||
candidates = {c for c in candidates if c.version in allowed_versions}
|
||||
return candidates
|
||||
|
||||
|
||||
@attr.s
|
||||
class AbstractDependency(object):
|
||||
name = attr.ib()
|
||||
specifiers = attr.ib()
|
||||
markers = attr.ib()
|
||||
candidates = attr.ib()
|
||||
requirement = attr.ib()
|
||||
parent = attr.ib()
|
||||
finder = attr.ib()
|
||||
dep_dict = attr.ib(default=attr.Factory(dict))
|
||||
|
||||
@property
|
||||
def version_set(self):
|
||||
"""Return the set of versions for the candidates in this abstract dependency.
|
||||
|
||||
:return: A set of matching versions
|
||||
:rtype: set(str)
|
||||
"""
|
||||
|
||||
if len(self.candidates) == 1:
|
||||
return set()
|
||||
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.
|
||||
|
||||
:param other: An abstract dependency to compare with.
|
||||
:type other: :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
:return: A set of compatible version strings
|
||||
:rtype: set(str)
|
||||
"""
|
||||
|
||||
if len(self.candidates) == 1 and first(self.candidates).editable:
|
||||
return self
|
||||
elif len(other.candidates) == 1 and first(other.candidates).editable:
|
||||
return other
|
||||
return self.version_set & other.version_set
|
||||
|
||||
def compatible_abstract_dep(self, other):
|
||||
"""Merge this abstract dependency with another one.
|
||||
|
||||
Return the result of the merge as a new abstract dependency.
|
||||
|
||||
:param other: An abstract dependency to merge with
|
||||
:type other: :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
:return: A new, combined abstract dependency
|
||||
:rtype: :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
"""
|
||||
|
||||
from .requirements import Requirement
|
||||
|
||||
if len(self.candidates) == 1 and first(self.candidates).editable:
|
||||
return self
|
||||
elif len(other.candidates) == 1 and first(other.candidates).editable:
|
||||
return other
|
||||
new_specifiers = self.specifiers & other.specifiers
|
||||
markers = set(self.markers,) if self.markers else set()
|
||||
if other.markers:
|
||||
markers.add(other.markers)
|
||||
new_markers = packaging.markers.Marker(" or ".join(str(m) for m in sorted(markers)))
|
||||
new_ireq = copy.deepcopy(self.requirement.ireq)
|
||||
new_ireq.req.specifier = new_specifiers
|
||||
new_ireq.req.marker = new_markers
|
||||
new_requirement = Requirement.from_line(format_requirement(new_ireq))
|
||||
compatible_versions = self.compatible_versions(other)
|
||||
if isinstance(compatible_versions, AbstractDependency):
|
||||
return compatible_versions
|
||||
candidates = [
|
||||
c
|
||||
for c in self.candidates
|
||||
if packaging.version.parse(version_from_ireq(c)) in compatible_versions
|
||||
]
|
||||
dep_dict = {}
|
||||
candidate_strings = [format_requirement(c) for c in candidates]
|
||||
for c in candidate_strings:
|
||||
if c in self.dep_dict:
|
||||
dep_dict[c] = self.dep_dict.get(c)
|
||||
return AbstractDependency(
|
||||
name=self.name,
|
||||
specifiers=new_specifiers,
|
||||
markers=new_markers,
|
||||
candidates=candidates,
|
||||
requirement=new_requirement,
|
||||
parent=self.parent,
|
||||
dep_dict=dep_dict,
|
||||
finder=self.finder
|
||||
)
|
||||
|
||||
def get_deps(self, candidate):
|
||||
"""Get the dependencies of the supplied candidate.
|
||||
|
||||
:param candidate: An installrequirement
|
||||
:type candidate: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A list of abstract dependencies
|
||||
:rtype: list[:class:`~requirementslib.models.dependency.AbstractDependency`]
|
||||
"""
|
||||
|
||||
key = format_requirement(candidate)
|
||||
if key not in self.dep_dict:
|
||||
from .requirements import Requirement
|
||||
|
||||
req = Requirement.from_line(key)
|
||||
req.merge_markers(self.markers)
|
||||
self.dep_dict[key] = req.get_abstract_dependencies()
|
||||
return self.dep_dict[key]
|
||||
|
||||
@classmethod
|
||||
def from_requirement(cls, requirement, parent=None):
|
||||
"""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.
|
||||
|
||||
:param requirement: A requirement for resolution
|
||||
:type requirement: :class:`~requirementslib.models.requirements.Requirement` object.
|
||||
"""
|
||||
name = requirement.normalized_name
|
||||
specifiers = requirement.ireq.specifier if not requirement.editable else ""
|
||||
markers = requirement.ireq.markers
|
||||
extras = requirement.ireq.extras
|
||||
is_pinned = is_pinned_requirement(requirement.ireq)
|
||||
is_constraint = bool(parent)
|
||||
finder = get_finder(sources=None)
|
||||
candidates = []
|
||||
if not is_pinned and not requirement.editable:
|
||||
for r in requirement.find_all_matches(finder=finder):
|
||||
req = make_install_requirement(
|
||||
name, r.version, extras=extras, markers=markers, constraint=is_constraint,
|
||||
)
|
||||
req.req.link = r.location
|
||||
req.parent = parent
|
||||
candidates.append(req)
|
||||
candidates = sorted(
|
||||
set(candidates), key=lambda k: packaging.version.parse(version_from_ireq(k)),
|
||||
)
|
||||
else:
|
||||
candidates = [requirement.ireq]
|
||||
return cls(
|
||||
name=name,
|
||||
specifiers=specifiers,
|
||||
markers=markers,
|
||||
candidates=candidates,
|
||||
requirement=requirement,
|
||||
parent=parent,
|
||||
finder=finder,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, line, parent=None):
|
||||
from .requirements import Requirement
|
||||
|
||||
req = Requirement.from_line(line)
|
||||
abstract_dep = cls.from_requirement(req, parent=parent)
|
||||
return abstract_dep
|
||||
|
||||
|
||||
def get_abstract_dependencies(reqs, sources=None, parent=None):
|
||||
"""Get all abstract dependencies for a given list of requirements.
|
||||
|
||||
Given a set of requirements, convert each requirement to an Abstract Dependency.
|
||||
|
||||
:param reqs: A list of Requirements
|
||||
:type reqs: list[:class:`~requirementslib.models.requirements.Requirement`]
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:param parent: The parent of this list of dependencies, defaults to None
|
||||
:param parent: :class:`~requirementslib.models.requirements.Requirement`, optional
|
||||
:return: A list of Abstract Dependencies
|
||||
:rtype: list[:class:`~requirementslib.models.dependency.AbstractDependency`]
|
||||
"""
|
||||
|
||||
deps = []
|
||||
from .requirements import Requirement
|
||||
|
||||
for req in reqs:
|
||||
if isinstance(req, InstallRequirement):
|
||||
requirement = Requirement.from_line(
|
||||
"{0}{1}".format(req.name, req.specifier)
|
||||
)
|
||||
if req.link:
|
||||
requirement.req.link = req.link
|
||||
requirement.markers = req.markers
|
||||
requirement.req.markers = req.markers
|
||||
requirement.extras = req.extras
|
||||
requirement.req.extras = req.extras
|
||||
elif isinstance(req, Requirement):
|
||||
requirement = copy.deepcopy(req)
|
||||
else:
|
||||
requirement = Requirement.from_line(req)
|
||||
dep = AbstractDependency.from_requirement(requirement, parent=parent)
|
||||
deps.append(dep)
|
||||
return deps
|
||||
|
||||
|
||||
def get_dependencies(ireq, sources=None, parent=None):
|
||||
"""Get all dependencies for a given install requirement.
|
||||
|
||||
:param ireq: A single InstallRequirement
|
||||
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:type sources: list[dict], optional
|
||||
:param parent: The parent of this list of dependencies, defaults to None
|
||||
:type parent: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str)
|
||||
"""
|
||||
if not isinstance(ireq, InstallRequirement):
|
||||
name = getattr(
|
||||
ireq, "project_name",
|
||||
getattr(ireq, "project", ireq.name),
|
||||
)
|
||||
version = getattr(ireq, "version")
|
||||
ireq = InstallRequirement.from_line("{0}=={1}".format(name, version))
|
||||
pip_options = get_pip_options(sources=sources)
|
||||
getters = [
|
||||
get_dependencies_from_cache,
|
||||
get_dependencies_from_wheel_cache,
|
||||
get_dependencies_from_json,
|
||||
functools.partial(get_dependencies_from_index, pip_options=pip_options)
|
||||
]
|
||||
for getter in getters:
|
||||
deps = getter(ireq)
|
||||
if deps is not None:
|
||||
return deps
|
||||
raise RuntimeError('failed to get dependencies for {}'.format(ireq))
|
||||
|
||||
|
||||
def get_dependencies_from_wheel_cache(ireq):
|
||||
"""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`
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str) or None
|
||||
"""
|
||||
|
||||
if ireq.editable or not is_pinned_requirement(ireq):
|
||||
return
|
||||
matches = WHEEL_CACHE.get(ireq.link, name_from_req(ireq.req))
|
||||
if matches:
|
||||
matches = set(matches)
|
||||
if not DEPENDENCY_CACHE.get(ireq):
|
||||
DEPENDENCY_CACHE[ireq] = [format_requirement(m) for m in matches]
|
||||
return matches
|
||||
return
|
||||
|
||||
|
||||
def _marker_contains_extra(ireq):
|
||||
# TODO: Implement better parsing logic avoid false-positives.
|
||||
return "extra" in repr(ireq.markers)
|
||||
|
||||
|
||||
def get_dependencies_from_json(ireq):
|
||||
"""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`
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str) or None
|
||||
"""
|
||||
|
||||
if ireq.editable or not is_pinned_requirement(ireq):
|
||||
return
|
||||
|
||||
# It is technically possible to parse extras out of the JSON API's
|
||||
# requirement format, but it is such a chore let's just use the simple API.
|
||||
if ireq.extras:
|
||||
return
|
||||
|
||||
session = requests.session()
|
||||
version = str(ireq.req.specifier).lstrip("=")
|
||||
|
||||
def gen(ireq):
|
||||
info = None
|
||||
info = session.get(
|
||||
"https://pypi.org/pypi/{0}/{1}/json".format(ireq.req.name, version)
|
||||
).json()["info"]
|
||||
requires_dist = info.get("requires_dist", info.get("requires"))
|
||||
if not requires_dist: # The API can return None for this.
|
||||
return
|
||||
for requires in requires_dist:
|
||||
i = InstallRequirement.from_line(requires)
|
||||
# See above, we don't handle requirements with extras.
|
||||
if not _marker_contains_extra(i):
|
||||
yield format_requirement(i)
|
||||
|
||||
if ireq not in DEPENDENCY_CACHE:
|
||||
try:
|
||||
reqs = DEPENDENCY_CACHE[ireq] = list(gen(ireq))
|
||||
except JSONDecodeError:
|
||||
return
|
||||
req_iter = iter(reqs)
|
||||
else:
|
||||
req_iter = gen(ireq)
|
||||
return set(req_iter)
|
||||
|
||||
|
||||
def get_dependencies_from_cache(ireq):
|
||||
"""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`
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str) or None
|
||||
"""
|
||||
if ireq.editable or not is_pinned_requirement(ireq):
|
||||
return
|
||||
if ireq not in DEPENDENCY_CACHE:
|
||||
return
|
||||
cached = set(DEPENDENCY_CACHE[ireq])
|
||||
|
||||
# Preserving sanity: Run through the cache and make sure every entry if
|
||||
# valid. If this fails, something is wrong with the cache. Drop it.
|
||||
try:
|
||||
broken = False
|
||||
for line in cached:
|
||||
dep_ireq = InstallRequirement.from_line(line)
|
||||
name = canonicalize_name(dep_ireq.name)
|
||||
if _marker_contains_extra(dep_ireq):
|
||||
broken = True # The "extra =" marker breaks everything.
|
||||
elif name == canonicalize_name(ireq.name):
|
||||
broken = True # A package cannot depend on itself.
|
||||
if broken:
|
||||
break
|
||||
except Exception:
|
||||
broken = True
|
||||
|
||||
if broken:
|
||||
del DEPENDENCY_CACHE[ireq]
|
||||
return
|
||||
|
||||
return cached
|
||||
|
||||
|
||||
def is_python(section):
|
||||
return section.startswith('[') and ':' in 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.
|
||||
|
||||
:param dep: A single InstallRequirement
|
||||
:type dep: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:type sources: list[dict], optional
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str) or None
|
||||
"""
|
||||
|
||||
finder = get_finder(sources=sources, pip_options=pip_options)
|
||||
if not wheel_cache:
|
||||
wheel_cache = WHEEL_CACHE
|
||||
dep.is_direct = True
|
||||
reqset = RequirementSet()
|
||||
reqset.add_requirement(dep)
|
||||
requirements = None
|
||||
setup_requires = {}
|
||||
with temp_environ(), start_resolver(finder=finder, wheel_cache=wheel_cache) as resolver:
|
||||
os.environ['PIP_EXISTS_ACTION'] = 'i'
|
||||
dist = None
|
||||
if dep.editable and not dep.prepared and not dep.req:
|
||||
with cd(dep.setup_py_dir):
|
||||
from setuptools.dist import distutils
|
||||
try:
|
||||
dist = distutils.core.run_setup(dep.setup_py)
|
||||
except (ImportError, TypeError, AttributeError):
|
||||
dist = None
|
||||
else:
|
||||
setup_requires[dist.get_name()] = dist.setup_requires
|
||||
if not dist:
|
||||
try:
|
||||
dist = dep.get_dist()
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
setup_requires[dist.get_name()] = dist.setup_requires
|
||||
resolver.require_hashes = False
|
||||
try:
|
||||
results = resolver._resolve_one(reqset, dep)
|
||||
except Exception:
|
||||
# FIXME: Needs to bubble the exception somehow to the user.
|
||||
results = []
|
||||
finally:
|
||||
try:
|
||||
wheel_cache.cleanup()
|
||||
except AttributeError:
|
||||
pass
|
||||
resolver_requires_python = getattr(resolver, "requires_python", None)
|
||||
requires_python = getattr(reqset, "requires_python", resolver_requires_python)
|
||||
if requires_python:
|
||||
add_marker = fix_requires_python_marker(requires_python)
|
||||
reqset.remove(dep)
|
||||
if dep.req.marker:
|
||||
dep.req.marker._markers.extend(['and',].extend(add_marker._markers))
|
||||
else:
|
||||
dep.req.marker = add_marker
|
||||
reqset.add(dep)
|
||||
requirements = set()
|
||||
for r in results:
|
||||
if requires_python:
|
||||
if r.req.marker:
|
||||
r.req.marker._markers.extend(['and',].extend(add_marker._markers))
|
||||
else:
|
||||
r.req.marker = add_marker
|
||||
requirements.add(format_requirement(r))
|
||||
for section in setup_requires:
|
||||
python_version = section
|
||||
not_python = not is_python(section)
|
||||
|
||||
# This is for cleaning up :extras: formatted markers
|
||||
# by adding them to the results of the resolver
|
||||
# since any such extra would have been returned as a result anyway
|
||||
for value in setup_requires[section]:
|
||||
|
||||
# This is a marker.
|
||||
if is_python(section):
|
||||
python_version = value[1:-1]
|
||||
else:
|
||||
not_python = True
|
||||
|
||||
if ':' not in value and not_python:
|
||||
try:
|
||||
requirement_str = "{0}{1}".format(value, python_version).replace(":", ";")
|
||||
requirements.add(format_requirement(make_install_requirement(requirement_str).ireq))
|
||||
# Anything could go wrong here -- can't be too careful.
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not dep.editable and is_pinned_requirement(dep) and requirements is not None:
|
||||
DEPENDENCY_CACHE[dep] = list(requirements)
|
||||
return requirements
|
||||
|
||||
|
||||
def get_pip_options(args=[], 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
|
||||
:param sources: list[dict], optional
|
||||
:param pip_command: A pre-built pip command instance
|
||||
:type pip_command: :class:`~pip._internal.cli.base_command.Command`
|
||||
:return: An instance of pip_options using the supplied arguments plus sane defaults
|
||||
:rtype: :class:`~pip._internal.cli.cmdoptions`
|
||||
"""
|
||||
|
||||
if not pip_command:
|
||||
pip_command = get_pip_command()
|
||||
if not sources:
|
||||
sources = [
|
||||
{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}
|
||||
]
|
||||
_ensure_dir(CACHE_DIR)
|
||||
pip_args = args
|
||||
pip_args = prepare_pip_source_args(sources, pip_args)
|
||||
pip_options, _ = pip_command.parser.parse_args(pip_args)
|
||||
pip_options.cache_dir = CACHE_DIR
|
||||
return pip_options
|
||||
|
||||
|
||||
def get_finder(sources=None, pip_command=None, pip_options=None):
|
||||
"""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
|
||||
:param pip_command: A pip command instance, defaults to None
|
||||
:type pip_command: :class:`~pip._internal.cli.base_command.Command`
|
||||
:param pip_options: A pip options, defaults to None
|
||||
:type pip_options: :class:`~pip._internal.cli.cmdoptions`
|
||||
:return: A package finder
|
||||
:rtype: :class:`~pip._internal.index.PackageFinder`
|
||||
"""
|
||||
|
||||
if not pip_command:
|
||||
pip_command = get_pip_command()
|
||||
if not sources:
|
||||
sources = [
|
||||
{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}
|
||||
]
|
||||
if not pip_options:
|
||||
pip_options = get_pip_options(sources=sources, pip_command=pip_command)
|
||||
session = pip_command._build_session(pip_options)
|
||||
finder = PackageFinder(
|
||||
find_links=[],
|
||||
index_urls=[s.get("url") for s in sources],
|
||||
trusted_hosts=[],
|
||||
allow_all_prereleases=pip_options.pre,
|
||||
session=session,
|
||||
)
|
||||
return finder
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def start_resolver(finder=None, wheel_cache=None):
|
||||
"""Context manager to produce a resolver.
|
||||
|
||||
:param finder: A package finder to use for searching the index
|
||||
:type finder: :class:`~pip._internal.index.PackageFinder`
|
||||
:return: A 3-tuple of finder, preparer, resolver
|
||||
:rtype: (:class:`~pip._internal.operations.prepare.RequirementPreparer`, :class:`~pip._internal.resolve.Resolver`)
|
||||
"""
|
||||
|
||||
pip_command = get_pip_command()
|
||||
pip_options = get_pip_options(pip_command=pip_command)
|
||||
|
||||
if not finder:
|
||||
finder = get_finder(pip_command=pip_command, pip_options=pip_options)
|
||||
|
||||
if not wheel_cache:
|
||||
wheel_cache = WHEEL_CACHE
|
||||
_ensure_dir(fs_str(os.path.join(wheel_cache.cache_dir, "wheels")))
|
||||
|
||||
download_dir = PKGS_DOWNLOAD_DIR
|
||||
_ensure_dir(download_dir)
|
||||
|
||||
_build_dir = TemporaryDirectory(fs_str("build"))
|
||||
_source_dir = TemporaryDirectory(fs_str("source"))
|
||||
preparer = partialclass(
|
||||
RequirementPreparer,
|
||||
build_dir=_build_dir.name,
|
||||
src_dir=_source_dir.name,
|
||||
download_dir=download_dir,
|
||||
wheel_download_dir=WHEEL_DOWNLOAD_DIR,
|
||||
progress_bar="off",
|
||||
build_isolation=False,
|
||||
)
|
||||
resolver = partialclass(
|
||||
Resolver,
|
||||
finder=finder,
|
||||
session=finder.session,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
force_reinstall=True,
|
||||
ignore_dependencies=False,
|
||||
ignore_requires_python=True,
|
||||
ignore_installed=True,
|
||||
isolated=False,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=False,
|
||||
)
|
||||
if packaging.version.parse(pip_version) >= packaging.version.parse('18'):
|
||||
with RequirementTracker() as req_tracker:
|
||||
preparer = preparer(req_tracker=req_tracker)
|
||||
yield resolver(preparer=preparer)
|
||||
else:
|
||||
preparer = preparer()
|
||||
yield resolver(preparer=preparer)
|
||||
|
||||
|
||||
def get_grouped_dependencies(constraints):
|
||||
# We need to track what contributed a specifierset
|
||||
# as well as which specifiers were required by the root node
|
||||
# in order to resolve any conflicts when we are deciding which thing to backtrack on
|
||||
# then we take the loose match (which _is_ flexible) and start moving backwards in
|
||||
# versions by popping them off of a stack and checking for the conflicting package
|
||||
for _, ireqs in full_groupby(constraints, key=key_from_ireq):
|
||||
ireqs = list(ireqs)
|
||||
editable_ireq = first(ireqs, key=lambda ireq: ireq.editable)
|
||||
if editable_ireq:
|
||||
yield editable_ireq # ignore all the other specs: the editable one is the one that counts
|
||||
continue
|
||||
ireqs = iter(ireqs)
|
||||
# deepcopy the accumulator so as to not modify the self.our_constraints invariant
|
||||
combined_ireq = copy.deepcopy(next(ireqs))
|
||||
for ireq in ireqs:
|
||||
# NOTE we may be losing some info on dropped reqs here
|
||||
try:
|
||||
combined_ireq.req.specifier &= ireq.req.specifier
|
||||
except TypeError:
|
||||
if ireq.req.specifier._specs and not combined_ireq.req.specifier._specs:
|
||||
combined_ireq.req.specifier._specs = ireq.req.specifier._specs
|
||||
combined_ireq.constraint &= ireq.constraint
|
||||
if not combined_ireq.markers:
|
||||
combined_ireq.markers = ireq.markers
|
||||
else:
|
||||
_markers = combined_ireq.markers._markers
|
||||
if not isinstance(_markers[0], (tuple, list)):
|
||||
combined_ireq.markers._markers = [
|
||||
_markers,
|
||||
"and",
|
||||
ireq.markers._markers,
|
||||
]
|
||||
# Return a sorted, de-duped tuple of extras
|
||||
combined_ireq.extras = tuple(
|
||||
sorted(set(tuple(combined_ireq.extras) + tuple(ireq.extras)))
|
||||
)
|
||||
yield combined_ireq
|
||||
+61
-21
@@ -1,24 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import attr
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import plette.lockfiles
|
||||
import six
|
||||
|
||||
from vistir.compat import Path
|
||||
from vistir.contextmanagers import atomic_open_for_write
|
||||
|
||||
from .requirements import Requirement
|
||||
from .utils import optional_instance_of
|
||||
from .._compat import Path, FileNotFoundError
|
||||
|
||||
|
||||
@attr.s
|
||||
class Lockfile(object):
|
||||
dev_requirements = attr.ib(default=attr.Factory(list))
|
||||
requirements = attr.ib(default=attr.Factory(list))
|
||||
path = attr.ib(default=None, validator=optional_instance_of(Path))
|
||||
pipfile_hash = attr.ib(default=None)
|
||||
DEFAULT_NEWLINES = u"\n"
|
||||
|
||||
|
||||
def preferred_newlines(f):
|
||||
if isinstance(f.newlines, six.text_type):
|
||||
return f.newlines
|
||||
return DEFAULT_NEWLINES
|
||||
|
||||
|
||||
class Lockfile(plette.lockfiles.Lockfile):
|
||||
def __init__(self, *args, **kwargs):
|
||||
path = kwargs.pop("path", None)
|
||||
self.requirements = kwargs.pop("requirements", [])
|
||||
self.dev_requirements = kwargs.pop("dev_requirements", [])
|
||||
self.path = Path(path) if path else None
|
||||
self.newlines = u"\n"
|
||||
super(Lockfile, self).__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
if not path:
|
||||
path = os.curdir
|
||||
path = Path(path).absolute()
|
||||
if path.is_dir():
|
||||
path = path / "Pipfile.lock"
|
||||
elif path.name == "Pipfile":
|
||||
path = path.parent / "Pipfile.lock"
|
||||
if not path.exists():
|
||||
raise OSError("Path does not exist: %s" % path)
|
||||
return cls.create(path.parent, lockfile_name=path.name)
|
||||
|
||||
@classmethod
|
||||
def create(cls, project_path, lockfile_name="Pipfile.lock"):
|
||||
"""Create a new lockfile instance
|
||||
|
||||
:param project_path: Path to the project root
|
||||
:param project_path: Path to project root
|
||||
:type project_path: str or :class:`~pathlib.Path`
|
||||
:returns: List[:class:`~requirementslib.Requirement`] objects
|
||||
"""
|
||||
@@ -28,20 +58,30 @@ class Lockfile(object):
|
||||
lockfile_path = project_path / lockfile_name
|
||||
requirements = []
|
||||
dev_requirements = []
|
||||
if not lockfile_path.exists():
|
||||
raise FileNotFoundError("No such lockfile: %s" % lockfile_path)
|
||||
|
||||
with lockfile_path.open(encoding="utf-8") as f:
|
||||
lockfile = json.loads(f.read())
|
||||
lockfile = super(Lockfile, cls).load(f)
|
||||
lockfile.newlines = preferred_newlines(f)
|
||||
for k in lockfile["develop"].keys():
|
||||
dev_requirements.append(Requirement.from_pipfile(k, lockfile["develop"][k]))
|
||||
dev_requirements.append(Requirement.from_pipfile(k, lockfile.develop[k]._data))
|
||||
for k in lockfile["default"].keys():
|
||||
requirements.append(Requirement.from_pipfile(k, lockfile["default"][k]))
|
||||
return cls(
|
||||
path=lockfile_path,
|
||||
requirements=requirements,
|
||||
dev_requirements=dev_requirements,
|
||||
)
|
||||
requirements.append(Requirement.from_pipfile(k, lockfile.default[k]._data))
|
||||
lockfile.requirements = requirements
|
||||
lockfile.dev_requirements = dev_requirements
|
||||
lockfile.path = lockfile_path
|
||||
return lockfile
|
||||
|
||||
@property
|
||||
def dev_requirements_list(self):
|
||||
return [r.as_pipfile() for r in self.dev_requirements]
|
||||
|
||||
@property
|
||||
def requirements_list(self):
|
||||
return [r.as_pipfile() for r in self.requirements]
|
||||
|
||||
def write(self):
|
||||
open_kwargs = {"newline": self.newlines}
|
||||
with atomic_open_for_write(self.path.as_posix(), **open_kwargs) as f:
|
||||
super(Lockfile, self).dump(f, encoding="utf-8")
|
||||
|
||||
def as_requirements(self, include_hashes=False, dev=False):
|
||||
"""Returns a list of requirements in pip-style format"""
|
||||
|
||||
+8
-7
@@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import attr
|
||||
import six
|
||||
from packaging.markers import Marker, InvalidMarker
|
||||
from .baserequirement import BaseRequirement
|
||||
from .utils import validate_markers, filter_none
|
||||
|
||||
from packaging.markers import InvalidMarker, Marker
|
||||
|
||||
from ..exceptions import RequirementError
|
||||
from .baserequirement import BaseRequirement
|
||||
from .utils import filter_none, validate_markers
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -84,10 +85,10 @@ class PipenvMarkers(BaseRequirement):
|
||||
markers = []
|
||||
for marker in marker_strings:
|
||||
markers.append(marker)
|
||||
marker = ''
|
||||
combined_marker = None
|
||||
try:
|
||||
marker = cls.make_marker(" and ".join(markers))
|
||||
combined_marker = cls.make_marker(" and ".join(markers))
|
||||
except RequirementError:
|
||||
pass
|
||||
else:
|
||||
return marker
|
||||
return combined_marker
|
||||
|
||||
+41
-165
@@ -1,100 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import attr
|
||||
import contoml
|
||||
import os
|
||||
import toml
|
||||
from .._vendor import pipfile
|
||||
|
||||
from vistir.compat import Path
|
||||
|
||||
from .requirements import Requirement
|
||||
from .utils import optional_instance_of, filter_none
|
||||
from .._compat import Path, FileNotFoundError
|
||||
from ..exceptions import RequirementError
|
||||
import plette.pipfiles
|
||||
|
||||
|
||||
@attr.s
|
||||
class Source(object):
|
||||
#: URL to PyPI instance
|
||||
url = attr.ib(default="pypi")
|
||||
#: If False, skip SSL checks
|
||||
verify_ssl = attr.ib(default=True, validator=optional_instance_of(bool))
|
||||
#: human name to refer to this source (can be referenced in packages or dev-packages)
|
||||
name = attr.ib(default="")
|
||||
|
||||
def get_dict(self):
|
||||
return attr.asdict(self)
|
||||
|
||||
@property
|
||||
def expanded(self):
|
||||
source_dict = attr.asdict(self).copy()
|
||||
source_dict["url"] = os.path.expandvars(source_dict.get("url"))
|
||||
return source_dict
|
||||
|
||||
|
||||
@attr.s
|
||||
class Section(object):
|
||||
ALLOWED_NAMES = ("packages", "dev-packages")
|
||||
#: Name of the pipfile section
|
||||
name = attr.ib(default="packages")
|
||||
#: A list of requirements that are contained by the section
|
||||
requirements = attr.ib(default=list)
|
||||
|
||||
def get_dict(self):
|
||||
_dict = {}
|
||||
for req in self.requirements:
|
||||
_dict.update(req.as_pipfile())
|
||||
return {self.name: _dict}
|
||||
|
||||
@property
|
||||
def vcs_requirements(self):
|
||||
return [req for req in self.requirements if req.is_vcs]
|
||||
|
||||
@property
|
||||
def editable_requirements(self):
|
||||
return [req for req in self.requirements if req.editable]
|
||||
|
||||
|
||||
@attr.s
|
||||
class RequiresSection(object):
|
||||
python_version = attr.ib(default=None)
|
||||
python_full_version = attr.ib(default=None)
|
||||
|
||||
def get_dict(self):
|
||||
requires = attr.asdict(self, filter=filter_none)
|
||||
if not requires:
|
||||
return {}
|
||||
return {"requires": requires}
|
||||
|
||||
|
||||
@attr.s
|
||||
class PipenvSection(object):
|
||||
allow_prereleases = attr.ib(default=False)
|
||||
|
||||
def get_dict(self):
|
||||
if self.allow_prereleases:
|
||||
return {"pipenv": attr.asdict(self)}
|
||||
return {}
|
||||
|
||||
|
||||
@attr.s
|
||||
class Pipfile(object):
|
||||
#: Path to the pipfile
|
||||
path = attr.ib(default=None, converter=Path, validator=optional_instance_of(Path))
|
||||
#: Sources listed in the pipfile
|
||||
sources = attr.ib(default=attr.Factory(list))
|
||||
#: Sections contained by the pipfile
|
||||
sections = attr.ib(default=attr.Factory(list))
|
||||
#: Scripts found in the pipfile
|
||||
scripts = attr.ib(default=attr.Factory(dict))
|
||||
#: This section stores information about what python version is required
|
||||
requires = attr.ib(default=attr.Factory(RequiresSection))
|
||||
#: This section stores information about pipenv such as prerelease requirements
|
||||
pipenv = attr.ib(default=attr.Factory(PipenvSection))
|
||||
#: This is the sha256 hash of the pipfile (without environment interpolation)
|
||||
pipfile_hash = attr.ib()
|
||||
|
||||
@pipfile_hash.default
|
||||
def get_hash(self):
|
||||
p = pipfile.load(self.path.as_posix(), inject_env=False)
|
||||
return p.hash
|
||||
class Pipfile(plette.pipfiles.Pipfile):
|
||||
|
||||
@property
|
||||
def requires_python(self):
|
||||
@@ -102,50 +15,7 @@ class Pipfile(object):
|
||||
|
||||
@property
|
||||
def allow_prereleases(self):
|
||||
return self.pipenv.allow_prereleases
|
||||
|
||||
def get_sources(self):
|
||||
"""Return a dictionary with a list of dictionaries of pipfile sources"""
|
||||
_dict = {}
|
||||
for src in self.sources:
|
||||
_dict.update(src.get_dict())
|
||||
return {"source": _dict} if _dict else {}
|
||||
|
||||
def get_sections(self):
|
||||
"""Return a dictionary with both pipfile sections and requirements"""
|
||||
_dict = {}
|
||||
for section in self.sections:
|
||||
_dict.update(section.get_dict())
|
||||
return _dict
|
||||
|
||||
def get_pipenv(self):
|
||||
pipenv_dict = self.pipenv.get_dict()
|
||||
if pipenv_dict:
|
||||
return pipenv_dict
|
||||
|
||||
def get_requires(self):
|
||||
req_dict = self.requires.get_dict()
|
||||
return req_dict if req_dict else {}
|
||||
|
||||
def get_dict(self):
|
||||
_dict = attr.asdict(self, recurse=False)
|
||||
for k in ["path", "pipfile_hash", "sources", "sections", "requires", "pipenv"]:
|
||||
if k in _dict:
|
||||
_dict.pop(k)
|
||||
return _dict
|
||||
|
||||
def dump(self, to_dict=False):
|
||||
"""Dumps the pipfile to a toml string
|
||||
"""
|
||||
|
||||
_dict = self.get_sources()
|
||||
_dict.update(self.get_sections())
|
||||
_dict.update(self.get_dict())
|
||||
_dict.update(self.get_pipenv())
|
||||
_dict.update(self.get_requires())
|
||||
if to_dict:
|
||||
return _dict
|
||||
return contoml.dumps(_dict)
|
||||
return self.get("pipenv", {}).get("allow_prereleases", False)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
@@ -156,34 +26,40 @@ class Pipfile(object):
|
||||
raise FileNotFoundError("%s is not a valid project path!" % path)
|
||||
elif not pipfile_path.exists() or not pipfile_path.is_file():
|
||||
raise RequirementError("%s is not a valid Pipfile" % pipfile_path)
|
||||
pipfile_dict = toml.load(pipfile_path.as_posix())
|
||||
sections = [cls.get_section(pipfile_dict, s) for s in Section.ALLOWED_NAMES]
|
||||
pipenv = pipfile_dict.get("pipenv", {})
|
||||
requires = pipfile_dict.get("requires", {})
|
||||
creation_dict = {
|
||||
"path": pipfile_path,
|
||||
"sources": [Source(**src) for src in pipfile_dict.get("source", [])],
|
||||
"sections": sections,
|
||||
"scripts": pipfile_dict.get("scripts"),
|
||||
}
|
||||
if requires:
|
||||
creation_dict["requires"] = RequiresSection(**requires)
|
||||
if pipenv:
|
||||
creation_dict["pipenv"] = PipenvSection(**pipenv)
|
||||
return cls(**creation_dict)
|
||||
with pipfile_path.open(encoding="utf-8") as fp:
|
||||
pipfile = super(Pipfile, cls).load(fp)
|
||||
pipfile.dev_requirements = [
|
||||
Requirement.from_pipfile(k, v) for k, v in pipfile.dev_packages.items()
|
||||
]
|
||||
pipfile.requirements = [
|
||||
Requirement.from_pipfile(k, v) for k, v in pipfile.packages.items()
|
||||
]
|
||||
pipfile.path = pipfile_path
|
||||
return pipfile
|
||||
|
||||
@staticmethod
|
||||
def get_section(pf_dict, section):
|
||||
"""Get section objects from a pipfile dictionary
|
||||
# def resolve(self):
|
||||
# It would be nice to still use this api someday
|
||||
# option_sources = [s.expanded for s in self.sources]
|
||||
# pip_args = []
|
||||
# if self.pipenv.allow_prereleases:
|
||||
# pip_args.append('--pre')
|
||||
# pip_options = get_pip_options(pip_args, sources=option_sources)
|
||||
# finder = get_finder(sources=option_sources, pip_options=pip_options)
|
||||
# resolver = DependencyResolver.create(finder=finder, allow_prereleases=self.pipenv.allow_prereleases)
|
||||
# pkg_dict = {}
|
||||
# for pkg in self.dev_packages.requirements + self.packages.requirements:
|
||||
# pkg_dict[pkg.name] = pkg
|
||||
# resolver.resolve(list(pkg_dict.values()))
|
||||
# return resolver
|
||||
|
||||
:param pf_dict: A toml loaded pipfile dictionary
|
||||
:type pf_dict: dict
|
||||
:returns: Section objects
|
||||
"""
|
||||
sect = pf_dict.get(section)
|
||||
requirements = []
|
||||
if section not in Section.ALLOWED_NAMES:
|
||||
raise ValueError("Not a valid pipfile section name: %s" % section)
|
||||
for name, pf_entry in sect.items():
|
||||
requirements.append(Requirement.from_pipfile(name, pf_entry))
|
||||
return Section(name=section, requirements=requirements)
|
||||
@property
|
||||
def dev_packages(self, as_requirements=True):
|
||||
if as_requirements:
|
||||
return self.dev_requirements
|
||||
return self.dev_packages
|
||||
|
||||
@property
|
||||
def packages(self, as_requirements=True):
|
||||
if as_requirements:
|
||||
return self.requirements
|
||||
return self.packages
|
||||
|
||||
+311
-114
@@ -1,55 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
import attr
|
||||
import collections
|
||||
import hashlib
|
||||
import os
|
||||
import requirements
|
||||
|
||||
import attr
|
||||
import atexit
|
||||
|
||||
from first import first
|
||||
from packaging.markers import Marker
|
||||
from packaging.specifiers import Specifier, SpecifierSet
|
||||
from packaging.utils import canonicalize_name
|
||||
from six.moves.urllib import parse as urllib_parse
|
||||
from six.moves.urllib.parse import unquote
|
||||
|
||||
from pip_shims.shims import (
|
||||
InstallRequirement, Link, Wheel, _strip_extras, parse_version, path_to_url,
|
||||
url_to_path
|
||||
)
|
||||
from vistir.compat import FileNotFoundError, Path, TemporaryDirectory
|
||||
from vistir.misc import dedup
|
||||
from vistir.path import get_converted_relative_path, is_valid_url, is_file_url, mkdir_p
|
||||
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import VCS_LIST, is_vcs, is_installable_file
|
||||
from .baserequirement import BaseRequirement
|
||||
from .dependencies import (
|
||||
AbstractDependency, find_all_matches, get_abstract_dependencies,
|
||||
get_dependencies, get_finder
|
||||
)
|
||||
from .markers import PipenvMarkers
|
||||
from .utils import (
|
||||
HASH_STRING,
|
||||
extras_to_string,
|
||||
get_version,
|
||||
specs_to_string,
|
||||
validate_specifiers,
|
||||
validate_path,
|
||||
validate_vcs,
|
||||
build_vcs_link,
|
||||
add_ssh_scheme_to_git_uri,
|
||||
strip_ssh_from_git_uri,
|
||||
split_vcs_method_from_uri,
|
||||
filter_none,
|
||||
optional_instance_of,
|
||||
split_markers_from_line,
|
||||
parse_extras,
|
||||
)
|
||||
from .._compat import (
|
||||
Link,
|
||||
path_to_url,
|
||||
url_to_path,
|
||||
_strip_extras,
|
||||
InstallRequirement,
|
||||
Path,
|
||||
urlparse,
|
||||
unquote,
|
||||
Wheel,
|
||||
FileNotFoundError,
|
||||
)
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import (
|
||||
VCS_LIST,
|
||||
is_installable_file,
|
||||
is_vcs,
|
||||
is_valid_url,
|
||||
pep423_name,
|
||||
get_converted_relative_path,
|
||||
HASH_STRING, add_ssh_scheme_to_git_uri, build_vcs_link, filter_none,
|
||||
format_requirement, get_version, init_requirement,
|
||||
is_pinned_requirement, make_install_requirement, optional_instance_of,
|
||||
parse_extras, specs_to_string, split_markers_from_line,
|
||||
split_vcs_method_from_uri, strip_ssh_from_git_uri, validate_path,
|
||||
validate_specifiers, validate_vcs, extras_to_string
|
||||
)
|
||||
from .vcs import VCSRepository
|
||||
from packaging.requirements import Requirement as PackagingRequirement
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -58,25 +49,31 @@ class NamedRequirement(BaseRequirement):
|
||||
version = attr.ib(validator=attr.validators.optional(validate_specifiers))
|
||||
req = attr.ib()
|
||||
extras = attr.ib(default=attr.Factory(list))
|
||||
editable = attr.ib(default=False)
|
||||
|
||||
@req.default
|
||||
def get_requirement(self):
|
||||
from pkg_resources import RequirementParseError
|
||||
try:
|
||||
req = first(requirements.parse("{0}{1}".format(self.name, self.version)))
|
||||
except RequirementParseError:
|
||||
raise RequirementError(
|
||||
"Error parsing requirement: %s%s" % (self.name, self.version)
|
||||
)
|
||||
req = init_requirement("{0}{1}".format(canonicalize_name(self.name), self.version))
|
||||
return req
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
req = first(requirements.parse(line))
|
||||
req = init_requirement(line)
|
||||
specifiers = None
|
||||
if req.specifier:
|
||||
specifiers = specs_to_string(req.specs)
|
||||
return cls(name=req.name, version=specifiers, req=req)
|
||||
specifiers = specs_to_string(req.specifier)
|
||||
req.line = line
|
||||
name = getattr(req, "name", None)
|
||||
if not name:
|
||||
name = getattr(req, "project_name", None)
|
||||
req.name = name
|
||||
if not name:
|
||||
name = getattr(req, "key", line)
|
||||
req.name = name
|
||||
extras = None
|
||||
if req.extras:
|
||||
extras = list(req.extras)
|
||||
return cls(name=name, version=specifiers, req=req, extras=extras)
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
@@ -85,13 +82,17 @@ class NamedRequirement(BaseRequirement):
|
||||
creation_args = {k: v for k, v in pipfile.items() if k in cls.attr_fields()}
|
||||
creation_args["name"] = name
|
||||
version = get_version(pipfile)
|
||||
extras = creation_args.get("extras", None)
|
||||
creation_args["version"] = version
|
||||
creation_args["req"] = first(requirements.parse("{0}{1}".format(name, version)))
|
||||
req = init_requirement("{0}{1}".format(name, version))
|
||||
if extras:
|
||||
req.extras += tuple(extras,)
|
||||
creation_args["req"] = req
|
||||
return cls(**creation_args)
|
||||
|
||||
@property
|
||||
def line_part(self):
|
||||
return "{self.name}".format(self=self)
|
||||
return "{0}".format(canonicalize_name(self.name))
|
||||
|
||||
@property
|
||||
def pipfile_part(self):
|
||||
@@ -243,17 +244,17 @@ class FileRequirement(BaseRequirement):
|
||||
and self.setup_path
|
||||
and self.setup_path.exists()
|
||||
):
|
||||
from distutils.core import run_setup
|
||||
from setuptools.dist import distutils
|
||||
|
||||
old_curdir = os.path.abspath(os.getcwd())
|
||||
try:
|
||||
os.chdir(str(self.setup_path.parent))
|
||||
dist = run_setup(self.setup_path.as_posix(), stop_after="init")
|
||||
dist = distutils.core.run_setup(self.setup_path.as_posix())
|
||||
name = dist.get_name()
|
||||
except (FileNotFoundError, IOError) as e:
|
||||
dist = None
|
||||
except Exception as e:
|
||||
from .._compat import InstallRequirement, make_abstract_dist
|
||||
from pip_shims.shims import InstallRequirement, make_abstract_dist
|
||||
|
||||
try:
|
||||
if not isinstance(Path, self.path):
|
||||
@@ -289,14 +290,18 @@ class FileRequirement(BaseRequirement):
|
||||
|
||||
@req.default
|
||||
def get_requirement(self):
|
||||
prefix = "-e " if self.editable else ""
|
||||
line = "{0}{1}".format(prefix, self.link.url)
|
||||
req = first(requirements.parse(line))
|
||||
req = init_requirement(canonicalize_name(self.name))
|
||||
req.editable = False
|
||||
req.line = self.link.url_without_fragment
|
||||
if self.path and self.link and self.link.scheme.startswith("file"):
|
||||
req.local_file = True
|
||||
req.path = self.path
|
||||
req.uri = None
|
||||
req.url = None
|
||||
self._uri_scheme = "file"
|
||||
else:
|
||||
req.local_file = False
|
||||
req.path = None
|
||||
req.url = self.link.url_without_fragment
|
||||
if self.editable:
|
||||
req.editable = True
|
||||
req.link = self.link
|
||||
@@ -330,7 +335,7 @@ class FileRequirement(BaseRequirement):
|
||||
editable = line.startswith("-e ")
|
||||
line = line.split(" ", 1)[1] if editable else line
|
||||
setup_path = None
|
||||
if not any([is_installable_file(line), is_valid_url(line)]):
|
||||
if not any([is_installable_file(line), is_valid_url(line), is_file_url(line)]):
|
||||
raise RequirementError(
|
||||
"Supplied requirement is not installable: {0!r}".format(line)
|
||||
)
|
||||
@@ -473,17 +478,6 @@ class VCSRequirement(FileRequirement):
|
||||
name = attr.ib()
|
||||
link = attr.ib()
|
||||
req = attr.ib()
|
||||
_INCLUDE_FIELDS = (
|
||||
"editable",
|
||||
"uri",
|
||||
"path",
|
||||
"vcs",
|
||||
"ref",
|
||||
"subdirectory",
|
||||
"name",
|
||||
"link",
|
||||
"req",
|
||||
)
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
split = urllib_parse.urlsplit(self.uri)
|
||||
@@ -522,16 +516,66 @@ class VCSRequirement(FileRequirement):
|
||||
uri = "{0}+{1}".format(self.vcs, uri)
|
||||
return uri
|
||||
|
||||
def get_commit_hash(self, src_dir=None):
|
||||
src_dir = os.environ.get('SRC_DIR', None) if not src_dir else src_dir
|
||||
if not src_dir:
|
||||
_src_dir = TemporaryDirectory()
|
||||
atexit.register(_src_dir.cleanup)
|
||||
src_dir = _src_dir.name
|
||||
checkout_dir = Path(src_dir).joinpath(self.name).as_posix()
|
||||
vcsrepo = VCSRepository(
|
||||
url=self.link.url,
|
||||
name=self.name,
|
||||
ref=self.ref if self.ref else None,
|
||||
checkout_directory=checkout_dir,
|
||||
vcs_type=self.vcs
|
||||
)
|
||||
vcsrepo.obtain()
|
||||
return vcsrepo.get_commit_hash()
|
||||
|
||||
def update_repo(self, src_dir=None, ref=None):
|
||||
src_dir = os.environ.get('SRC_DIR', None) if not src_dir else src_dir
|
||||
if not src_dir:
|
||||
_src_dir = TemporaryDirectory()
|
||||
atexit.register(_src_dir.cleanup)
|
||||
src_dir = _src_dir.name
|
||||
checkout_dir = Path(src_dir).joinpath(self.name).as_posix()
|
||||
ref = self.ref if not ref else ref
|
||||
vcsrepo = VCSRepository(
|
||||
url=self.link.url,
|
||||
name=self.name,
|
||||
ref=ref if ref else None,
|
||||
checkout_directory=checkout_dir,
|
||||
vcs_type=self.vcs
|
||||
)
|
||||
if not os.path.exists(checkout_dir):
|
||||
vcsrepo.obtain()
|
||||
else:
|
||||
vcsrepo.update()
|
||||
return vcsrepo.get_commit_hash()
|
||||
|
||||
@req.default
|
||||
def get_requirement(self):
|
||||
prefix = "-e " if self.editable else ""
|
||||
line = "{0}{1}".format(prefix, self.link.url)
|
||||
req = first(requirements.parse(line))
|
||||
name = self.name or self.link.egg_fragment
|
||||
url = self.uri or self.link.url_without_fragment
|
||||
if not name:
|
||||
raise ValueError(
|
||||
"pipenv requires an #egg fragment for version controlled "
|
||||
"dependencies. Please install remote dependency "
|
||||
"in the form {0}#egg=<package-name>.".format(url)
|
||||
)
|
||||
req = init_requirement(canonicalize_name(self.name))
|
||||
req.editable = self.editable
|
||||
req.url = self.uri
|
||||
req.line = self.link.url
|
||||
if self.ref:
|
||||
req.revision = self.ref
|
||||
if self.extras:
|
||||
req.extras = self.extras
|
||||
req.vcs = self.vcs
|
||||
if self.path and self.link and self.link.scheme.startswith("file"):
|
||||
req.local_file = True
|
||||
req.path = self.path
|
||||
if self.editable:
|
||||
req.editable = True
|
||||
req.link = self.link
|
||||
if (
|
||||
self.uri != unquote(self.link.url_without_fragment)
|
||||
@@ -539,19 +583,7 @@ class VCSRequirement(FileRequirement):
|
||||
and "git+git@" in self.uri
|
||||
):
|
||||
req.line = self.uri
|
||||
req.uri = self.uri
|
||||
if not req.name:
|
||||
raise ValueError(
|
||||
"pipenv requires an #egg fragment for version controlled "
|
||||
"dependencies. Please install remote dependency "
|
||||
"in the form {0}#egg=<package-name>.".format(req.uri)
|
||||
)
|
||||
if self.vcs and not req.vcs:
|
||||
req.vcs = self.vcs
|
||||
if self.ref and not req.revision:
|
||||
req.revision = self.ref
|
||||
if self.extras and not req.extras:
|
||||
req.extras = self.extras
|
||||
req.url = self.uri
|
||||
return req
|
||||
|
||||
@classmethod
|
||||
@@ -564,6 +596,10 @@ class VCSRequirement(FileRequirement):
|
||||
if k in pipfile
|
||||
]
|
||||
for key in pipfile_keys:
|
||||
if key == "extras":
|
||||
extras = pipfile.get(key, None)
|
||||
if extras:
|
||||
pipfile[key] = sorted(dedup([extra.lower() for extra in extras]))
|
||||
if key in VCS_LIST:
|
||||
creation_args["vcs"] = key
|
||||
composed_uri = add_ssh_scheme_to_git_uri(
|
||||
@@ -612,8 +648,12 @@ class VCSRequirement(FileRequirement):
|
||||
def line_part(self):
|
||||
"""requirements.txt compatible line part sans-extras"""
|
||||
if self.req:
|
||||
return self.req.line
|
||||
base = "{0}".format(self.link)
|
||||
base = self.req.line
|
||||
if base and self.extras and not extras_to_string(self.extras) in base:
|
||||
if self.subdirectory:
|
||||
base = "{0}".format(self.get_link().url)
|
||||
else:
|
||||
base = "{0}{1}".format(base, extras_to_string(sorted(self.extras)))
|
||||
if self.editable:
|
||||
base = "-e {0}".format(base)
|
||||
return base
|
||||
@@ -650,8 +690,8 @@ class Requirement(object):
|
||||
editable = attr.ib(default=None)
|
||||
hashes = attr.ib(default=attr.Factory(list), converter=list)
|
||||
extras = attr.ib(default=attr.Factory(list))
|
||||
abstract_dep = attr.ib(default=None)
|
||||
_ireq = None
|
||||
_INCLUDE_FIELDS = ("name", "markers", "index", "editable", "hashes", "extras")
|
||||
|
||||
@name.default
|
||||
def get_name(self):
|
||||
@@ -678,14 +718,20 @@ class Requirement(object):
|
||||
@property
|
||||
def extras_as_pip(self):
|
||||
if self.extras:
|
||||
return "[{0}]".format(",".join(self.extras))
|
||||
return "[{0}]".format(",".join(sorted([extra.lower() for extra in self.extras])))
|
||||
|
||||
return ""
|
||||
|
||||
@property
|
||||
def commit_hash(self):
|
||||
if not self.is_vcs:
|
||||
return None
|
||||
return self.req.get_commit_hash()
|
||||
|
||||
@specifiers.default
|
||||
def get_specifiers(self):
|
||||
if self.req and self.req.req.specifier:
|
||||
return specs_to_string(self.req.req.specs)
|
||||
return specs_to_string(self.req.req.specifier)
|
||||
return
|
||||
|
||||
@property
|
||||
@@ -702,10 +748,15 @@ class Requirement(object):
|
||||
|
||||
@property
|
||||
def normalized_name(self):
|
||||
return pep423_name(self.name)
|
||||
return canonicalize_name(self.name)
|
||||
|
||||
def copy(self):
|
||||
return attr.evolve(self)
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
if isinstance(line, InstallRequirement):
|
||||
line = format_requirement(line)
|
||||
hashes = None
|
||||
if "--hash=" in line:
|
||||
hashes = line.split(" --hash=")
|
||||
@@ -714,6 +765,7 @@ class Requirement(object):
|
||||
line = line.split(" ", 1)[1] if editable else line
|
||||
line, markers = split_markers_from_line(line)
|
||||
line, extras = _strip_extras(line)
|
||||
specifiers = ''
|
||||
if extras:
|
||||
extras = parse_extras(extras)
|
||||
line = line.strip('"').strip("'").strip()
|
||||
@@ -721,9 +773,10 @@ class Requirement(object):
|
||||
vcs = None
|
||||
# Installable local files and installable non-vcs urls are handled
|
||||
# as files, generally speaking
|
||||
if is_installable_file(line) or (is_valid_url(line) and not is_vcs(line)):
|
||||
line_is_vcs = is_vcs(line)
|
||||
if is_installable_file(line) or ((is_file_url(line) or is_valid_url(line)) and not line_is_vcs):
|
||||
r = FileRequirement.from_line(line_with_prefix)
|
||||
elif is_vcs(line):
|
||||
elif line_is_vcs:
|
||||
r = VCSRequirement.from_line(line_with_prefix, extras=extras)
|
||||
vcs = r.vcs
|
||||
elif line == "." and not is_installable_file(line):
|
||||
@@ -739,6 +792,7 @@ class Requirement(object):
|
||||
spec_idx = min((line.index(match) for match in spec_matches))
|
||||
name = line[:spec_idx]
|
||||
version = line[spec_idx:]
|
||||
specifiers = version
|
||||
if not extras:
|
||||
name, extras = _strip_extras(name)
|
||||
if extras:
|
||||
@@ -746,8 +800,19 @@ class Requirement(object):
|
||||
if version:
|
||||
name = "{0}{1}".format(name, version)
|
||||
r = NamedRequirement.from_line(line)
|
||||
req_markers = None
|
||||
if markers:
|
||||
r.req.markers = markers
|
||||
req_markers = PackagingRequirement("fakepkg; {0}".format(markers))
|
||||
r.req.marker = getattr(req_markers, "marker", None)
|
||||
r.req.local_file = getattr(r.req, "local_file", False)
|
||||
name = getattr(r.req, "name", None)
|
||||
if not name:
|
||||
name = getattr(r.req, "project_name", None)
|
||||
r.req.name = name
|
||||
if not name:
|
||||
name = getattr(r.req, "key", None)
|
||||
if name:
|
||||
r.req.name = name
|
||||
args = {
|
||||
"name": r.name,
|
||||
"vcs": vcs,
|
||||
@@ -756,15 +821,26 @@ class Requirement(object):
|
||||
"editable": editable,
|
||||
}
|
||||
if extras:
|
||||
extras = sorted(dedup([extra.lower() for extra in extras]))
|
||||
args["extras"] = extras
|
||||
r.req.extras = extras
|
||||
r.extras = extras
|
||||
elif r.extras:
|
||||
args["extras"] = r.extras
|
||||
args["extras"] = sorted(dedup([extra.lower() for extra in r.extras]))
|
||||
if hashes:
|
||||
args["hashes"] = hashes
|
||||
return cls(**args)
|
||||
|
||||
@classmethod
|
||||
def from_ireq(cls, ireq):
|
||||
return cls.from_line(format_requirement(ireq))
|
||||
|
||||
@classmethod
|
||||
def from_metadata(cls, name, version, extras, markers):
|
||||
return cls.from_ireq(make_install_requirement(
|
||||
name, version, extras=extras, markers=markers,
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
_pipfile = {}
|
||||
@@ -780,8 +856,14 @@ class Requirement(object):
|
||||
else:
|
||||
r = NamedRequirement.from_pipfile(name, pipfile)
|
||||
markers = PipenvMarkers.from_pipfile(name, _pipfile)
|
||||
req_markers = None
|
||||
if markers:
|
||||
markers = str(markers)
|
||||
req_markers = PackagingRequirement("fakepkg; {0}".format(markers))
|
||||
r.req.marker = getattr(req_markers, "marker", None)
|
||||
r.req.specifier = SpecifierSet(_pipfile["version"])
|
||||
extras = _pipfile.get("extras")
|
||||
r.req.extras = sorted(dedup([extra.lower() for extra in extras])) if extras else []
|
||||
args = {
|
||||
"name": r.name,
|
||||
"vcs": vcs,
|
||||
@@ -793,9 +875,12 @@ class Requirement(object):
|
||||
}
|
||||
if any(key in _pipfile for key in ["hash", "hashes"]):
|
||||
args["hashes"] = _pipfile.get("hashes", [pipfile.get("hash")])
|
||||
return cls(**args)
|
||||
cls_inst = cls(**args)
|
||||
if cls_inst.is_named:
|
||||
cls_inst.req.req.line = cls_inst.as_line()
|
||||
return cls_inst
|
||||
|
||||
def as_line(self, sources=None):
|
||||
def as_line(self, sources=None, include_hashes=True, include_extras=True):
|
||||
"""Format this requirement as a line in requirements.txt.
|
||||
|
||||
If `sources` provided, it should be an sequence of mappings, containing
|
||||
@@ -804,22 +889,52 @@ class Requirement(object):
|
||||
If `sources` is omitted or falsy, no index information will be included
|
||||
in the requirement line.
|
||||
"""
|
||||
line = "{0}{1}{2}{3}{4}".format(
|
||||
if self.is_vcs:
|
||||
include_extras = False
|
||||
parts = [
|
||||
self.req.line_part,
|
||||
self.extras_as_pip if not self.is_vcs else "",
|
||||
self.extras_as_pip if include_extras else "",
|
||||
self.specifiers if self.specifiers else "",
|
||||
self.markers_as_pip,
|
||||
self.hashes_as_pip,
|
||||
)
|
||||
]
|
||||
if include_hashes:
|
||||
parts.append(self.hashes_as_pip)
|
||||
if sources and not (self.requirement.local_file or self.vcs):
|
||||
from ..utils import prepare_pip_source_args
|
||||
|
||||
if self.index:
|
||||
sources = [s for s in sources if s.get("name") == self.index]
|
||||
index_string = " ".join(prepare_pip_source_args(sources))
|
||||
line = "{0} {1}".format(line, index_string)
|
||||
parts.extend([" ", index_string])
|
||||
line = "".join(parts)
|
||||
return line
|
||||
|
||||
def get_markers(self):
|
||||
markers = self.markers
|
||||
if markers:
|
||||
fake_pkg = PackagingRequirement('fakepkg; {0}'.format(markers))
|
||||
markers = fake_pkg.markers
|
||||
return markers
|
||||
|
||||
def get_specifier(self):
|
||||
return Specifier(self.specifiers)
|
||||
|
||||
def get_version(self):
|
||||
return parse_version(self.get_specifier().version)
|
||||
|
||||
def get_requirement(self):
|
||||
req_line = self.req.req.line
|
||||
if req_line.startswith('-e '):
|
||||
_, req_line = req_line.split(" ", 1)
|
||||
req = init_requirement(self.name)
|
||||
req.line = req_line
|
||||
req.specifier = SpecifierSet(self.specifiers if self.specifiers else '')
|
||||
if self.is_vcs or self.is_file_or_url:
|
||||
req.url = self.req.link.url_without_fragment
|
||||
req.marker = self.get_markers()
|
||||
req.extras = set(self.extras) if self.extras else set()
|
||||
return req
|
||||
|
||||
@property
|
||||
def constraint_line(self):
|
||||
return self.as_line()
|
||||
@@ -839,6 +954,8 @@ class Requirement(object):
|
||||
if k in good_keys
|
||||
}
|
||||
name = self.name
|
||||
if 'markers' in req_dict and req_dict['markers']:
|
||||
req_dict['markers'] = req_dict['markers'].replace('"', "'")
|
||||
base_dict = {
|
||||
k: v
|
||||
for k, v in self.req.pipfile_part[name].items()
|
||||
@@ -850,23 +967,103 @@ class Requirement(object):
|
||||
conflicts = [k for k in (conflicting_keys[1:],) if k in base_dict]
|
||||
for k in conflicts:
|
||||
base_dict.pop(k)
|
||||
if "hashes" in base_dict and len(base_dict["hashes"]) == 1:
|
||||
base_dict["hash"] = base_dict.pop("hashes")[0]
|
||||
if "hashes" in base_dict:
|
||||
_hashes = base_dict.pop("hashes")
|
||||
hashes = []
|
||||
for _hash in _hashes:
|
||||
try:
|
||||
hashes.append(_hash.as_line())
|
||||
except AttributeError:
|
||||
hashes.append(_hash)
|
||||
base_dict["hashes"] = sorted(hashes)
|
||||
if len(base_dict.keys()) == 1 and "version" in base_dict:
|
||||
base_dict = base_dict.get("version")
|
||||
return {name: base_dict}
|
||||
|
||||
def as_ireq(self):
|
||||
ireq_line = self.as_line(include_hashes=False)
|
||||
if self.editable or self.req.editable:
|
||||
if ireq_line.startswith("-e "):
|
||||
ireq_line = ireq_line[len("-e "):]
|
||||
ireq = InstallRequirement.from_editable(ireq_line)
|
||||
else:
|
||||
ireq = InstallRequirement.from_line(ireq_line)
|
||||
if not getattr(ireq, "req", None):
|
||||
ireq.req = self.req.req
|
||||
else:
|
||||
ireq.req.extras = self.req.req.extras
|
||||
ireq.req.marker = self.req.req.marker
|
||||
return ireq
|
||||
|
||||
@property
|
||||
def pipfile_entry(self):
|
||||
return self.as_pipfile().copy().popitem()
|
||||
|
||||
@property
|
||||
def ireq(self):
|
||||
if not self._ireq:
|
||||
ireq_line = self.as_line()
|
||||
if ireq_line.startswith("-e "):
|
||||
ireq_line = ireq_line[len("-e ") :]
|
||||
self._ireq = InstallRequirement.from_editable(ireq_line)
|
||||
else:
|
||||
self._ireq = InstallRequirement.from_line(ireq_line)
|
||||
return self._ireq
|
||||
return self.as_ireq()
|
||||
|
||||
def get_dependencies(self, sources=None):
|
||||
"""Retrieve the dependencies of the current requirement.
|
||||
|
||||
Retrieves dependencies of the current requirement. This only works on pinned
|
||||
requirements.
|
||||
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:return: A set of requirement strings of the dependencies of this requirement.
|
||||
:rtype: set(str)
|
||||
"""
|
||||
if not sources:
|
||||
sources = [{
|
||||
'name': 'pypi',
|
||||
'url': 'https://pypi.org/simple',
|
||||
'verify_ssl': True,
|
||||
}]
|
||||
return get_dependencies(self.as_ireq(), sources=sources)
|
||||
|
||||
def get_abstract_dependencies(self, sources=None):
|
||||
"""Retrieve the abstract dependencies of this requirement.
|
||||
|
||||
Returns the abstract dependencies of the current requirement in order to resolve.
|
||||
|
||||
:param sources: A list of sources (pipfile format), defaults to None
|
||||
:param sources: list, optional
|
||||
:return: A list of abstract (unpinned) dependencies
|
||||
:rtype: list[ :class:`~requirementslib.models.dependency.AbstractDependency` ]
|
||||
"""
|
||||
|
||||
if not self.abstract_dep:
|
||||
parent = getattr(self, 'parent', None)
|
||||
self.abstract_dep = AbstractDependency.from_requirement(self, parent=parent)
|
||||
if not sources:
|
||||
sources = [{'url': 'https://pypi.org/simple', 'name': 'pypi', 'verify_ssl': True},]
|
||||
if is_pinned_requirement(self.ireq):
|
||||
deps = self.get_dependencies()
|
||||
else:
|
||||
ireq = sorted(self.find_all_matches(), key=lambda k: k.version)
|
||||
deps = get_dependencies(ireq.pop(), sources=sources)
|
||||
return get_abstract_dependencies(deps, sources=sources, parent=self.abstract_dep)
|
||||
|
||||
def find_all_matches(self, sources=None, finder=None):
|
||||
"""Find all matching candidates for the current requirement.
|
||||
|
||||
Consults a finder to find all matching candidates.
|
||||
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:return: A list of Installation Candidates
|
||||
:rtype: list[ :class:`~pip._internal.index.InstallationCandidate` ]
|
||||
"""
|
||||
if not finder:
|
||||
finder = get_finder(sources=sources)
|
||||
return find_all_matches(finder, self.as_ireq())
|
||||
|
||||
def merge_markers(self, markers):
|
||||
if not isinstance(markers, Marker):
|
||||
markers = Marker(markers)
|
||||
_markers = set(Marker(self.ireq.markers)) if self.ireq.markers else set(markers)
|
||||
_markers.add(markers)
|
||||
new_markers = Marker(" or ".join([str(m) for m in sorted(_markers)]))
|
||||
self.markers = str(new_markers)
|
||||
self.req.req.marker = new_markers
|
||||
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from contextlib import contextmanager
|
||||
|
||||
import attr
|
||||
import six
|
||||
|
||||
from pip_shims.shims import VcsSupport, Wheel
|
||||
|
||||
from ..utils import log
|
||||
from .cache import HashCache
|
||||
from .dependencies import AbstractDependency, find_all_matches, get_finder
|
||||
from .utils import format_requirement, is_pinned_requirement, version_from_ireq
|
||||
|
||||
|
||||
class ResolutionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class DependencyResolver(object):
|
||||
pinned_deps = attr.ib(default=attr.Factory(dict))
|
||||
#: A dictionary of abstract dependencies by name
|
||||
dep_dict = attr.ib(default=attr.Factory(dict))
|
||||
#: A dictionary of sets of version numbers that are valid for a candidate currently
|
||||
candidate_dict = attr.ib(default=attr.Factory(dict))
|
||||
#: A historical record of pins
|
||||
pin_history = attr.ib(default=attr.Factory(dict))
|
||||
#: Whether to allow prerelease dependencies
|
||||
allow_prereleases = attr.ib(default=False)
|
||||
#: Stores hashes for each dependency
|
||||
hashes = attr.ib(default=attr.Factory(dict))
|
||||
#: A hash cache
|
||||
hash_cache = attr.ib(default=attr.Factory(HashCache))
|
||||
#: A finder for searching the index
|
||||
finder = attr.ib(default=None)
|
||||
#: Whether to include hashes even from incompatible wheels
|
||||
include_incompatible_hashes = attr.ib(default=True)
|
||||
#: A cache for storing available canddiates when using all wheels
|
||||
_available_candidates_cache = attr.ib(default=attr.Factory(dict))
|
||||
|
||||
@classmethod
|
||||
def create(cls, finder=None, allow_prereleases=False, get_all_hashes=True):
|
||||
if not finder:
|
||||
finder_args = []
|
||||
if allow_prereleases:
|
||||
finder_args.append('--pre')
|
||||
finder = get_finder(*finder_args)
|
||||
creation_kwargs = {
|
||||
'allow_prereleases': allow_prereleases,
|
||||
'include_incompatible_hashes': get_all_hashes,
|
||||
'finder': finder,
|
||||
'hash_cache': HashCache(),
|
||||
}
|
||||
resolver = cls(**creation_kwargs)
|
||||
return resolver
|
||||
|
||||
@property
|
||||
def dependencies(self):
|
||||
return list(self.dep_dict.values())
|
||||
|
||||
@property
|
||||
def resolution(self):
|
||||
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.
|
||||
|
||||
:param dep: An abstract dependency to add
|
||||
:type dep: :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
:raises ResolutionError: Raised when the given dependency is not compatible with
|
||||
an existing abstract dependency.
|
||||
"""
|
||||
|
||||
if dep.name in self.dep_dict:
|
||||
compatible_versions = self.dep_dict[dep.name].compatible_versions(dep)
|
||||
if compatible_versions:
|
||||
self.candidate_dict[dep.name] = compatible_versions
|
||||
self.dep_dict[dep.name] = self.dep_dict[
|
||||
dep.name
|
||||
].compatible_abstract_dep(dep)
|
||||
else:
|
||||
raise ResolutionError
|
||||
else:
|
||||
self.candidate_dict[dep.name] = dep.version_set
|
||||
self.dep_dict[dep.name] = dep
|
||||
|
||||
def pin_deps(self):
|
||||
"""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.
|
||||
"""
|
||||
|
||||
for name in list(self.dep_dict.keys()):
|
||||
candidates = self.dep_dict[name].candidates[:]
|
||||
abs_dep = self.dep_dict[name]
|
||||
while candidates:
|
||||
pin = candidates.pop()
|
||||
# Move on from existing pins if the new pin isn't compatible
|
||||
if name in self.pinned_deps:
|
||||
if self.pinned_deps[name].editable:
|
||||
continue
|
||||
old_version = version_from_ireq(self.pinned_deps[name])
|
||||
if not pin.editable:
|
||||
new_version = version_from_ireq(pin)
|
||||
if (new_version != old_version and
|
||||
new_version not in self.candidate_dict[name]):
|
||||
continue
|
||||
pin.parent = abs_dep.parent
|
||||
pin_subdeps = self.dep_dict[name].get_deps(pin)
|
||||
backup = self.dep_dict.copy(), self.candidate_dict.copy()
|
||||
try:
|
||||
for pin_dep in pin_subdeps:
|
||||
self.add_abstract_dep(pin_dep)
|
||||
except ResolutionError:
|
||||
self.dep_dict, self.candidate_dict = backup
|
||||
continue
|
||||
else:
|
||||
self.pinned_deps[name] = pin
|
||||
break
|
||||
|
||||
def resolve(self, root_nodes, max_rounds=20):
|
||||
"""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.
|
||||
|
||||
:param root_nodes: A list of the root requirements.
|
||||
:type root_nodes: list[:class:`~requirementslib.models.requirements.Requirement`]
|
||||
:param max_rounds: The max number of resolution rounds, defaults to 20
|
||||
:param max_rounds: int, optional
|
||||
:raises RuntimeError: Raised when max rounds is exceeded without a resolution.
|
||||
"""
|
||||
if self.dep_dict:
|
||||
raise RuntimeError("Do not use the same resolver more than once")
|
||||
|
||||
if not self.hash_cache:
|
||||
self.hash_cache = HashCache()
|
||||
|
||||
# Coerce input into AbstractDependency instances.
|
||||
# We accept str, Requirement, and AbstractDependency as input.
|
||||
for dep in root_nodes:
|
||||
if isinstance(dep, six.string_types):
|
||||
dep = AbstractDependency.from_string(dep)
|
||||
elif not isinstance(dep, AbstractDependency):
|
||||
dep = AbstractDependency.from_requirement(dep)
|
||||
self.add_abstract_dep(dep)
|
||||
|
||||
for round_ in range(max_rounds):
|
||||
self.pin_deps()
|
||||
self.pin_history[round_] = self.pinned_deps.copy()
|
||||
|
||||
if round_ > 0:
|
||||
previous_round = set(self.pin_history[round_ - 1].values())
|
||||
current_values = set(self.pin_history[round_].values())
|
||||
difference = current_values - previous_round
|
||||
else:
|
||||
difference = set(self.pin_history[round_].values())
|
||||
|
||||
log.debug("\n")
|
||||
log.debug("{:=^30}".format(" Round {0} ".format(round_)))
|
||||
log.debug("\n")
|
||||
if difference:
|
||||
log.debug("New Packages: ")
|
||||
for d in difference:
|
||||
log.debug("{:>30}".format(format_requirement(d)))
|
||||
elif round_ >= 3:
|
||||
log.debug("Stable Pins: ")
|
||||
for d in current_values:
|
||||
log.debug("{:>30}".format(format_requirement(d)))
|
||||
return
|
||||
else:
|
||||
log.debug("No New Packages.")
|
||||
# TODO: Raise a better error.
|
||||
raise RuntimeError("cannot resolve after {} rounds".format(max_rounds))
|
||||
|
||||
def get_hashes(self):
|
||||
for dep in self.pinned_deps.values():
|
||||
if dep.name not in self.hashes:
|
||||
self.hashes[dep.name] = self.get_hashes_for_one(dep)
|
||||
return self.hashes.copy()
|
||||
|
||||
def get_hashes_for_one(self, ireq):
|
||||
if not self.finder:
|
||||
finder_args = []
|
||||
if self.allow_prereleases:
|
||||
finder_args.append('--pre')
|
||||
self.finder = get_finder(*finder_args)
|
||||
|
||||
if ireq.editable:
|
||||
return set()
|
||||
|
||||
vcs = VcsSupport()
|
||||
if ireq.link and ireq.link.scheme in vcs.all_schemes and 'ssh' in ireq.link.scheme:
|
||||
return set()
|
||||
|
||||
if not is_pinned_requirement(ireq):
|
||||
raise TypeError(
|
||||
"Expected pinned requirement, got {}".format(ireq))
|
||||
|
||||
matching_candidates = set()
|
||||
with self.allow_all_wheels():
|
||||
matching_candidates = (
|
||||
find_all_matches(self.finder, ireq, pre=self.allow_prereleases)
|
||||
)
|
||||
|
||||
return {
|
||||
self.hash_cache.get_hash(candidate.location)
|
||||
for candidate in matching_candidates
|
||||
}
|
||||
|
||||
@contextmanager
|
||||
def allow_all_wheels(self):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
def _wheel_supported(self, tags=None):
|
||||
# Ignore current platform. Support everything.
|
||||
return True
|
||||
|
||||
def _wheel_support_index_min(self, tags=None):
|
||||
# All wheels are equal priority for sorting.
|
||||
return 0
|
||||
|
||||
original_wheel_supported = Wheel.supported
|
||||
original_support_index_min = Wheel.support_index_min
|
||||
|
||||
Wheel.supported = _wheel_supported
|
||||
Wheel.support_index_min = _wheel_support_index_min
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
Wheel.supported = original_wheel_supported
|
||||
Wheel.support_index_min = original_support_index_min
|
||||
+337
-13
@@ -1,10 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from collections import defaultdict
|
||||
from itertools import chain, groupby
|
||||
from operator import attrgetter
|
||||
|
||||
import six
|
||||
|
||||
from attr import validators
|
||||
from first import first
|
||||
from .._compat import Link
|
||||
from packaging.markers import InvalidMarker, Marker, Op, Value, Variable
|
||||
from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet
|
||||
from packaging.version import parse as parse_version
|
||||
from packaging.requirements import Requirement as PackagingRequirement
|
||||
from pkg_resources import Requirement
|
||||
|
||||
from vistir.misc import dedup
|
||||
from pip_shims.shims import InstallRequirement, Link
|
||||
|
||||
from ..utils import SCHEME_LIST, VCS_LIST, is_star
|
||||
|
||||
|
||||
@@ -21,6 +37,15 @@ def optional_instance_of(cls):
|
||||
return validators.optional(validators.instance_of(cls))
|
||||
|
||||
|
||||
def init_requirement(name):
|
||||
req = Requirement.parse(name)
|
||||
req.vcs = None
|
||||
req.local_file = None
|
||||
req.revision = None
|
||||
req.path = None
|
||||
return req
|
||||
|
||||
|
||||
def extras_to_string(extras):
|
||||
"""Turn a list of extras into a string"""
|
||||
if isinstance(extras, six.string_types):
|
||||
@@ -29,16 +54,13 @@ def extras_to_string(extras):
|
||||
|
||||
else:
|
||||
extras = [extras]
|
||||
return "[{0}]".format(",".join(extras))
|
||||
return "[{0}]".format(",".join(sorted(extras)))
|
||||
|
||||
|
||||
def parse_extras(extras_str):
|
||||
"""Turn a string of extras into a parsed extras list"""
|
||||
import requirements
|
||||
extras = first(
|
||||
requirements.parse("fakepkg{0}".format(extras_to_string(extras_str)))
|
||||
).extras
|
||||
return extras
|
||||
extras = Requirement.parse("fakepkg{0}".format(extras_to_string(extras_str))).extras
|
||||
return sorted(dedup([extra.lower() for extra in extras]))
|
||||
|
||||
|
||||
def specs_to_string(specs):
|
||||
@@ -46,7 +68,11 @@ def specs_to_string(specs):
|
||||
if specs:
|
||||
if isinstance(specs, six.string_types):
|
||||
return specs
|
||||
return ",".join(["".join(spec) for spec in specs])
|
||||
try:
|
||||
extras = ",".join(["".join(spec) for spec in specs])
|
||||
except TypeError:
|
||||
extras = ",".join(["".join(spec._spec) for spec in specs])
|
||||
return extras
|
||||
return ""
|
||||
|
||||
|
||||
@@ -91,7 +117,7 @@ def strip_ssh_from_git_uri(uri):
|
||||
|
||||
|
||||
def add_ssh_scheme_to_git_uri(uri):
|
||||
"""Cleans VCS uris from pip format"""
|
||||
"""Cleans VCS uris from pipenv.patched.notpip format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
# Add scheme for parsing purposes, this is also what pip does
|
||||
if uri.startswith("git+") and "://" not in uri:
|
||||
@@ -101,7 +127,6 @@ def add_ssh_scheme_to_git_uri(uri):
|
||||
|
||||
def split_markers_from_line(line):
|
||||
"""Split markers from a dependency"""
|
||||
from packaging.markers import Marker, InvalidMarker
|
||||
if not any(line.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
|
||||
marker_sep = ";"
|
||||
else:
|
||||
@@ -133,7 +158,6 @@ def validate_path(instance, attr_, value):
|
||||
|
||||
|
||||
def validate_markers(instance, attr_, value):
|
||||
from packaging.markers import Marker, InvalidMarker
|
||||
try:
|
||||
Marker("{0}{1}".format(attr_.name, value))
|
||||
except InvalidMarker:
|
||||
@@ -141,11 +165,311 @@ def validate_markers(instance, attr_, value):
|
||||
|
||||
|
||||
def validate_specifiers(instance, attr_, value):
|
||||
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||
from packaging.markers import InvalidMarker
|
||||
if value == "":
|
||||
return True
|
||||
try:
|
||||
SpecifierSet(value)
|
||||
except (InvalidMarker, InvalidSpecifier):
|
||||
raise ValueError("Invalid Specifiers {0}".format(value))
|
||||
|
||||
|
||||
def key_from_ireq(ireq):
|
||||
"""Get a standardized key for an InstallRequirement."""
|
||||
if ireq.req is None and ireq.link is not None:
|
||||
return str(ireq.link)
|
||||
else:
|
||||
return key_from_req(ireq.req)
|
||||
|
||||
|
||||
def key_from_req(req):
|
||||
"""Get an all-lowercase version of the requirement's name."""
|
||||
if hasattr(req, 'key'):
|
||||
# from pkg_resources, such as installed dists for pip-sync
|
||||
key = req.key
|
||||
else:
|
||||
# from packaging, such as install requirements from requirements.txt
|
||||
key = req.name
|
||||
|
||||
key = key.replace('_', '-').lower()
|
||||
return key
|
||||
|
||||
|
||||
def _requirement_to_str_lowercase_name(requirement):
|
||||
"""
|
||||
Formats a packaging.requirements.Requirement with a lowercase name.
|
||||
|
||||
This is simply a copy of
|
||||
https://github.com/pypa/packaging/blob/16.8/packaging/requirements.py#L109-L124
|
||||
modified to lowercase the dependency name.
|
||||
|
||||
Previously, we were invoking the original Requirement.__str__ method and
|
||||
lowercasing the entire result, which would lowercase the name, *and* other,
|
||||
important stuff that should not be lowercased (such as the marker). See
|
||||
this issue for more information: https://github.com/pypa/pipenv/issues/2113.
|
||||
"""
|
||||
parts = [requirement.name.lower()]
|
||||
|
||||
if requirement.extras:
|
||||
parts.append("[{0}]".format(",".join(sorted(requirement.extras))))
|
||||
|
||||
if requirement.specifier:
|
||||
parts.append(str(requirement.specifier))
|
||||
|
||||
if requirement.url:
|
||||
parts.append("@ {0}".format(requirement.url))
|
||||
|
||||
if requirement.marker:
|
||||
parts.append("; {0}".format(requirement.marker))
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def format_requirement(ireq):
|
||||
"""
|
||||
Generic formatter for pretty printing InstallRequirements to the terminal
|
||||
in a less verbose way than using its `__str__` method.
|
||||
"""
|
||||
if ireq.editable:
|
||||
line = '-e {}'.format(ireq.link)
|
||||
else:
|
||||
line = _requirement_to_str_lowercase_name(ireq.req)
|
||||
|
||||
if str(ireq.req.marker) != str(ireq.markers):
|
||||
if not ireq.req.marker:
|
||||
line = '{}; {}'.format(line, ireq.markers)
|
||||
else:
|
||||
name, markers = line.split(";", 1)
|
||||
markers = markers.strip()
|
||||
line = '{}; ({}) and ({})'.format(name, markers, ireq.markers)
|
||||
|
||||
return line
|
||||
|
||||
|
||||
def format_specifier(ireq):
|
||||
"""
|
||||
Generic formatter for pretty printing the specifier part of
|
||||
InstallRequirements to the terminal.
|
||||
"""
|
||||
# TODO: Ideally, this is carried over to the pip library itself
|
||||
specs = ireq.specifier._specs if ireq.req is not None else []
|
||||
specs = sorted(specs, key=lambda x: x._spec[1])
|
||||
return ','.join(str(s) for s in specs) or '<any>'
|
||||
|
||||
|
||||
def is_pinned_requirement(ireq):
|
||||
"""
|
||||
Returns whether an InstallRequirement is a "pinned" requirement.
|
||||
|
||||
An InstallRequirement is considered pinned if:
|
||||
|
||||
- Is not editable
|
||||
- It has exactly one specifier
|
||||
- That specifier is "=="
|
||||
- The version does not contain a wildcard
|
||||
|
||||
Examples:
|
||||
django==1.8 # pinned
|
||||
django>1.8 # NOT pinned
|
||||
django~=1.8 # NOT pinned
|
||||
django==1.* # NOT pinned
|
||||
"""
|
||||
if ireq.editable:
|
||||
return False
|
||||
|
||||
specifier = getattr(ireq, "specifier", None)
|
||||
if not specifier:
|
||||
return False
|
||||
if len(specifier._specs) != 1:
|
||||
return False
|
||||
|
||||
op, version = first(specifier._specs)._spec
|
||||
return (op == '==' or op == '===') and not version.endswith('.*')
|
||||
|
||||
|
||||
def as_tuple(ireq):
|
||||
"""
|
||||
Pulls out the (name: str, version:str, extras:(str)) tuple from the pinned InstallRequirement.
|
||||
"""
|
||||
if not is_pinned_requirement(ireq):
|
||||
raise TypeError('Expected a pinned InstallRequirement, got {}'.format(ireq))
|
||||
|
||||
name = key_from_req(ireq.req)
|
||||
version = first(ireq.specifier._specs)._spec[1]
|
||||
extras = tuple(sorted(ireq.extras))
|
||||
return name, version, extras
|
||||
|
||||
|
||||
def full_groupby(iterable, key=None):
|
||||
"""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"""
|
||||
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.
|
||||
|
||||
Supports building normal and unique lookup tables. For example:
|
||||
|
||||
>>> assert lookup_table(
|
||||
... ['foo', 'bar', 'baz', 'qux', 'quux'], lambda s: s[0]) == {
|
||||
... 'b': {'bar', 'baz'},
|
||||
... 'f': {'foo'},
|
||||
... 'q': {'quux', 'qux'}
|
||||
... }
|
||||
|
||||
For key functions that uniquely identify values, set unique=True:
|
||||
|
||||
>>> assert lookup_table(
|
||||
... ['foo', 'bar', 'baz', 'qux', 'quux'], lambda s: s[0],
|
||||
... unique=True) == {
|
||||
... 'b': 'baz',
|
||||
... 'f': 'foo',
|
||||
... 'q': 'quux'
|
||||
... }
|
||||
|
||||
The values of the resulting lookup table will be values, not sets.
|
||||
|
||||
For extra power, you can even change the values while building up the LUT.
|
||||
To do so, use the `keyval` function instead of the `key` arg:
|
||||
|
||||
>>> assert lookup_table(
|
||||
... ['foo', 'bar', 'baz', 'qux', 'quux'],
|
||||
... keyval=lambda s: (s[0], s[1:])) == {
|
||||
... 'b': {'ar', 'az'},
|
||||
... 'f': {'oo'},
|
||||
... 'q': {'uux', 'ux'}
|
||||
... }
|
||||
|
||||
"""
|
||||
if keyval is None:
|
||||
if key is None:
|
||||
keyval = (lambda v: v)
|
||||
else:
|
||||
keyval = (lambda v: (key(v), v))
|
||||
|
||||
if unique:
|
||||
return dict(keyval(v) for v in values)
|
||||
|
||||
lut = {}
|
||||
for value in values:
|
||||
k, v = keyval(value)
|
||||
try:
|
||||
s = lut[k]
|
||||
except KeyError:
|
||||
if use_lists:
|
||||
s = lut[k] = list()
|
||||
else:
|
||||
s = lut[k] = set()
|
||||
if use_lists:
|
||||
s.append(v)
|
||||
else:
|
||||
s.add(v)
|
||||
return dict(lut)
|
||||
|
||||
|
||||
def name_from_req(req):
|
||||
"""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
|
||||
else:
|
||||
# from packaging, such as install requirements from requirements.txt
|
||||
return req.name
|
||||
|
||||
|
||||
def make_install_requirement(name, version, extras, markers, constraint=False):
|
||||
"""make_install_requirement Generates an :class:`~pip._internal.req.req_install.InstallRequirement`.
|
||||
|
||||
Create an InstallRequirement from the supplied metadata.
|
||||
|
||||
:param name: The requirement's name.
|
||||
:type name: str
|
||||
:param version: The requirement version (must be pinned).
|
||||
:type version: str.
|
||||
:param extras: The desired extras.
|
||||
:type extras: list[str]
|
||||
:param markers: The desired markers, without a preceding semicolon.
|
||||
:type markers: str
|
||||
:param constraint: Whether to flag the requirement as a constraint, defaults to False.
|
||||
:param constraint: bool, optional
|
||||
:return: A generated InstallRequirement
|
||||
:rtype: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
"""
|
||||
|
||||
# If no extras are specified, the extras string is blank
|
||||
extras_string = ""
|
||||
if extras:
|
||||
# Sort extras for stability
|
||||
extras_string = "[{}]".format(",".join(sorted(extras)))
|
||||
|
||||
if not markers:
|
||||
return InstallRequirement.from_line(
|
||||
str('{}{}=={}'.format(name, extras_string, version)),
|
||||
constraint=constraint)
|
||||
else:
|
||||
return InstallRequirement.from_line(
|
||||
str('{}{}=={}; {}'.format(name, extras_string, version, str(markers))),
|
||||
constraint=constraint)
|
||||
|
||||
|
||||
def version_from_ireq(ireq):
|
||||
"""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`
|
||||
:return: The version of the InstallRequirement.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return first(ireq.specifier._specs).version
|
||||
|
||||
|
||||
def clean_requires_python(candidates):
|
||||
"""Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes."""
|
||||
all_candidates = []
|
||||
sys_version = '.'.join(map(str, sys.version_info[:3]))
|
||||
py_version = parse_version(os.environ.get('PIP_PYTHON_VERSION', sys_version))
|
||||
for c in candidates:
|
||||
from_location = attrgetter("location.requires_python")
|
||||
requires_python = getattr(c, "requires_python", from_location(c))
|
||||
if requires_python:
|
||||
# Old specifications had people setting this to single digits
|
||||
# which is effectively the same as '>=digit,<digit+1'
|
||||
if requires_python.isdigit():
|
||||
requires_python = '>={0},<{1}'.format(requires_python, int(requires_python) + 1)
|
||||
try:
|
||||
specifierset = SpecifierSet(requires_python)
|
||||
except InvalidSpecifier:
|
||||
continue
|
||||
else:
|
||||
if not specifierset.contains(py_version):
|
||||
continue
|
||||
all_candidates.append(c)
|
||||
return all_candidates
|
||||
|
||||
|
||||
def fix_requires_python_marker(requires_python):
|
||||
marker_str = ''
|
||||
if any(requires_python.startswith(op) for op in Specifier._operators.keys()):
|
||||
spec_dict = defaultdict(set)
|
||||
# We are checking first if we have leading specifier operator
|
||||
# if not, we can assume we should be doing a == comparison
|
||||
specifierset = list(SpecifierSet(requires_python))
|
||||
# for multiple specifiers, the correct way to represent that in
|
||||
# a specifierset is `Requirement('fakepkg; python_version<"3.0,>=2.6"')`
|
||||
marker_key = Variable('python_version')
|
||||
for spec in specifierset:
|
||||
operator, val = spec._spec
|
||||
cleaned_val = Value(val).serialize().replace('"', "")
|
||||
spec_dict[Op(operator).serialize()].add(cleaned_val)
|
||||
marker_str = ' and '.join([
|
||||
"{0}{1}'{2}'".format(marker_key.serialize(), op, ','.join(vals))
|
||||
for op, vals in spec_dict.items()
|
||||
])
|
||||
marker_to_add = PackagingRequirement('fakepkg; {0}'.format(marker_str)).marker
|
||||
return marker_to_add
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import attr
|
||||
from pip_shims import VcsSupport
|
||||
import os
|
||||
|
||||
|
||||
VCS_SUPPORT = VcsSupport()
|
||||
|
||||
|
||||
@attr.s
|
||||
class VCSRepository(object):
|
||||
url = attr.ib()
|
||||
name = attr.ib()
|
||||
checkout_directory = attr.ib()
|
||||
vcs_type = attr.ib()
|
||||
commit_sha = attr.ib(default=None)
|
||||
ref = attr.ib(default=None)
|
||||
repo_instance = attr.ib()
|
||||
|
||||
@repo_instance.default
|
||||
def get_repo_instance(self):
|
||||
backend = VCS_SUPPORT._registry.get(self.vcs_type)
|
||||
return backend(url=self.url)
|
||||
|
||||
def obtain(self):
|
||||
if not os.path.exists(self.checkout_directory):
|
||||
self.repo_instance.obtain(self.checkout_directory)
|
||||
if self.ref:
|
||||
self.checkout_ref(self.ref)
|
||||
self.commit_sha = self.get_commit_hash(self.ref)
|
||||
else:
|
||||
self.ref = self.repo_instance.default_arg_rev
|
||||
if not self.commit_sha:
|
||||
self.commit_sha = self.get_commit_hash()
|
||||
|
||||
def checkout_ref(self, ref):
|
||||
target_rev = self.repo_instance.make_rev_options(ref)
|
||||
if not self.repo_instance.is_commit_id_equal(
|
||||
self.checkout_directory, self.get_commit_hash(ref)
|
||||
) and not self.repo_instance.is_commit_id_equal(self.checkout_directory, ref):
|
||||
self.repo_instance.switch(self.checkout_directory, self.url, target_rev)
|
||||
|
||||
def update(self, ref):
|
||||
target_rev = self.repo_instance.make_rev_options(ref)
|
||||
self.repo_instance.update(self.checkout_directory, target_rev)
|
||||
|
||||
def get_commit_hash(self, ref=None):
|
||||
return self.repo_instance.get_revision(self.checkout_directory)
|
||||
+45
-92
@@ -1,25 +1,28 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
|
||||
import six
|
||||
|
||||
from itertools import product
|
||||
from six.moves.urllib.parse import urlparse, urlsplit
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
from pip_shims import (
|
||||
Command, VcsSupport, cmdoptions, is_archive_file, is_installable_dir
|
||||
)
|
||||
from vistir.compat import Path
|
||||
from vistir.path import is_valid_url, ensure_mkdir_p
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
VCS_ACCESS = VcsSupport()
|
||||
VCS_LIST = ("git", "svn", "hg", "bzr")
|
||||
VCS_SCHEMES = []
|
||||
SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://")
|
||||
|
||||
if not VCS_SCHEMES:
|
||||
VCS_SCHEMES = VcsSupport().all_schemes
|
||||
|
||||
|
||||
def setup_logger():
|
||||
logger = logging.getLogger("requirementslib")
|
||||
@@ -40,74 +43,14 @@ def is_vcs(pipfile_entry):
|
||||
return any(key for key in pipfile_entry.keys() if key in VCS_LIST)
|
||||
|
||||
elif isinstance(pipfile_entry, six.string_types):
|
||||
vcs_starts = product(
|
||||
("git+", "hg+", "svn+", "bzr+"),
|
||||
("file", "ssh", "https", "http", "svn", "sftp", ""),
|
||||
)
|
||||
|
||||
return next(
|
||||
(
|
||||
v
|
||||
for v in (
|
||||
pipfile_entry.startswith("{0}{1}".format(vcs, scheme))
|
||||
for vcs, scheme in vcs_starts
|
||||
)
|
||||
if v
|
||||
),
|
||||
False,
|
||||
)
|
||||
|
||||
if not is_valid_url(pipfile_entry) and pipfile_entry.startswith("git+"):
|
||||
from .models.utils import add_ssh_scheme_to_git_uri
|
||||
pipfile_entry = add_ssh_scheme_to_git_uri(pipfile_entry)
|
||||
parsed_entry = urlsplit(pipfile_entry)
|
||||
return parsed_entry.scheme in VCS_SCHEMES
|
||||
return False
|
||||
|
||||
|
||||
def check_for_unc_path(path):
|
||||
""" Checks to see if a pathlib `Path` object is a unc path or not"""
|
||||
if (
|
||||
os.name == "nt"
|
||||
and len(path.drive) > 2
|
||||
and not path.drive[0].isalpha()
|
||||
and path.drive[1] != ":"
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_converted_relative_path(path, relative_to=os.curdir):
|
||||
"""Convert `path` to be relative.
|
||||
|
||||
Given a vague relative path, return the path relative to the given
|
||||
location.
|
||||
|
||||
This performs additional conversion to ensure the result is of POSIX form,
|
||||
and starts with `./`, or is precisely `.`.
|
||||
"""
|
||||
|
||||
start_path = Path(relative_to)
|
||||
try:
|
||||
start = start_path.resolve()
|
||||
except OSError:
|
||||
start = start_path.absolute()
|
||||
|
||||
# check if there is a drive letter or mount point
|
||||
# if it is a mountpoint use the original absolute path
|
||||
# instead of the unc path
|
||||
if check_for_unc_path(start):
|
||||
start = start_path.absolute()
|
||||
|
||||
path = start.joinpath(path).relative_to(start)
|
||||
|
||||
# check and see if the path that was passed into the function is a UNC path
|
||||
# and raise value error if it is not.
|
||||
if check_for_unc_path(path):
|
||||
raise ValueError("The path argument does not currently accept UNC paths")
|
||||
|
||||
relpath_s = posixpath.normpath(path.as_posix())
|
||||
if not (relpath_s == "." or relpath_s.startswith("./")):
|
||||
relpath_s = posixpath.join(".", relpath_s)
|
||||
return relpath_s
|
||||
|
||||
|
||||
def multi_split(s, split):
|
||||
"""Splits on multiple given separators."""
|
||||
for r in split:
|
||||
@@ -121,7 +64,6 @@ def is_star(val):
|
||||
|
||||
def is_installable_file(path):
|
||||
"""Determine if a path can potentially be installed"""
|
||||
from ._compat import is_installable_dir, is_archive_file
|
||||
from packaging import specifiers
|
||||
|
||||
if hasattr(path, "keys") and any(
|
||||
@@ -160,22 +102,6 @@ def is_installable_file(path):
|
||||
return False
|
||||
|
||||
|
||||
def is_valid_url(url):
|
||||
"""Checks if a given string is an url"""
|
||||
pieces = urlparse(url)
|
||||
return all([pieces.scheme, any([pieces.netloc, pieces.path])])
|
||||
|
||||
|
||||
def pep423_name(name):
|
||||
"""Normalize package name to PEP 423 style standard."""
|
||||
name = name.lower()
|
||||
if any(i not in name for i in (VCS_LIST + SCHEME_LIST)):
|
||||
return name.replace("_", "-")
|
||||
|
||||
else:
|
||||
return name
|
||||
|
||||
|
||||
def prepare_pip_source_args(sources, pip_args=None):
|
||||
if pip_args is None:
|
||||
pip_args = []
|
||||
@@ -197,3 +123,30 @@ def prepare_pip_source_args(sources, pip_args=None):
|
||||
["--trusted-host", urlparse(source["url"]).hostname]
|
||||
)
|
||||
return pip_args
|
||||
|
||||
|
||||
class PipCommand(Command):
|
||||
name = 'PipCommand'
|
||||
|
||||
|
||||
def get_pip_command():
|
||||
# Use pip's parser for pip.conf management and defaults.
|
||||
# General options (find_links, index_url, extra_index_url, trusted_host,
|
||||
# and pre) are defered to pip.
|
||||
import optparse
|
||||
pip_command = PipCommand()
|
||||
pip_command.parser.add_option(cmdoptions.no_binary())
|
||||
pip_command.parser.add_option(cmdoptions.only_binary())
|
||||
index_opts = cmdoptions.make_option_group(
|
||||
cmdoptions.index_group,
|
||||
pip_command.parser,
|
||||
)
|
||||
pip_command.parser.insert_option_group(0, index_opts)
|
||||
pip_command.parser.add_option(optparse.Option('--pre', action='store_true', default=False))
|
||||
|
||||
return pip_command
|
||||
|
||||
|
||||
@ensure_mkdir_p(mode=0o777)
|
||||
def _ensure_dir(path):
|
||||
return path
|
||||
|
||||
Reference in New Issue
Block a user