mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
+1
-2
@@ -20,7 +20,7 @@ except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
from .cmdparse import Script
|
||||
from .vendor.requirementslib.requirements import Requirement
|
||||
from .vendor.requirementslib import Requirement
|
||||
from .utils import (
|
||||
atomic_open_for_write,
|
||||
mkdir_p,
|
||||
@@ -46,7 +46,6 @@ from .environments import (
|
||||
PIPENV_PYTHON,
|
||||
PIPENV_DEFAULT_PYTHON_VERSION,
|
||||
)
|
||||
from .vendor.first import first
|
||||
|
||||
|
||||
def _normalized(p):
|
||||
|
||||
+5
-8
@@ -248,10 +248,10 @@ def actually_resolve_deps(
|
||||
dep, url = dep.split(' -i ')
|
||||
req = Requirement.from_line(dep)
|
||||
|
||||
# req.as_line() is theoratically the same as dep, but is guarenteed to
|
||||
# req.as_line() is theoratically the same as dep, but is guaranteed to
|
||||
# be normalized. This is safer than passing in dep.
|
||||
# TODO: Stop passing dep lines around; just use requirement objects.
|
||||
constraints.append(req.as_line())
|
||||
constraints.append(req.as_line(sources=None))
|
||||
# extra_constraints = []
|
||||
|
||||
if url:
|
||||
@@ -509,12 +509,9 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False):
|
||||
dependencies = []
|
||||
for dep_name, dep in deps.items():
|
||||
indexes = project.sources if hasattr(project, 'sources') else None
|
||||
if hasattr(dep, 'keys') and dep.get('index'):
|
||||
indexes = project.get_source(dep['index'])
|
||||
new_dep = Requirement.from_pipfile(dep_name, indexes, dep)
|
||||
new_dep = Requirement.from_pipfile(dep_name, dep)
|
||||
req = new_dep.as_line(
|
||||
project=project,
|
||||
include_index=include_index
|
||||
sources=indexes if include_index else None
|
||||
).strip()
|
||||
dependencies.append(req)
|
||||
if not r:
|
||||
@@ -1191,7 +1188,7 @@ def get_vcs_deps(
|
||||
|
||||
pipfile_name = vcs_uri_map[_vcs_match]["name"]
|
||||
pipfile_rev = vcs_uri_map[_vcs_match]["ref"]
|
||||
pipfile_req = Requirement.from_pipfile(pipfile_name, [], packages[pipfile_name])
|
||||
pipfile_req = Requirement.from_pipfile(pipfile_name, packages[pipfile_name])
|
||||
names = {pipfile_name}
|
||||
backend = vcs_registry()._registry.get(pipfile_req.vcs)
|
||||
# TODO: Why doesn't pip freeze list 'git+git://' formatted urls?
|
||||
|
||||
+4
-2
@@ -1,4 +1,6 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
__version__ = "0.0.7.dev0"
|
||||
__version__ = "0.2.0"
|
||||
|
||||
from .requirements import Requirement
|
||||
|
||||
from .exceptions import RequirementError
|
||||
from .models import Requirement, Lockfile, Pipfile
|
||||
|
||||
+21
-1
@@ -1,6 +1,25 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
# -*- 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):
|
||||
@@ -38,3 +57,4 @@ get_installed_distributions = do_import(
|
||||
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")
|
||||
PyPI = do_import("models.index", "PyPI")
|
||||
make_abstract_dist = do_import("operations.prepare", "make_abstract_dist", old_path="req.req_set")
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Taken from pip: https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/src/pip/_vendor/Makefile
|
||||
all: clean vendor
|
||||
|
||||
clean:
|
||||
@# Delete vendored items
|
||||
find . -maxdepth 1 -mindepth 1 -type d -exec rm -rf {} \;
|
||||
|
||||
vendor:
|
||||
@# Install vendored libraries
|
||||
pip install -t . -r vendor.txt
|
||||
|
||||
@# Cleanup .egg-info directories
|
||||
rm -rf *.egg-info
|
||||
rm -rf *.dist-info
|
||||
@@ -0,0 +1 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
@@ -0,0 +1,3 @@
|
||||
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.
|
||||
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,23 @@
|
||||
Copyright (c) Kenneth Reitz and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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__
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
@@ -0,0 +1,230 @@
|
||||
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)
|
||||
@@ -0,0 +1 @@
|
||||
pipfile
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
class RequirementError(Exception):
|
||||
pass
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
__all__ = ["Requirement", "Lockfile", "Pipfile", "RequirementError"]
|
||||
|
||||
|
||||
from .requirements import Requirement
|
||||
from .lockfile import Lockfile
|
||||
from .pipfile import Pipfile
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import abc
|
||||
import attr
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseRequirement:
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
"""Returns a requirement from a requirements.txt or pip-compatible line"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def line_part(self):
|
||||
"""Returns the current requirement as a pip-compatible line"""
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
"""Returns a requirement from a pipfile entry"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def pipfile_part(self):
|
||||
"""Returns the current requirement as a pipfile entry"""
|
||||
|
||||
@classmethod
|
||||
def attr_fields(cls):
|
||||
return [field.name for field in attr.fields(cls)]
|
||||
@@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import attr
|
||||
import json
|
||||
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=list)
|
||||
requirements = attr.ib(default=list)
|
||||
path = attr.ib(default=None, validator=optional_instance_of(Path))
|
||||
pipfile_hash = attr.ib(default=None)
|
||||
|
||||
@classmethod
|
||||
def create(cls, project_path, lockfile_name="Pipfile.lock"):
|
||||
"""Create a new lockfile instance
|
||||
|
||||
:param project_path: Path to the project root
|
||||
:type project_path: str or :class:`~pathlib.Path`
|
||||
:returns: List[:class:`~requirementslib.Requirement`] objects
|
||||
"""
|
||||
|
||||
if not isinstance(project_path, Path):
|
||||
project_path = Path(project_path)
|
||||
lockfile_path = project_path / lockfile_name
|
||||
requirements = []
|
||||
dev_requirements = []
|
||||
if not lockfile_path.exists():
|
||||
raise FileNotFoundError("No such lockfile: %s" % lockfile_path)
|
||||
|
||||
lockfile = json.loads(lockfile_path.read_text(encoding="utf-8"))
|
||||
for k in lockfile["develop"].keys():
|
||||
dev_requirements.append(Requirement.from_pipfile(k, lockfile["develop"][k]))
|
||||
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,
|
||||
)
|
||||
|
||||
def as_requirements(self, include_hashes=False, dev=False):
|
||||
"""Returns a list of requirements in pip-style format"""
|
||||
lines = []
|
||||
section = self.dev_requirements if dev else self.requirements
|
||||
for req in section:
|
||||
r = req.as_line()
|
||||
if not include_hashes:
|
||||
r = r.split("--hash", 1)[0]
|
||||
lines.append(r.strip())
|
||||
return lines
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
# -*- 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 ..exceptions import RequirementError
|
||||
|
||||
|
||||
@attr.s
|
||||
class PipenvMarkers(BaseRequirement):
|
||||
"""System-level requirements - see PEP508 for more detail"""
|
||||
|
||||
os_name = attr.ib(default=None, validator=attr.validators.optional(validate_markers))
|
||||
sys_platform = attr.ib(default=None, validator=attr.validators.optional(validate_markers))
|
||||
platform_machine = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
platform_python_implementation = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
platform_release = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
platform_system = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
platform_version = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
python_version = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
python_full_version = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
implementation_name = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
implementation_version = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
|
||||
@property
|
||||
def line_part(self):
|
||||
return " and ".join(
|
||||
[
|
||||
"{0} {1}".format(k, v)
|
||||
for k, v in attr.asdict(self, filter=filter_none).items()
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def pipfile_part(self):
|
||||
return {"markers": self.as_line}
|
||||
|
||||
@classmethod
|
||||
def make_marker(cls, marker_string):
|
||||
try:
|
||||
marker = Marker(marker_string)
|
||||
except InvalidMarker:
|
||||
raise RequirementError("Invalid requirement: Invalid marker %r" % marker_string)
|
||||
marker_dict = {}
|
||||
for m in marker._markers:
|
||||
if isinstance(m, six.string_types):
|
||||
continue
|
||||
var, op, val = m
|
||||
if var.value in cls.attr_fields():
|
||||
marker_dict[var.value] = '{0} "{1}"'.format(op, val)
|
||||
return marker_dict
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
if ";" in line:
|
||||
line = line.rsplit(";", 1)[1].strip()
|
||||
marker_dict = cls.make_marker(line)
|
||||
return cls(**marker_dict)
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
found_keys = [k for k in pipfile.keys() if k in cls.attr_fields()]
|
||||
marker_strings = ["{0} {1}".format(k, pipfile[k]) for k in found_keys]
|
||||
if pipfile.get("markers"):
|
||||
marker_strings.append(pipfile.get("markers"))
|
||||
markers = {}
|
||||
for marker in marker_strings:
|
||||
marker_dict = cls.make_marker(marker)
|
||||
if marker_dict:
|
||||
markers.update(marker_dict)
|
||||
return cls(**markers)
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import attr
|
||||
import contoml
|
||||
import os
|
||||
import toml
|
||||
from .._vendor import pipfile
|
||||
from .requirements import Requirement
|
||||
from .utils import optional_instance_of, filter_none
|
||||
from .._compat import Path, FileNotFoundError
|
||||
from ..exceptions import RequirementError
|
||||
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def requires_python(self):
|
||||
return self.requires.requires_python
|
||||
|
||||
@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)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
if not isinstance(path, Path):
|
||||
path = Path(path)
|
||||
pipfile_path = path / 'Pipfile'
|
||||
if not path.exists():
|
||||
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)
|
||||
|
||||
@staticmethod
|
||||
def get_section(pf_dict, section):
|
||||
"""Get section objects from a pipfile dictionary
|
||||
|
||||
: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)
|
||||
pipenv/vendor/requirementslib/requirements.py → pipenv/vendor/requirementslib/models/requirements.py
Vendored
+195
-361
@@ -1,19 +1,42 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import abc
|
||||
import sys
|
||||
import attr
|
||||
import hashlib
|
||||
import os
|
||||
import requirements
|
||||
import six
|
||||
from attr import attrs, attrib, Factory, validators
|
||||
import attr
|
||||
from ._compat import Link, path_to_url, _strip_extras, InstallRequirement, Wheel
|
||||
from distlib.markers import Evaluator
|
||||
from packaging.markers import Marker, InvalidMarker
|
||||
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||
from first import first
|
||||
from pkg_resources import RequirementParseError
|
||||
from .baserequirement import BaseRequirement
|
||||
from .markers import PipenvMarkers
|
||||
from .utils import (
|
||||
SCHEME_LIST,
|
||||
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,
|
||||
)
|
||||
from .._compat import (
|
||||
Link,
|
||||
path_to_url,
|
||||
_strip_extras,
|
||||
InstallRequirement,
|
||||
Path,
|
||||
urlparse,
|
||||
unquote,
|
||||
Wheel,
|
||||
FileNotFoundError,
|
||||
)
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import (
|
||||
VCS_LIST,
|
||||
is_installable_file,
|
||||
is_vcs,
|
||||
@@ -21,232 +44,31 @@ from .utils import (
|
||||
pep423_name,
|
||||
get_converted_relative_path,
|
||||
multi_split,
|
||||
is_star,
|
||||
)
|
||||
from first import first
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
|
||||
HASH_STRING = " --hash={0}"
|
||||
|
||||
|
||||
def _strip_ssh_from_git_uri(uri):
|
||||
"""Return git+ssh:// formatted URI to git+git@ format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
uri = uri.replace("git+ssh://", "git+")
|
||||
return uri
|
||||
|
||||
|
||||
def _clean_git_uri(uri):
|
||||
"""Cleans VCS uris from pip9 format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
# Add scheme for parsing purposes, this is also what pip does
|
||||
if uri.startswith("git+") and "://" not in uri:
|
||||
uri = uri.replace("git+", "git+ssh://")
|
||||
return uri
|
||||
|
||||
|
||||
def _split_markers(line):
|
||||
"""Split markers from a dependency"""
|
||||
if not any(line.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
|
||||
marker_sep = ";"
|
||||
else:
|
||||
marker_sep = "; "
|
||||
markers = None
|
||||
if marker_sep in line:
|
||||
line, markers = line.split(marker_sep, 1)
|
||||
markers = markers.strip() if markers else None
|
||||
return line, markers
|
||||
|
||||
|
||||
def _split_vcs_method(uri):
|
||||
"""Split a vcs+uri formatted uri into (vcs, uri)"""
|
||||
vcs_start = "{0}+"
|
||||
vcs = first([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))])
|
||||
if vcs:
|
||||
vcs, uri = uri.split("+", 1)
|
||||
return vcs, uri
|
||||
|
||||
|
||||
def _validate_vcs(instance, attr_, value):
|
||||
if value not in VCS_LIST:
|
||||
raise ValueError("Invalid vcs {0!r}".format(value))
|
||||
|
||||
|
||||
def _validate_path(instance, attr_, value):
|
||||
if not os.path.exists(value):
|
||||
raise ValueError("Invalid path {0!r}", format(value))
|
||||
|
||||
|
||||
def _validate_markers(instance, attr_, value):
|
||||
try:
|
||||
Marker("{0}{1}".format(attr_.name, value))
|
||||
except InvalidMarker:
|
||||
raise ValueError("Invalid Marker {0}{1}".format(attr_, value))
|
||||
|
||||
|
||||
def _validate_specifiers(instance, attr_, value):
|
||||
if value == "":
|
||||
return True
|
||||
try:
|
||||
SpecifierSet(value)
|
||||
except InvalidMarker:
|
||||
raise ValueError("Invalid Specifiers {0}".format(value))
|
||||
|
||||
|
||||
def _filter_none(k, v):
|
||||
if v:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _optional_instance_of(cls):
|
||||
return validators.optional(validators.instance_of(cls))
|
||||
|
||||
|
||||
@attrs
|
||||
class Source(object):
|
||||
# : URL to PyPI instance
|
||||
url = attrib(default="")
|
||||
# : If False, skip SSL checks
|
||||
verify_ssl = attrib(
|
||||
default=True, validator=validators.optional(validators.instance_of(bool))
|
||||
)
|
||||
# : human name to refer to this source (can be referenced in packages or dev-packages)
|
||||
name = attrib(default="")
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseRequirement():
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
"""Returns a requirement from a requirements.txt or pip-compatible line"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def line_part(self):
|
||||
"""Returns the current requirement as a pip-compatible line"""
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
"""Returns a requirement from a pipfile entry"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def pipfile_part(self):
|
||||
"""Returns the current requirement as a pipfile entry"""
|
||||
|
||||
@classmethod
|
||||
def attr_fields(cls):
|
||||
return [field.name for field in attr.fields(cls)]
|
||||
|
||||
|
||||
@attrs
|
||||
class PipenvMarkers(BaseRequirement):
|
||||
"""System-level requirements - see PEP508 for more detail"""
|
||||
os_name = attrib(default=None, validator=validators.optional(_validate_markers))
|
||||
sys_platform = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
platform_machine = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
platform_python_implementation = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
platform_release = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
platform_system = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
platform_version = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
python_version = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
python_full_version = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
implementation_name = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
implementation_version = attrib(
|
||||
default=None, validator=validators.optional(_validate_markers)
|
||||
)
|
||||
|
||||
@property
|
||||
def line_part(self):
|
||||
return " and ".join(
|
||||
[
|
||||
"{0} {1}".format(k, v)
|
||||
for k, v in attr.asdict(self, filter=_filter_none).items()
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def pipfile_part(self):
|
||||
return {"markers": self.as_line}
|
||||
|
||||
@classmethod
|
||||
def make_marker(cls, marker_string):
|
||||
marker = Marker(marker_string)
|
||||
marker_dict = {}
|
||||
for m in marker._markers:
|
||||
if isinstance(m, six.string_types):
|
||||
continue
|
||||
var, op, val = m
|
||||
if var.value in cls.attr_fields():
|
||||
marker_dict[var.value] = '{0} "{1}"'.format(op, val)
|
||||
return marker_dict
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
if ";" in line:
|
||||
line = line.rsplit(";", 1)[1].strip()
|
||||
marker_dict = cls.make_marker(line)
|
||||
return cls(**marker_dict)
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
found_keys = [k for k in pipfile.keys() if k in cls.attr_fields()]
|
||||
marker_strings = ["{0} {1}".format(k, pipfile[k]) for k in found_keys]
|
||||
if pipfile.get("markers"):
|
||||
marker_strings.append(pipfile.get("markers"))
|
||||
markers = {}
|
||||
for marker in marker_strings:
|
||||
marker_dict = cls.make_marker(marker)
|
||||
if marker_dict:
|
||||
markers.update(marker_dict)
|
||||
return cls(**markers)
|
||||
|
||||
|
||||
@attrs
|
||||
@attr.s
|
||||
class NamedRequirement(BaseRequirement):
|
||||
name = attrib()
|
||||
version = attrib(validator=validators.optional(_validate_specifiers))
|
||||
req = attrib()
|
||||
name = attr.ib()
|
||||
version = attr.ib(validator=attr.validators.optional(validate_specifiers))
|
||||
req = attr.ib()
|
||||
|
||||
@req.default
|
||||
def get_requirement(self):
|
||||
return first(requirements.parse("{0}{1}".format(self.name, self.version)))
|
||||
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)
|
||||
)
|
||||
return req
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
req = first(requirements.parse(line))
|
||||
specifiers = None
|
||||
if req.specifier:
|
||||
specifiers = _specs_to_string(req.specs)
|
||||
specifiers = specs_to_string(req.specs)
|
||||
return cls(name=req.name, version=specifiers, req=req)
|
||||
|
||||
@classmethod
|
||||
@@ -255,7 +77,7 @@ class NamedRequirement(BaseRequirement):
|
||||
if hasattr(pipfile, "keys"):
|
||||
creation_args = {k: v for k, v in pipfile.items() if k in cls.attr_fields()}
|
||||
creation_args["name"] = name
|
||||
version = _get_version(pipfile)
|
||||
version = get_version(pipfile)
|
||||
creation_args["version"] = version
|
||||
creation_args["req"] = first(requirements.parse("{0}{1}".format(name, version)))
|
||||
return cls(**creation_args)
|
||||
@@ -266,24 +88,26 @@ class NamedRequirement(BaseRequirement):
|
||||
|
||||
@property
|
||||
def pipfile_part(self):
|
||||
pipfile_dict = attr.asdict(self, filter=_filter_none).copy()
|
||||
pipfile_dict = attr.asdict(self, filter=filter_none).copy()
|
||||
if "version" not in pipfile_dict:
|
||||
pipfile_dict["version"] = "*"
|
||||
name = pipfile_dict.pop("name")
|
||||
return {name: pipfile_dict}
|
||||
|
||||
|
||||
@attrs
|
||||
@attr.s
|
||||
class FileRequirement(BaseRequirement):
|
||||
"""File requirements for tar.gz installable files or wheels or setup.py
|
||||
containing directories."""
|
||||
path = attrib(default=None, validator=validators.optional(_validate_path))
|
||||
|
||||
setup_path = attr.ib(default=None)
|
||||
path = attr.ib(default=None, validator=attr.validators.optional(validate_path))
|
||||
# : path to hit - without any of the VCS prefixes (like git+ / http+ / etc)
|
||||
uri = attrib()
|
||||
name = attrib()
|
||||
link = attrib()
|
||||
editable = attrib(default=None)
|
||||
req = attrib()
|
||||
editable = attr.ib(default=None)
|
||||
uri = attr.ib()
|
||||
link = attr.ib()
|
||||
name = attr.ib()
|
||||
req = attr.ib()
|
||||
_has_hashed_name = False
|
||||
_uri_scheme = None
|
||||
|
||||
@@ -298,17 +122,50 @@ class FileRequirement(BaseRequirement):
|
||||
loc = self.path or self.uri
|
||||
if loc:
|
||||
self._uri_scheme = "path" if self.path else "uri"
|
||||
name = None
|
||||
if self.link and self.link.egg_fragment:
|
||||
return self.link.egg_fragment
|
||||
elif self.link and self.link.is_wheel:
|
||||
return os.path.basename(Wheel(self.link.path).name)
|
||||
if self._uri_scheme != "uri" and self.path and self.setup_path:
|
||||
from distutils.core import run_setup
|
||||
|
||||
try:
|
||||
dist = run_setup(self.setup_path.as_posix(), stop_after="init")
|
||||
name = dist.get_name()
|
||||
except (FileNotFoundError, IOError) as e:
|
||||
dist = None
|
||||
except (NameError, RuntimeError) as e:
|
||||
from .._compat import InstallRequirement, make_abstract_dist
|
||||
|
||||
try:
|
||||
if not isinstance(Path, self.path):
|
||||
_path = Path(self.path)
|
||||
else:
|
||||
_path = self.path
|
||||
if self.editable:
|
||||
_ireq = InstallRequirement.from_editable(_path.as_uri())
|
||||
else:
|
||||
_ireq = InstallRequirement.from_line(_path.as_posix())
|
||||
dist = make_abstract_dist(_ireq).get_dist()
|
||||
name = dist.project_name
|
||||
except (TypeError, ValueError, AttributeError) as e:
|
||||
dist = None
|
||||
hashed_loc = hashlib.sha256(loc.encode("utf-8")).hexdigest()
|
||||
hash_fragment = hashed_loc[-7:]
|
||||
self._has_hashed_name = True
|
||||
return hash_fragment
|
||||
hashed_name = hashed_loc[-7:]
|
||||
if not name or name == "UNKNOWN":
|
||||
self._has_hashed_name = True
|
||||
name = hashed_name
|
||||
if self.link and not self._has_hashed_name:
|
||||
self.link = Link("{0}#egg={1}".format(self.link.url, name))
|
||||
return name
|
||||
|
||||
@link.default
|
||||
def get_link(self):
|
||||
target = "{0}#egg={1}".format(self.uri, self.name)
|
||||
target = "{0}".format(self.uri)
|
||||
if hasattr(self, "name"):
|
||||
target = "{0}#egg={1}".format(target, self.name)
|
||||
link = Link(target)
|
||||
if link.is_wheel and self._has_hashed_name:
|
||||
self.name = os.path.basename(Wheel(link.path).name)
|
||||
return link
|
||||
|
||||
@req.default
|
||||
@@ -328,21 +185,25 @@ class FileRequirement(BaseRequirement):
|
||||
|
||||
@property
|
||||
def is_remote_artifact(self):
|
||||
return any(
|
||||
self.link.scheme.startswith(scheme)
|
||||
for scheme in ("http", "https", "ftp", "ftps", "uri")
|
||||
) and (
|
||||
self.link.is_artifact or self.link.is_wheel
|
||||
) and not self.req.editable
|
||||
return (
|
||||
any(
|
||||
self.link.scheme.startswith(scheme)
|
||||
for scheme in ("http", "https", "ftp", "ftps", "uri")
|
||||
)
|
||||
and (self.link.is_artifact or self.link.is_wheel)
|
||||
and not self.req.editable
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
line = line.strip('"').strip("'")
|
||||
link = None
|
||||
path = None
|
||||
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)]):
|
||||
raise ValueError(
|
||||
raise RequirementError(
|
||||
"Supplied requirement is not installable: {0!r}".format(line)
|
||||
)
|
||||
|
||||
@@ -351,15 +212,18 @@ class FileRequirement(BaseRequirement):
|
||||
else:
|
||||
if is_valid_url(line):
|
||||
parsed = urlparse(line)
|
||||
link = Link('{0}'.format(line))
|
||||
link = Link("{0}".format(line))
|
||||
if parsed.scheme == "file":
|
||||
path = Path(parsed.path).absolute().as_posix()
|
||||
path = Path(parsed.path)
|
||||
setup_path = path / "setup.py"
|
||||
path = path.absolute().as_posix()
|
||||
if get_converted_relative_path(path) == ".":
|
||||
path = "."
|
||||
line = path
|
||||
else:
|
||||
_path = Path(line)
|
||||
link = Link(_path.absolute().as_uri())
|
||||
setup_path = _path / "setup.py"
|
||||
link = Link(unquote(_path.absolute().as_uri()))
|
||||
if _path.is_absolute() or _path.as_posix() == ".":
|
||||
path = _path.as_posix()
|
||||
else:
|
||||
@@ -369,6 +233,7 @@ class FileRequirement(BaseRequirement):
|
||||
"uri": link.url_without_fragment,
|
||||
"link": link,
|
||||
"editable": editable,
|
||||
"setup_path": setup_path,
|
||||
}
|
||||
if link.egg_fragment:
|
||||
arg_dict["name"] = link.egg_fragment
|
||||
@@ -382,11 +247,11 @@ class FileRequirement(BaseRequirement):
|
||||
if not uri_key:
|
||||
abs_path = os.path.abspath(uri)
|
||||
uri = path_to_url(abs_path) if os.path.exists(abs_path) else None
|
||||
link = Link(uri) if uri else None
|
||||
link = Link(unquote(uri)) if uri else None
|
||||
arg_dict = {
|
||||
"name": name,
|
||||
"path": pipfile.get("path"),
|
||||
"uri": link.url_without_fragment,
|
||||
"uri": unquote(link.url_without_fragment if link else uri),
|
||||
"editable": pipfile.get("editable"),
|
||||
"link": link,
|
||||
}
|
||||
@@ -403,8 +268,10 @@ class FileRequirement(BaseRequirement):
|
||||
|
||||
@property
|
||||
def pipfile_part(self):
|
||||
pipfile_dict = {k: v for k, v in attr.asdict(self, filter=_filter_none).items()}
|
||||
pipfile_dict = {k: v for k, v in attr.asdict(self, filter=filter_none).items()}
|
||||
name = pipfile_dict.pop("name")
|
||||
if "setup_path" in pipfile_dict:
|
||||
pipfile_dict.pop("setup_path")
|
||||
req = self.req
|
||||
# For local paths and remote installable artifacts (zipfiles, etc)
|
||||
if self.is_remote_artifact:
|
||||
@@ -419,31 +286,39 @@ class FileRequirement(BaseRequirement):
|
||||
collisions = [key for key in ["path", "uri", "file"] if key in pipfile_dict]
|
||||
if len(collisions) > 1:
|
||||
for k in collisions[1:]:
|
||||
_ = pipfile_dict.pop(k)
|
||||
pipfile_dict.pop(k)
|
||||
return {name: pipfile_dict}
|
||||
|
||||
|
||||
@attrs
|
||||
@attr.s
|
||||
class VCSRequirement(FileRequirement):
|
||||
editable = attrib(default=None)
|
||||
uri = attrib(default=None)
|
||||
path = attrib(default=None, validator=validators.optional(_validate_path))
|
||||
vcs = attrib(validator=validators.optional(_validate_vcs), default=None)
|
||||
editable = attr.ib(default=None)
|
||||
uri = attr.ib(default=None)
|
||||
path = attr.ib(default=None, validator=attr.validators.optional(validate_path))
|
||||
vcs = attr.ib(validator=attr.validators.optional(validate_vcs), default=None)
|
||||
# : vcs reference name (branch / commit / tag)
|
||||
ref = attrib(default=None)
|
||||
subdirectory = attrib(default=None)
|
||||
name = attrib()
|
||||
link = attrib()
|
||||
req = attrib()
|
||||
ref = attr.ib(default=None)
|
||||
subdirectory = attr.ib(default=None)
|
||||
name = attr.ib()
|
||||
link = attr.ib()
|
||||
req = attr.ib()
|
||||
_INCLUDE_FIELDS = (
|
||||
"editable", "uri", "path", "vcs", "ref", "subdirectory", "name", "link", "req"
|
||||
"editable",
|
||||
"uri",
|
||||
"path",
|
||||
"vcs",
|
||||
"ref",
|
||||
"subdirectory",
|
||||
"name",
|
||||
"link",
|
||||
"req",
|
||||
)
|
||||
|
||||
@link.default
|
||||
def get_link(self):
|
||||
return build_vcs_link(
|
||||
self.vcs,
|
||||
_clean_git_uri(self.uri),
|
||||
add_ssh_scheme_to_git_uri(self.uri),
|
||||
name=self.name,
|
||||
ref=self.ref,
|
||||
subdirectory=self.subdirectory,
|
||||
@@ -451,7 +326,11 @@ class VCSRequirement(FileRequirement):
|
||||
|
||||
@name.default
|
||||
def get_name(self):
|
||||
return self.link.egg_fragment or self.req.name if self.req else ""
|
||||
return (
|
||||
self.link.egg_fragment or self.req.name
|
||||
if self.req
|
||||
else super(VCSRequirement, self).get_name()
|
||||
)
|
||||
|
||||
@property
|
||||
def vcs_uri(self):
|
||||
@@ -476,8 +355,8 @@ class VCSRequirement(FileRequirement):
|
||||
and "git+ssh://" in self.link.url
|
||||
and "git+git@" in self.uri
|
||||
):
|
||||
req.line = _strip_ssh_from_git_uri(req.line)
|
||||
req.uri = _strip_ssh_from_git_uri(req.uri)
|
||||
req.line = strip_ssh_from_git_uri(req.line)
|
||||
req.uri = strip_ssh_from_git_uri(req.uri)
|
||||
if not req.name:
|
||||
raise ValueError(
|
||||
"pipenv requires an #egg fragment for version controlled "
|
||||
@@ -502,11 +381,9 @@ class VCSRequirement(FileRequirement):
|
||||
for key in pipfile_keys:
|
||||
if key in VCS_LIST:
|
||||
creation_args["vcs"] = key
|
||||
composed_uri = _clean_git_uri(
|
||||
composed_uri = add_ssh_scheme_to_git_uri(
|
||||
"{0}+{1}".format(key, pipfile.get(key))
|
||||
).lstrip(
|
||||
"{0}+".format(key)
|
||||
)
|
||||
).lstrip("{0}+".format(key))
|
||||
is_url = is_valid_url(pipfile.get(key)) or is_valid_url(composed_uri)
|
||||
target_key = "uri" if is_url else "path"
|
||||
creation_args[target_key] = pipfile.get(key)
|
||||
@@ -521,8 +398,8 @@ class VCSRequirement(FileRequirement):
|
||||
if line.startswith("-e "):
|
||||
editable = True
|
||||
line = line.split(" ", 1)[1]
|
||||
vcs_line = _clean_git_uri(line)
|
||||
vcs_method, vcs_location = _split_vcs_method(vcs_line)
|
||||
vcs_line = add_ssh_scheme_to_git_uri(line)
|
||||
vcs_method, vcs_location = split_vcs_method_from_uri(vcs_line)
|
||||
if not is_valid_url(vcs_location) and os.path.exists(vcs_location):
|
||||
path = get_converted_relative_path(vcs_location)
|
||||
vcs_location = path_to_url(os.path.abspath(vcs_location))
|
||||
@@ -530,7 +407,7 @@ class VCSRequirement(FileRequirement):
|
||||
name = link.egg_fragment
|
||||
uri = link.url_without_fragment
|
||||
if "git+git@" in line:
|
||||
uri = _strip_ssh_from_git_uri(uri)
|
||||
uri = strip_ssh_from_git_uri(uri)
|
||||
subdirectory = link.subdirectory_fragment
|
||||
ref = None
|
||||
if "@" in link.show_url:
|
||||
@@ -562,32 +439,32 @@ class VCSRequirement(FileRequirement):
|
||||
if src_keys:
|
||||
chosen_key = first(src_keys)
|
||||
vcs_type = pipfile.pop("vcs")
|
||||
_, pipfile_url = _split_vcs_method(pipfile.get(chosen_key))
|
||||
_, pipfile_url = split_vcs_method_from_uri(pipfile.get(chosen_key))
|
||||
pipfile[vcs_type] = pipfile_url
|
||||
for removed in src_keys:
|
||||
_ = pipfile.pop(removed)
|
||||
pipfile.pop(removed)
|
||||
return pipfile
|
||||
|
||||
@property
|
||||
def pipfile_part(self):
|
||||
pipfile_dict = attr.asdict(self, filter=_filter_none).copy()
|
||||
pipfile_dict = attr.asdict(self, filter=filter_none).copy()
|
||||
if "vcs" in pipfile_dict:
|
||||
pipfile_dict = self._choose_vcs_source(pipfile_dict)
|
||||
name = pipfile_dict.pop("name")
|
||||
return {name: pipfile_dict}
|
||||
|
||||
|
||||
@attrs
|
||||
@attr.s
|
||||
class Requirement(object):
|
||||
name = attrib()
|
||||
vcs = attrib(default=None, validator=validators.optional(_validate_vcs))
|
||||
req = attrib(default=None, validator=_optional_instance_of(BaseRequirement))
|
||||
markers = attrib(default=None)
|
||||
specifiers = attrib(validator=validators.optional(_validate_specifiers))
|
||||
index = attrib(default=None)
|
||||
editable = attrib(default=None)
|
||||
hashes = attrib(default=Factory(list), converter=list)
|
||||
extras = attrib(default=Factory(list))
|
||||
name = attr.ib()
|
||||
vcs = attr.ib(default=None, validator=attr.validators.optional(validate_vcs))
|
||||
req = attr.ib(default=None, validator=optional_instance_of(BaseRequirement))
|
||||
markers = attr.ib(default=None)
|
||||
specifiers = attr.ib(validator=attr.validators.optional(validate_specifiers))
|
||||
index = attr.ib(default=None)
|
||||
editable = attr.ib(default=None)
|
||||
hashes = attr.ib(default=attr.Factory(list), converter=list)
|
||||
extras = attr.ib(default=attr.Factory(list))
|
||||
_ireq = None
|
||||
_INCLUDE_FIELDS = ("name", "markers", "index", "editable", "hashes", "extras")
|
||||
|
||||
@@ -623,7 +500,7 @@ class Requirement(object):
|
||||
@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.specs)
|
||||
return
|
||||
|
||||
@property
|
||||
@@ -640,9 +517,7 @@ class Requirement(object):
|
||||
|
||||
@property
|
||||
def normalized_name(self):
|
||||
if not self.is_vcs and not self.is_file_or_url:
|
||||
return pep423_name(self.name)
|
||||
return self.name
|
||||
return pep423_name(self.name)
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
@@ -651,14 +526,15 @@ class Requirement(object):
|
||||
hashes = line.split(" --hash=")
|
||||
line, hashes = hashes[0], hashes[1:]
|
||||
editable = line.startswith("-e ")
|
||||
stripped_line = line.split(" ", 1)[1] if editable else line
|
||||
line, markers = _split_markers(line)
|
||||
line, markers = split_markers_from_line(line)
|
||||
line, extras = _strip_extras(line)
|
||||
stripped_line = line.split(" ", 1)[1] if editable else line
|
||||
vcs = None
|
||||
# Installable local files and installable non-vcs urls are handled
|
||||
# as files, generally speaking
|
||||
if (
|
||||
is_installable_file(stripped_line)
|
||||
or is_installable_file(line)
|
||||
or (is_valid_url(stripped_line) and not is_vcs(stripped_line))
|
||||
):
|
||||
r = FileRequirement.from_line(line)
|
||||
@@ -672,7 +548,7 @@ class Requirement(object):
|
||||
r = NamedRequirement.from_line(stripped_line)
|
||||
if extras:
|
||||
extras = first(
|
||||
requirements.parse("fakepkg{0}".format(_extras_to_string(extras)))
|
||||
requirements.parse("fakepkg{0}".format(extras_to_string(extras)))
|
||||
).extras
|
||||
r.req.extras = extras
|
||||
if markers:
|
||||
@@ -691,11 +567,11 @@ class Requirement(object):
|
||||
return cls(**args)
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, indexes, pipfile):
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
_pipfile = {}
|
||||
if hasattr(pipfile, "keys"):
|
||||
_pipfile = dict(pipfile).copy()
|
||||
_pipfile["version"] = _get_version(pipfile)
|
||||
_pipfile["version"] = get_version(pipfile)
|
||||
vcs = first([vcs for vcs in VCS_LIST if vcs in _pipfile])
|
||||
if vcs:
|
||||
_pipfile["vcs"] = vcs
|
||||
@@ -717,7 +593,15 @@ class Requirement(object):
|
||||
args["hashes"] = _pipfile.get("hashes", [pipfile.get("hash")])
|
||||
return cls(**args)
|
||||
|
||||
def as_line(self, include_index=False, project=None):
|
||||
def as_line(self, sources=None):
|
||||
"""Format this requirement as a line in requirements.txt.
|
||||
|
||||
If `sources` provided, it should be an sequence of mappings, containing
|
||||
all possible sources to be used for this requirement.
|
||||
|
||||
If `sources` is omitted or falsy, no index information will be included
|
||||
in the requirement line.
|
||||
"""
|
||||
line = "{0}{1}{2}{3}{4}".format(
|
||||
self.req.line_part,
|
||||
self.extras_as_pip,
|
||||
@@ -725,24 +609,27 @@ class Requirement(object):
|
||||
self.markers_as_pip,
|
||||
self.hashes_as_pip,
|
||||
)
|
||||
if include_index and not (self.requirement.local_file or self.vcs):
|
||||
from .utils import prepare_pip_source_args
|
||||
if sources and not (self.requirement.local_file or self.vcs):
|
||||
from ..utils import prepare_pip_source_args
|
||||
|
||||
if self.index:
|
||||
pip_src_args = [project.get_source(self.index)]
|
||||
else:
|
||||
pip_src_args = project.sources
|
||||
index_string = " ".join(prepare_pip_source_args(pip_src_args))
|
||||
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)
|
||||
return line
|
||||
|
||||
def as_pipfile(self, include_index=False):
|
||||
def as_pipfile(self):
|
||||
good_keys = (
|
||||
"hashes", "extras", "markers", "editable", "version", "index"
|
||||
"hashes",
|
||||
"extras",
|
||||
"markers",
|
||||
"editable",
|
||||
"version",
|
||||
"index",
|
||||
) + VCS_LIST
|
||||
req_dict = {
|
||||
k: v
|
||||
for k, v in attr.asdict(self, recurse=False, filter=_filter_none).items()
|
||||
for k, v in attr.asdict(self, recurse=False, filter=filter_none).items()
|
||||
if k in good_keys
|
||||
}
|
||||
name = self.name
|
||||
@@ -756,7 +643,7 @@ class Requirement(object):
|
||||
if "file" in base_dict and any(k in base_dict for k in conflicting_keys[1:]):
|
||||
conflicts = [k for k in (conflicting_keys[1:],) if k in base_dict]
|
||||
for k in conflicts:
|
||||
_ = base_dict.pop(k)
|
||||
base_dict.pop(k)
|
||||
if "hashes" in base_dict and len(base_dict["hashes"]) == 1:
|
||||
base_dict["hash"] = base_dict.pop("hashes")[0]
|
||||
if len(base_dict.keys()) == 1 and "version" in base_dict:
|
||||
@@ -777,56 +664,3 @@ class Requirement(object):
|
||||
else:
|
||||
self._ireq = InstallRequirement.from_line(ireq_line)
|
||||
return self._ireq
|
||||
|
||||
|
||||
def _extras_to_string(extras):
|
||||
"""Turn a list of extras into a string"""
|
||||
if isinstance(extras, six.string_types):
|
||||
if extras.startswith("["):
|
||||
return extras
|
||||
|
||||
else:
|
||||
extras = [extras]
|
||||
return "[{0}]".format(",".join(extras))
|
||||
|
||||
|
||||
def _specs_to_string(specs):
|
||||
"""Turn a list of specifier tuples into a string"""
|
||||
if specs:
|
||||
if isinstance(specs, six.string_types):
|
||||
return specs
|
||||
return ",".join(["".join(spec) for spec in specs])
|
||||
return ""
|
||||
|
||||
|
||||
def build_vcs_link(vcs, uri, name=None, ref=None, subdirectory=None, extras=None):
|
||||
if extras is None:
|
||||
extras = []
|
||||
vcs_start = "{0}+".format(vcs)
|
||||
if not uri.startswith(vcs_start):
|
||||
uri = "{0}{1}".format(vcs_start, uri)
|
||||
uri = _clean_git_uri(uri)
|
||||
if ref:
|
||||
uri = "{0}@{1}".format(uri, ref)
|
||||
if name:
|
||||
uri = "{0}#egg={1}".format(uri, name)
|
||||
if extras:
|
||||
extras = _extras_to_string(extras)
|
||||
uri = "{0}{1}".format(uri, extras)
|
||||
if subdirectory:
|
||||
uri = "{0}&subdirectory={1}".format(uri, subdirectory)
|
||||
return Link(uri)
|
||||
|
||||
|
||||
def _get_version(pipfile_entry):
|
||||
if str(pipfile_entry) == "{}" or is_star(pipfile_entry):
|
||||
return ""
|
||||
|
||||
elif hasattr(pipfile_entry, "keys") and "version" in pipfile_entry:
|
||||
if is_star(pipfile_entry.get("version")):
|
||||
return ""
|
||||
return pipfile_entry.get("version", "")
|
||||
|
||||
if isinstance(pipfile_entry, six.string_types):
|
||||
return pipfile_entry
|
||||
return ""
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import os
|
||||
import six
|
||||
from attr import validators
|
||||
from first import first
|
||||
from packaging.markers import Marker, InvalidMarker
|
||||
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||
from .._compat import Link
|
||||
from ..utils import (
|
||||
SCHEME_LIST,
|
||||
VCS_LIST,
|
||||
is_star,
|
||||
)
|
||||
|
||||
|
||||
HASH_STRING = " --hash={0}"
|
||||
|
||||
|
||||
def filter_none(k, v):
|
||||
if v:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def optional_instance_of(cls):
|
||||
return validators.optional(validators.instance_of(cls))
|
||||
|
||||
|
||||
def extras_to_string(extras):
|
||||
"""Turn a list of extras into a string"""
|
||||
if isinstance(extras, six.string_types):
|
||||
if extras.startswith("["):
|
||||
return extras
|
||||
|
||||
else:
|
||||
extras = [extras]
|
||||
return "[{0}]".format(",".join(extras))
|
||||
|
||||
|
||||
def specs_to_string(specs):
|
||||
"""Turn a list of specifier tuples into a string"""
|
||||
if specs:
|
||||
if isinstance(specs, six.string_types):
|
||||
return specs
|
||||
return ",".join(["".join(spec) for spec in specs])
|
||||
return ""
|
||||
|
||||
|
||||
def build_vcs_link(vcs, uri, name=None, ref=None, subdirectory=None, extras=None):
|
||||
if extras is None:
|
||||
extras = []
|
||||
vcs_start = "{0}+".format(vcs)
|
||||
if not uri.startswith(vcs_start):
|
||||
uri = "{0}{1}".format(vcs_start, uri)
|
||||
uri = add_ssh_scheme_to_git_uri(uri)
|
||||
if ref:
|
||||
uri = "{0}@{1}".format(uri, ref)
|
||||
if name:
|
||||
uri = "{0}#egg={1}".format(uri, name)
|
||||
if extras:
|
||||
extras = extras_to_string(extras)
|
||||
uri = "{0}{1}".format(uri, extras)
|
||||
if subdirectory:
|
||||
uri = "{0}&subdirectory={1}".format(uri, subdirectory)
|
||||
return Link(uri)
|
||||
|
||||
|
||||
def get_version(pipfile_entry):
|
||||
if str(pipfile_entry) == "{}" or is_star(pipfile_entry):
|
||||
return ""
|
||||
|
||||
elif hasattr(pipfile_entry, "keys") and "version" in pipfile_entry:
|
||||
if is_star(pipfile_entry.get("version")):
|
||||
return ""
|
||||
return pipfile_entry.get("version", "")
|
||||
|
||||
if isinstance(pipfile_entry, six.string_types):
|
||||
return pipfile_entry
|
||||
return ""
|
||||
|
||||
|
||||
def strip_ssh_from_git_uri(uri):
|
||||
"""Return git+ssh:// formatted URI to git+git@ format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
uri = uri.replace("git+ssh://", "git+")
|
||||
return uri
|
||||
|
||||
|
||||
def add_ssh_scheme_to_git_uri(uri):
|
||||
"""Cleans VCS uris from pip format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
# Add scheme for parsing purposes, this is also what pip does
|
||||
if uri.startswith("git+") and "://" not in uri:
|
||||
uri = uri.replace("git+", "git+ssh://")
|
||||
return uri
|
||||
|
||||
|
||||
def split_markers_from_line(line):
|
||||
"""Split markers from a dependency"""
|
||||
if not any(line.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
|
||||
marker_sep = ";"
|
||||
else:
|
||||
marker_sep = "; "
|
||||
markers = None
|
||||
if marker_sep in line:
|
||||
line, markers = line.split(marker_sep, 1)
|
||||
markers = markers.strip() if markers else None
|
||||
return line, markers
|
||||
|
||||
|
||||
def split_vcs_method_from_uri(uri):
|
||||
"""Split a vcs+uri formatted uri into (vcs, uri)"""
|
||||
vcs_start = "{0}+"
|
||||
vcs = first([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))])
|
||||
if vcs:
|
||||
vcs, uri = uri.split("+", 1)
|
||||
return vcs, uri
|
||||
|
||||
|
||||
def validate_vcs(instance, attr_, value):
|
||||
if value not in VCS_LIST:
|
||||
raise ValueError("Invalid vcs {0!r}".format(value))
|
||||
|
||||
|
||||
def validate_path(instance, attr_, value):
|
||||
if not os.path.exists(value):
|
||||
raise ValueError("Invalid path {0!r}", format(value))
|
||||
|
||||
|
||||
def validate_markers(instance, attr_, value):
|
||||
try:
|
||||
Marker("{0}{1}".format(attr_.name, value))
|
||||
except InvalidMarker:
|
||||
raise ValueError("Invalid Marker {0}{1}".format(attr_, value))
|
||||
|
||||
|
||||
def validate_specifiers(instance, attr_, value):
|
||||
if value == "":
|
||||
return True
|
||||
try:
|
||||
SpecifierSet(value)
|
||||
except (InvalidMarker, InvalidSpecifier):
|
||||
raise ValueError("Invalid Specifiers {0}".format(value))
|
||||
+26
-21
@@ -1,5 +1,6 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
import os
|
||||
import six
|
||||
|
||||
@@ -17,9 +18,22 @@ VCS_LIST = ("git", "svn", "hg", "bzr")
|
||||
SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://")
|
||||
|
||||
|
||||
def setup_logger():
|
||||
logger = logging.getLogger("requirementslib")
|
||||
loglevel = logging.DEBUG
|
||||
handler = logging.StreamHandler()
|
||||
handler.setLevel(loglevel)
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(loglevel)
|
||||
return logger
|
||||
|
||||
|
||||
log = setup_logger()
|
||||
|
||||
|
||||
def is_vcs(pipfile_entry):
|
||||
import requirements
|
||||
from .requirements import _clean_git_uri
|
||||
from .models.utils import add_ssh_scheme_to_git_uri
|
||||
|
||||
"""Determine if dictionary entry from Pipfile is for a vcs dependency."""
|
||||
if hasattr(pipfile_entry, "keys"):
|
||||
@@ -27,7 +41,7 @@ def is_vcs(pipfile_entry):
|
||||
|
||||
elif isinstance(pipfile_entry, six.string_types):
|
||||
return bool(
|
||||
requirements.requirement.VCS_REGEX.match(_clean_git_uri(pipfile_entry))
|
||||
requirements.requirement.VCS_REGEX.match(add_ssh_scheme_to_git_uri(pipfile_entry))
|
||||
)
|
||||
|
||||
return False
|
||||
@@ -36,7 +50,7 @@ def is_vcs(pipfile_entry):
|
||||
def get_converted_relative_path(path, relative_to=os.curdir):
|
||||
"""Given a vague relative path, return the path relative to the given location"""
|
||||
relpath = os.path.relpath(path, start=relative_to)
|
||||
if os.name == 'nt':
|
||||
if os.name == "nt":
|
||||
return os.altsep.join([".", relpath])
|
||||
return os.path.join(".", relpath)
|
||||
|
||||
@@ -57,9 +71,8 @@ def is_installable_file(path):
|
||||
from ._compat import is_installable_dir, is_archive_file
|
||||
from packaging import specifiers
|
||||
|
||||
if (
|
||||
hasattr(path, "keys")
|
||||
and any(key for key in path.keys() if key in ["file", "path"])
|
||||
if hasattr(path, "keys") and any(
|
||||
key for key in path.keys() if key in ["file", "path"]
|
||||
):
|
||||
path = urlparse(path["file"]).path if "file" in path else path["path"]
|
||||
if not isinstance(path, six.string_types) or path == "*":
|
||||
@@ -77,7 +90,7 @@ def is_installable_file(path):
|
||||
return False
|
||||
|
||||
parsed = urlparse(path)
|
||||
if parsed.scheme == 'file':
|
||||
if parsed.scheme == "file":
|
||||
path = parsed.path
|
||||
|
||||
if not os.path.exists(os.path.abspath(path)):
|
||||
@@ -115,25 +128,17 @@ def prepare_pip_source_args(sources, pip_args=None):
|
||||
pip_args = []
|
||||
if sources:
|
||||
# Add the source to pip9.
|
||||
pip_args.extend(['-i', sources[0]['url']])
|
||||
pip_args.extend(["-i", sources[0]["url"]])
|
||||
# Trust the host if it's not verified.
|
||||
if not sources[0].get('verify_ssl', True):
|
||||
if not sources[0].get("verify_ssl", True):
|
||||
pip_args.extend(
|
||||
[
|
||||
'--trusted-host',
|
||||
urlparse(sources[0]['url']).netloc.split(':')[0],
|
||||
]
|
||||
["--trusted-host", urlparse(sources[0]["url"]).netloc.split(":")[0]]
|
||||
)
|
||||
# Add additional sources as extra indexes.
|
||||
if len(sources) > 1:
|
||||
for source in sources[1:]:
|
||||
pip_args.extend(['--extra-index-url', source['url']])
|
||||
pip_args.extend(["--extra-index-url", source["url"]])
|
||||
# Trust the host if it's not verified.
|
||||
if not source.get('verify_ssl', True):
|
||||
pip_args.extend(
|
||||
[
|
||||
'--trusted-host',
|
||||
urlparse(source['url']).hostname,
|
||||
]
|
||||
)
|
||||
if not source.get("verify_ssl", True):
|
||||
pip_args.extend(["--trusted-host", urlparse(source["url"]).hostname])
|
||||
return pip_args
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user