Skip to content

Commit

Permalink
--update-all
Browse files Browse the repository at this point in the history
Part of #63
  • Loading branch information
peterbe committed Oct 26, 2018
1 parent ccb661b commit cbf3310
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 8 deletions.
42 changes: 34 additions & 8 deletions hashin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@
parser = argparse.ArgumentParser()
parser.add_argument(
"packages",
help=(
"One or more package specifiers (e.g. some-package or " "some-package==1.2.3)"
),
nargs="+",
help="One or more package specifiers (e.g. some-package or some-package==1.2.3)",
nargs="*",
)
parser.add_argument(
"-r",
Expand Down Expand Up @@ -79,6 +77,13 @@
action="store_true",
default=False,
)
parser.add_argument(
"-u",
"--update-all",
help="Update all mentioned packages in the requirements file.",
action="store_true",
default=False,
)


major_pip_version = int(pip_api.version().split(".")[0])
Expand Down Expand Up @@ -119,12 +124,22 @@ def _download(url, binary=False):
return r.read().decode(encoding)


def run(specs, *args, **kwargs):
def run(specs, requirements_file, *args, **kwargs):
if not specs: # then, assume all in the requirements file
regex = re.compile(r"(^|\n|\n\r).*==")
specs = []
with open(requirements_file) as f:
for line in f:
if regex.search(line):
req = Requirement(line.split("\\")[0])
# Deliberately strip the specifier (aka. the version)
req.specifier = None
specs.append(str(req))
if isinstance(specs, str):
specs = [specs]

for spec in specs:
run_single_package(spec, *args, **kwargs)
run_single_package(spec, requirements_file, *args, **kwargs)
return 0


Expand Down Expand Up @@ -198,12 +213,11 @@ def run_single_package(


def amend_requirements_content(requirements, package, new_lines):
# if the package wasn't already there, add it to the bottom
regex = re.compile(
r"(^|\n|\n\r){0}==|(^|\n|\n\r){0}\[.*\]==".format(re.escape(package)),
re.IGNORECASE,
)

# if the package wasn't already there, add it to the bottom
if not regex.search(requirements):
# easy peasy
if requirements:
Expand Down Expand Up @@ -493,6 +507,18 @@ def main():

args = parser.parse_args()

if args.update_all:
if args.packages:
print(
"Can not combine the --update-all option with a list of packages.",
file=sys.stderr,
)
return 2
elif not args.packages:
print("If you don't use --update-all you must list packages.", file=sys.stderr)
parser.print_usage()
return 3

try:
return run(
args.packages,
Expand Down
3 changes: 3 additions & 0 deletions tests/test_arg_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def test_everything():
version=False,
include_prereleases=False,
dry_run=True,
update_all=False,
)
assert args == (expected, [])

Expand Down Expand Up @@ -55,6 +56,7 @@ def test_everything_long():
version=False,
include_prereleases=False,
dry_run=True,
update_all=False,
)
assert args == (expected, [])

Expand All @@ -70,5 +72,6 @@ def test_minimal():
version=False,
include_prereleases=False,
dry_run=False,
update_all=False,
)
assert args == (expected, [])
245 changes: 245 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import argparse
import sys
import json
import os
Expand Down Expand Up @@ -187,11 +188,71 @@ def test_main_packageerrors_stderr(self, mock_run, mock_sys, mock_parser):
# Doesn't matter so much what, just make sure it breaks
mock_run.side_effect = hashin.PackageError("Some message here")

def mock_parse_args(*a, **k):
return argparse.Namespace(
packages=["something"],
requirements_file="requirements.txt",
algorithm="sha256",
python_version="3.8",
verbose=False,
include_prereleases=False,
dry_run=False,
update_all=False,
)

mock_parser.parse_args.side_effect = mock_parse_args

error = hashin.main()
self.assertEqual(error, 1)
mock_sys.stderr.write.assert_any_call("Some message here")
mock_sys.stderr.write.assert_any_call("\n")

