Skip to content

Commit

Permalink
Change: drop setuptools, pkg_resources. Don't use pip internal for gl…
Browse files Browse the repository at this point in the history
…obal scripts folder (#33)

* Change: drop setuptools

* Change: to_version_tuple -> packaging.version.parse

* Drop setuptools

* Bump dependencies

* Change: don't use pip internal for global scripts folder

* Fix: use utf8 encoding

* Upgrade python version

* Update doc
  • Loading branch information
eight04 committed Jan 5, 2022
1 parent ad6bc73 commit 5d22024
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 82 deletions.
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ disable=
bad-whitespace,

anomalous-backslash-in-string,
consider-using-f-string,
duplicate-code,
global-statement,
inconsistent-return-statements,
Expand Down
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
language: python
python:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "nightly"
install:
- pip install -r requirements-lock.txt
- pip install .
Expand Down
11 changes: 6 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ vpip
:alt: PyPI
:target: https://pypi.org/project/vpip

..
``vpip`` = `venv <https://docs.python.org/3/library/venv.html>`_ + `pipm <https://github.com/jnoortheen/pipm>`_

A CLI which aims to provide an ``npm``-like experience when installing Python packages.

Features
Expand Down Expand Up @@ -82,6 +78,11 @@ Documentation

https://vpip.readthedocs.io/en/latest/index.html

Similar projects
----------------

* `pipm <https://github.com/jnoortheen/pipm>`_ - which doesn't use virtualenv.

Changelog
---------

Expand Down Expand Up @@ -132,4 +133,4 @@ Changelog
* 0.1.0 (Nov 13, 2018)

- First release


2 changes: 1 addition & 1 deletion docs/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ link
vpip link
Link console scripts installed in the local venv to the global Scripts folder so they can be invoked without activating the venv.
Link console scripts installed in the local venv to the global Scripts folder so they can be invoked without activating the venv. See :func:`vpip.venv.get_global_script_folders`

This command checks the ``console_scripts`` in ``setup.cfg`` to decide which commands should be linked.

Expand Down
38 changes: 23 additions & 15 deletions requirements-lock.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
alabaster==0.7.12
astroid==2.3.2
astroid==2.9.2
atomicwrites==1.3.0
attrs==19.3.0
Babel==2.7.0
bleach==3.1.0
case-conversion==2.1.0
certifi==2019.9.11
chardet==3.0.4
colorama==0.4.1
ConfigUpdater==1.0.1
charset-normalizer==2.0.10
colorama==0.4.4
ConfigUpdater==3.0.1
docutils==0.15.2
idna==2.8
imagesize==1.1.0
importlib-metadata==4.10.0
iniconfig==1.1.1
isort==4.3.21
Jinja2==2.10.3
keyring==21.1.0
Expand All @@ -22,38 +25,43 @@ mccabe==0.6.1
more-itertools==7.2.0
natsort==6.0.0
ordered-set==3.1.1
packaging==20.1
pkginfo==1.5.0.1
packaging==21.3
pkginfo==1.8.2
platformdirs==2.4.1
pluggy==0.13.0
py==1.8.0
py==1.11.0
Pygments==2.4.2
pylint==2.4.4
pylint==2.12.2
pyparsing==2.4.2
pytest==5.3.5
pytest==6.2.5
pytz==2019.3
pywin32-ctypes==0.2.0
pyxcute==0.6.0
readme-renderer==24.0
regex==2020.1.8
requests==2.22.0
requests==2.27.0
requests-toolbelt==0.9.1
rfc3986==1.5.0
semver==2.9.0
Send2Trash==1.5.0
setuptools==45.1.0
six==1.12.0
snowballstemmer==2.0.0
Sphinx==2.3.1
Sphinx==4.3.2
sphinxcontrib-applehelp==1.0.1
sphinxcontrib-devhelp==1.0.1
sphinxcontrib-htmlhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.2
sphinxcontrib-serializinghtml==1.1.3
sphinxcontrib-serializinghtml==1.1.5
toml==0.10.2
tornado==6.0.3
tqdm==4.36.1
twine==3.1.1
twine==3.7.1
typing_extensions==4.0.1
urllib3==1.25.6
wcwidth==0.1.7
webencodings==0.5.1
wheel==0.34.2
wrapt==1.11.2
wheel==0.37.1
wrapt==1.11.2
zipp==3.7.0
10 changes: 5 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pylint==2.4.4
pytest==5.3.5
pylint==2.12.2
pytest==6.2.5
pyxcute==0.6.0
sphinx==2.3.1
twine==3.1.1
wheel==0.34.2
sphinx==4.3.2
twine==3.7.1
wheel==0.37.1
8 changes: 4 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ classifiers =
Natural Language :: Chinese (Traditional)
Operating System :: Microsoft :: Windows :: Windows 7
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Topic :: Software Development :: Build Tools

keywords = pip, pipm, venv, vex, virtualenv, pipenv
Expand All @@ -27,10 +28,9 @@ zip_safe = True
packages = find:
install_requires =
case-conversion~=2.1
configupdater~=1.0
packaging~=20.1
requests~=2.22
setuptools~=45.1
configupdater~=3.0
packaging~=21.3
requests~=2.27

[options.entry_points]
console_scripts =
Expand Down
11 changes: 6 additions & 5 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
def test_pypi_compatible():
from vpip.pypi import is_compatible
assert is_compatible("0.1.0", "0.2.0") is False
assert is_compatible("1.1.0", "1.2.0") is True
assert is_compatible("1.1.0", "2.2.0") is False
from packaging.version import parse as p
assert is_compatible(p("0.1.0"), p("0.2.0")) is False
assert is_compatible(p("1.1.0"), p("1.2.0")) is True
assert is_compatible(p("1.1.0"), p("2.2.0")) is False

def test_global_script_folder():
# make sure the script folder is already in the path
from vpip.venv import get_global_script_folders
import os
import pathlib
paths = set(pathlib.Path(p) for p in os.environ["PATH"].split(os.pathsep))
assert any(pathlib.Path(f) in paths for f in get_global_script_folders())

assert any(f in paths for f in get_global_script_folders())

6 changes: 3 additions & 3 deletions vpip/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import re
import importlib
from pkg_resources import resource_listdir
import importlib.resources

#: List of builtin command names.
names = [
filename.partition(".")[0]
for filename in resource_listdir(__name__, "")
for filename in importlib.resources.contents(__name__)
if re.match("[a-z]\w+\.py", filename)
]

Expand All @@ -19,4 +19,4 @@ def get_modules():
for name in names:
modules[name] = importlib.import_module(".{}".format(name), __name__)
return modules


4 changes: 2 additions & 2 deletions vpip/commands/update_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def update_venv(vv, global_pkg_name=None):
import configparser
env_dir = pathlib.Path(vv.env_dir)
config = configparser.ConfigParser()
config.read_string("[DEFAULT]\n" + (env_dir / "pyvenv.cfg").read_text())
config.read_string("[DEFAULT]\n" + (env_dir / "pyvenv.cfg").read_text(encoding="utf8"))
config_home = pathlib.Path(config.get("DEFAULT", "home"))
# https://github.com/python/cpython/blob/0118d109d54bf75c99a8b0fa9aeae1a478ac4b7e/Lib/venv/__init__.py#L109
current_home = pathlib.Path(getattr(sys, '_base_executable', sys.executable)).parent
Expand All @@ -52,4 +52,4 @@ def update_venv(vv, global_pkg_name=None):
from .. import pip_api
with vv.activate():
pip_api.install("pip", upgrade=True, latest=True)


40 changes: 35 additions & 5 deletions vpip/dependency.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,42 @@
import configparser
import re
from pathlib import Path
from typing import Iterator

from configupdater import ConfigUpdater
from pkg_resources import parse_requirements
from packaging.requirements import Requirement

LOCK_FILE = "requirements-lock.txt"

def parse_requirements(text) -> Iterator[Requirement]:
"""Parse requirements text.
FIXME: switch to an external function from pip if possible.
https://pip.pypa.io/en/stable/reference/requirements-file-format/#requirements-file-format
"""
for line in get_continued_lines(text):
# FIXME: handle options?
line = re.sub(r"(^|\s+)(#|--\w+|-e).+", "", line)
if not line:
continue
if re.match("https?:", line):
continue
if line.startswith("."):
continue
yield Requirement(line)

def get_continued_lines(text) -> Iterator[str]:
last_line = ""
for line in text.split("\n"):
last_line += line
if line.endswith("\\"):
continue
if last_line.strip():
yield last_line.strip()
last_line = ""
if last_line.strip():
yield last_line.strip()

def get_dev_requires():
return parse_requirements(DevUpdater().get_requirements())

Expand Down Expand Up @@ -98,12 +128,12 @@ def write_requirements(self, lines):
self.config.add_section("options")
self.config.set("options", "install_requires", "".join(
"\n" + self.indent + l for l in lines))
self.file.write_text(str(self.config).replace("\r", ""), "utf8")
self.file.write_text(str(self.config).replace("\r", ""), encoding="utf8")
if not self.file_py.exists():
self.file_py.write_text("\n".join([
"from setuptools import setup",
"setup()"
]))
]), encoding="utf8")

