Skip to content

Commit

Permalink
feat: add dry-run option (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
max-wittig authored and Peter Bengtsson committed Oct 8, 2018
1 parent 45c15ec commit 32445d8
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 9 deletions.
20 changes: 20 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,26 @@ these exact identifiers directly, if you need something specific.
The ``source`` release is always automatically included. ``pip`` will use
this as a fallback in the case a suitable wheel cannot be found.

Dry run mode
============

There are some use cases, when you maybe don't want to edit your ``requirements.txt``
right away. You can use the ``--dry-run`` argument to show the diff, so you
can preview the changes to your ``requirements.txt`` file.

Example::

hashin --dry-run requests==2.19.1

Would result in a printout on the command line::

--- Old
+++ New
@@ -0,0 +1,3 @@
+requests==2.19.1 \
+ --hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \
+ --hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a

PEP-0496 Environment Markers
============================

Expand Down
38 changes: 30 additions & 8 deletions hashin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import pip_api
from packaging.version import parse
import difflib

if sys.version_info >= (3,):
from urllib.request import urlopen
Expand Down Expand Up @@ -75,6 +76,12 @@
action='store_true',
default=False,
)
parser.add_argument(
'--dry-run',
help="Don't touch requirements.txt and just show the diff",
action='store_true',
default=False,
)


major_pip_version = int(pip_api.version().split('.')[0])
Expand All @@ -101,7 +108,7 @@ def _download(url, binary=False):
# Note that urlopen will, by default, follow redirects.
status_code = r.getcode()

if status_code >= 301 and status_code < 400:
if 301 <= status_code < 400:
location, _ = cgi.parse_header(r.headers.get('location', ''))
if not location:
raise PackageError("No 'Location' header on {0} ({1})".format(
Expand Down Expand Up @@ -137,6 +144,7 @@ def run_single_package(
python_versions=None,
verbose=False,
include_prereleases=False,
dry_run=False,
):
restriction = None
if ';' in spec:
Expand All @@ -159,7 +167,6 @@ def run_single_package(
package = data['package']

maybe_restriction = '' if not restriction else '; {0}'.format(restriction)
new_lines = ''
new_lines = '{0}=={1}{2} \\\n'.format(
package,
data['version'],
Expand All @@ -175,17 +182,31 @@ def run_single_package(
new_lines += ' \\'
new_lines += '\n'

if verbose:
_verbose('Editing', file)
with open(file) as f:
requirements = f.read()
old_requirements = f.read()
requirements = amend_requirements_content(
requirements,
old_requirements,
package,
new_lines
)
with open(file, 'w') as f:
f.write(requirements)
if dry_run:
if verbose:
_verbose('Dry run, not editing ', file)
print(
"".join(
difflib.unified_diff(
old_requirements.splitlines(True),
requirements.splitlines(True),
fromfile="Old",
tofile="New",
)
)
)
else:
with open(file, 'w') as f:
f.write(requirements)
if verbose:
_verbose('Editing', file)


def amend_requirements_content(requirements, package, new_lines):
Expand Down Expand Up @@ -498,6 +519,7 @@ def main():
args.python_version,
verbose=args.verbose,
include_prereleases=args.include_prereleases,
dry_run=args.dry_run,
)
except PackageError as exception:
print(str(exception), file=sys.stderr)
Expand Down
6 changes: 5 additions & 1 deletion tests/test_arg_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_everything():
'-r', 'reqs.txt',
'-a', 'sha512',
'-p', '3.5',
'-v',
'-v', '--dry-run'
])
expected = argparse.Namespace(
algorithm='sha512',
Expand All @@ -19,6 +19,7 @@ def test_everything():
verbose=True,
version=False,
include_prereleases=False,
dry_run=True,
)
assert args == (expected, [])

Expand All @@ -30,6 +31,7 @@ def test_everything_long():
'--algorithm', 'sha512',
'--python-version', '3.5',
'--verbose',
'--dry-run',
])
expected = argparse.Namespace(
algorithm='sha512',
Expand All @@ -39,6 +41,7 @@ def test_everything_long():
verbose=True,
version=False,
include_prereleases=False,
dry_run=True,
)
assert args == (expected, [])

Expand All @@ -53,5 +56,6 @@ def test_minimal():
verbose=False,
version=False,
include_prereleases=False,
dry_run=False,
)
assert args == (expected, [])
66 changes: 66 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,72 @@ def mocked_get(url, **options):
'hashin==0.11 \\'
)

@cleanup_tmpdir('hashin*')
@mock.patch('hashin.urlopen')
def test_run_dry(self, murlopen):
"""dry run should edit the requirements.txt file and print
hashes and package name in the console
"""

def mocked_get(url, **options):
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',
},
}
]
}
})

murlopen.side_effect = mocked_get

with tmpfile() as filename:
with open(filename, 'w') as f:
f.write('')

my_stdout = StringIO()
with redirect_stdout(my_stdout):
retcode = hashin.run(
'hashin==0.10',
filename,
'sha256',
verbose=False,
dry_run=True,
)

self.assertEqual(retcode, 0)

# verify that nothing has been written to file
with open(filename) as f:
output = f.read()
assert not output

# Check dry run output
out_lines = my_stdout.getvalue().splitlines()
self.assertTrue(
'+hashin==0.10' in out_lines[3]
)
self.assertTrue(
'+--hash=sha256:aaaaa' in out_lines[4].replace(" ", "")
)

@cleanup_tmpdir('hashin*')
@mock.patch('hashin.urlopen')
def test_run_pep_0496(self, murlopen):
Expand Down

0 comments on commit 32445d8

Please # to comment.