@mock.patch("hashin.parser")
@mock.patch("hashin.sys")
def test_packages_and_update_all(self, mock_sys, mock_parser):
def mock_parse_args(*a, **k):
return argparse.Namespace(
packages=["something"],
requirements_file="requirements.txt",
algorithm="sha256",
python_version="3.8",
verbose=False,
include_prereleases=False,
dry_run=False,
update_all=True, # Note!
)

mock_parser.parse_args.side_effect = mock_parse_args

error = hashin.main()
self.assertEqual(error, 2)
mock_sys.stderr.write.assert_any_call(
"Can not combine the --update-all option with a list of packages."
)

@mock.patch("hashin.parser")
@mock.patch("hashin.sys")
def test_no_packages_and_not_update_all(self, mock_sys, mock_parser):
def mock_parse_args(*a, **k):
return argparse.Namespace(
packages=[], # Note!
requirements_file="requirements.txt",
algorithm="sha256",
python_version="3.8",
verbose=False,
include_prereleases=False,
dry_run=False,
update_all=False,
)

mock_parser.parse_args.side_effect = mock_parse_args

error = hashin.main()
self.assertEqual(error, 3)
mock_sys.stderr.write.assert_any_call(
"If you don't use --update-all you must list packages."
)

@mock.patch("hashin.sys")
def test_main_version(self, mock_sys):
mock_sys.argv = [None, "--version"]
Expand Down Expand Up @@ -602,6 +663,190 @@ def test_run_case_insensitive(self, murlopen):
it should find it and correct the cast typing per what it is
inside the PyPI data."""

def mocked_get(url, **options):
# if url == "https://pypi.org/pypi/HASHin/json":
# return _Response(
# "",
# status_code=301,
# headers={"location": "https://pypi.org/pypi/hashin/json"},
# )
# elif url == "https://pypi.org/pypi/hashIN/json":
# return _Response(
# "",
# status_code=301,
# headers={"location": "https://pypi.org/pypi/hashin/json"},
# )
if url == "https://pypi.org/pypi/hashin/json":
return _Response(
{
"info": {"version": "0.11", "name": "hashin"},
"releases": {
"0.11": [
{
"url": "https://pypi.org/packages/source/p/hashin/hashin-0.11.tar.gz",
"digests": {"sha256": "bbbbb"},
}
],
"0.10": [
{
"url": "https://pypi.org/packages/source/p/hashin/hashin-0.10.tar.gz",
"digests": {"sha256": "aaaaa"},
}
],
},
}
)
elif url == "https://pypi.org/pypi/hashin/json":
return _Response(
{
"info": {"version": "0.11", "name": "hashin"},
"releases": {
"0.11": [
{
"url": "https://pypi.org/packages/source/p/hashin/hashin-0.11.tar.gz",
"digests": {"sha256": "bbbbb"},
}
],
"0.10": [
{
"url": "https://pypi.org/packages/source/p/hashin/hashin-0.10.tar.gz",
"digests": {"sha256": "aaaaa"},
}
],
},
}
)
elif url == "https://pypi.org/pypi/requests/json":
return _Response(
{
"info": {"version": "1.2.4", "name": "requests"},
"releases": {
"1.2.4": [
{
"url": "https://pypi.org/packages/source/p/requests/requests-1.2.4.tar.gz",
"digests": {"sha256": "dededede"},
}
]
},
}
)
if url == "https://pypi.org/pypi/enum34/json":
return _Response(
{
"info": {"version": "1.1.6", "name": "enum34"},
"releases": {
"1.1.6": [
{
"has_sig": False,
"upload_time": "2016-05-16T03:31:13",
"comment_text": "",
"python_version": "py2",
"url": "https://pypi.org/packages/c5/db/enum34-1.1.6-py2-none-any.whl",
"digests": {
"md5": "68f6982cc07dde78f4b500db829860bd",
"sha256": "aaaaa",
},
"md5_digest": "68f6982cc07dde78f4b500db829860bd",
"downloads": 4297423,
"filename": "enum34-1.1.6-py2-none-any.whl",
"packagetype": "bdist_wheel",
"path": "c5/db/enum34-1.1.6-py2-none-any.whl",
"size": 12427,
},
{
"has_sig": False,
"upload_time": "2016-05-16T03:31:19",
"comment_text": "",
"python_version": "py3",
"url": "https://pypi.org/packages/af/42/enum34-1.1.6-py3-none-any.whl",
"md5_digest": "a63ecb4f0b1b85fb69be64bdea999b43",
"digests": {
"md5": "a63ecb4f0b1b85fb69be64bdea999b43",
"sha256": "bbbbb",
},
"downloads": 98598,
"filename": "enum34-1.1.6-py3-none-any.whl",
"packagetype": "bdist_wheel",
"path": "af/42/enum34-1.1.6-py3-none-any.whl",
"size": 12428,
},
{
"has_sig": False,
"upload_time": "2016-05-16T03:31:30",
"comment_text": "",
"python_version": "source",
"url": "https://pypi.org/packages/bf/3e/enum34-1.1.6.tar.gz",
"md5_digest": "5f13a0841a61f7fc295c514490d120d0",
"digests": {
"md5": "5f13a0841a61f7fc295c514490d120d0",
"sha256": "ccccc",
},
"downloads": 188090,
"filename": "enum34-1.1.6.tar.gz",
"packagetype": "sdist",
"path": "bf/3e/enum34-1.1.6.tar.gz",
"size": 40048,
},
{
"has_sig": False,
"upload_time": "2016-05-16T03:31:48",
"comment_text": "",
"python_version": "source",
"url": "https://pypi.org/packages/e8/26/enum34-1.1.6.zip",
"md5_digest": "61ad7871532d4ce2d77fac2579237a9e",
"digests": {
"md5": "61ad7871532d4ce2d77fac2579237a9e",
"sha256": "dddddd",
},
"downloads": 775920,
"filename": "enum34-1.1.6.zip",
"packagetype": "sdist",
"path": "e8/26/enum34-1.1.6.zip",
"size": 44773,
},
]
},
}
)

raise NotImplementedError(url)

murlopen.side_effect = mocked_get

with tmpfile() as filename:
with self.assertRaises(FileNotFoundError):
retcode = hashin.run(None, filename, "sha256")

with open(filename, "w") as f:
f.write("# This is comment. Ignore this.\n")
f.write("\n")
f.write("requests[security]==1.2.3 \\\n")
f.write(" --hash=sha256:99dcfdaae\n")
f.write("hashin==0.11 \\\n")
f.write(" --hash=sha256:a84b8c9ab623\n")
f.write("enum34==1.1.5; python_version <= '3.4' \\\n")
f.write(" --hash=sha256:12ce5c2ef718\n")
f.write("\n")

retcode = hashin.run(None, filename, "sha256", verbose=True)

self.assertEqual(retcode, 0)
with open(filename) as f:
output = f.read()

self.assertTrue("requests[security]==1.2.3" not in output)
self.assertTrue("requests[security]==1.2.4" in output)
# This one didn't need to be updated.
self.assertTrue("hashin==0.11" in output)
self.assertTrue('enum34==1.1.5; python_version <= "3.4"' not in output)
self.assertTrue('enum34==1.1.6; python_version <= "3.4"' in output)

@cleanup_tmpdir("hashin*")
@mock.patch("hashin.urlopen")
def test_run_update_all(self, murlopen):
"""The --update-all flag will extra all the names from the existing
requirements file, and check with pypi.org if there's a new version."""

def mocked_get(url, **options):
if url == "https://pypi.org/pypi/HAShin/json":
return _Response(
Expand Down

0 comments on commit cbf3310

Please sign in to comment.