class UpdateDependencyResult:
def __init__(self):
Expand Down Expand Up @@ -164,7 +194,7 @@ def update_lock():
if pkg.name == "pip":
continue
lines.append("{}=={}".format(pkg.name, pkg.version))
Path(LOCK_FILE).write_text("\n".join(lines))
Path(LOCK_FILE).write_text("\n".join(lines), encoding="utf8")

def add_dev(packages):
return update_dependency(DevUpdater(), added=packages)
Expand All @@ -184,4 +214,4 @@ def detect_indent(text):
if match:
return match.group(1)
return None


8 changes: 4 additions & 4 deletions vpip/pip_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from argparse import Namespace
from subprocess import CalledProcessError

from packaging.requirements import Requirement
import case_conversion
from pkg_resources import Requirement

from .execute import execute

Expand All @@ -25,12 +25,12 @@ def install(package, install_scripts=None, upgrade=False, latest=False):
:rtype: Namespace
"""
cmd = "install"
require = Requirement.parse(package)
require = Requirement(package)
if install_scripts:
cmd += " --install-option \"--install-scripts={}\"".format(install_scripts)
if upgrade:
cmd += " -U"
if not latest and not require.specs:
if not latest and not require.specifier:
try:
version = show([require.name])[0].version
except CalledProcessError:
Expand Down Expand Up @@ -159,4 +159,4 @@ def get_compatible_version(version):
if version.startswith("0."):
return version
return ".".join(version.split(".")[:2])


26 changes: 9 additions & 17 deletions vpip/pypi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from collections import namedtuple
from packaging.version import parse as parse_version, LegacyVersion, Version
import requests
import packaging.version

UpdateResult = namedtuple("UpdateResult", ["compatible", "latest"])

Expand Down Expand Up @@ -36,11 +36,11 @@ def check_update(pkg, curr_version):
r.raise_for_status()

# curr_version = packaging.version.parse(curr_version)
all_versions = [packaging.version.parse(v) for v in r.json()["releases"].keys()]
all_versions = [parse_version(v) for v in r.json()["releases"].keys()]
all_versions = [v for v in all_versions if not v.is_prerelease]
all_versions.sort()

curr_version = packaging.version.parse(curr_version)
curr_version = parse_version(curr_version)
latest = None
compatible = None

Expand All @@ -55,24 +55,16 @@ def check_update(pkg, curr_version):
if compatible or latest:
return UpdateResult(compatible, latest)

def is_compatible(version, new_version):
def is_compatible(version: Version, new_version: Version) -> bool:
"""Check if two versions are compatible. ``new_version`` may be smaller
than ``version``.
:type version: str
:type new_version: str
:rtype: bool
"""
version = to_version_tuple(version)
new_version = to_version_tuple(new_version)
if version[0] == new_version[0]:
if version[0] != 0:
if any(isinstance(v, LegacyVersion) for v in [version, new_version]):
return False
if version.major == new_version.major:
if version.major != 0:
return True
if version[1] == new_version[1]:
if version.minor == new_version.minor:
return True
return False

def to_version_tuple(version):
"""Split version into number tuple"""
return tuple(int(n) for n in str(version).split("."))

0 comments on commit 5d22024

Please sign in to comment.