Merge pull request #239 from mapattacker/mode

New Option --mode for "gt", "compat", and "non-pin"
This commit is contained in:
Alan Barzilay 2021-03-29 15:34:49 -03:00 committed by GitHub
commit cb4add28ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 59 deletions

View File

@ -10,4 +10,5 @@ Development Lead
Contributors Contributors
------------ ------------
None yet. Why not be the first? * Jake Teo <mapattacker@gmail.com>
* Jerome Chan <cjerome94@gmail.com>

View File

@ -3,6 +3,11 @@
History History
------- -------
0.4.11 (2020-03-29)
--------------------
* Implement '--mode' (Jake Teo, Jerome Chan)
0.4.8 (2017-06-30) 0.4.8 (2017-06-30)
-------------------- --------------------

View File

@ -49,7 +49,10 @@ Usage
--force Overwrite existing requirements.txt --force Overwrite existing requirements.txt
--diff <file> Compare modules in requirements.txt to project imports. --diff <file> Compare modules in requirements.txt to project imports.
--clean <file> Clean up requirements.txt by removing modules that are not imported in project. --clean <file> Clean up requirements.txt by removing modules that are not imported in project.
--no-pin Omit version of output packages. --mode <scheme> Enables dynamic versioning with <compat>, <gt> or <non-pin> schemes.
<compat> | e.g. Flask~=1.1.2
<gt> | e.g. Flask>=1.1.2
<no-pin> | e.g. Flask
Example Example
------- -------

41
pipreqs/pipreqs.py Executable file → Normal file
View File

@ -31,7 +31,11 @@ Options:
imports. imports.
--clean <file> Clean up requirements.txt by removing modules --clean <file> Clean up requirements.txt by removing modules
that are not imported in project. that are not imported in project.
--no-pin Omit version of output packages. --mode <scheme> Enables dynamic versioning with <compat>,
<gt> or <non-pin> schemes.
<compat> | e.g. Flask~=1.1.2
<gt> | e.g. Flask>=1.1.2
<no-pin> | e.g. Flask
""" """
from __future__ import print_function, absolute_import from __future__ import print_function, absolute_import
from contextlib import contextmanager from contextlib import contextmanager
@ -161,21 +165,21 @@ def filter_line(line):
return len(line) > 0 and line[0] != "#" return len(line) > 0 and line[0] != "#"
def generate_requirements_file(path, imports): def generate_requirements_file(path, imports, symbol):
with _open(path, "w") as out_file: with _open(path, "w") as out_file:
logging.debug('Writing {num} requirements: {imports} to {file}'.format( logging.debug('Writing {num} requirements: {imports} to {file}'.format(
num=len(imports), num=len(imports),
file=path, file=path,
imports=", ".join([x['name'] for x in imports]) imports=", ".join([x['name'] for x in imports])
)) ))
fmt = '{name}=={version}' fmt = '{name}' + symbol + '{version}'
out_file.write('\n'.join( out_file.write('\n'.join(
fmt.format(**item) if item['version'] else '{name}'.format(**item) fmt.format(**item) if item['version'] else '{name}'.format(**item)
for item in imports) + '\n') for item in imports) + '\n')
def output_requirements(imports): def output_requirements(imports, symbol):
generate_requirements_file('-', imports) generate_requirements_file('-', imports, symbol)
def get_imports_info( def get_imports_info(
@ -397,6 +401,18 @@ def clean(file_, imports):
logging.info("Successfully cleaned up requirements in " + file_) logging.info("Successfully cleaned up requirements in " + file_)
def dynamic_versioning(scheme, imports):
"""Enables dynamic versioning with <compat>, <gt> or <non-pin> schemes."""
if scheme == "no-pin":
imports = [{"name": item["name"], "version": ""} for item in imports]
symbol = ""
elif scheme == "gt":
symbol = ">="
elif scheme == "compat":
symbol = "~="
return imports, symbol
def init(args): def init(args):
encoding = args.get('--encoding') encoding = args.get('--encoding')
extra_ignore_dirs = args.get('--ignore') extra_ignore_dirs = args.get('--ignore')
@ -457,14 +473,21 @@ def init(args):
"use --force to overwrite it") "use --force to overwrite it")
return return
if args.get('--no-pin'): if args["--mode"]:
imports = [{'name': item["name"], 'version': ''} for item in imports] scheme = args.get("--mode")
if scheme in ["compat", "gt", "no-pin"]:
imports, symbol = dynamic_versioning(scheme, imports)
else:
raise ValueError("Invalid argument for mode flag, "
"use 'compat', 'gt' or 'no-pin' instead")
else:
symbol = "=="
if args["--print"]: if args["--print"]:
output_requirements(imports) output_requirements(imports, symbol)
logging.info("Successfully output requirements") logging.info("Successfully output requirements")
else: else:
generate_requirements_file(path, imports) generate_requirements_file(path, imports, symbol)
logging.info("Successfully saved requirements file in " + path) logging.info("Successfully saved requirements file in " + path)

130
tests/test_pipreqs.py Executable file → Normal file
View File

@ -113,11 +113,9 @@ class TestPipreqs(unittest.TestCase):
""" """
Test that all modules we will test upon are in requirements file Test that all modules we will test upon are in requirements file
""" """
pipreqs.init( pipreqs.init({'<path>': self.project, '--savepath': None, '--print': False,
{'<path>': self.project, '--savepath': None, '--print': False, '--use-local': None, '--force': True, '--proxy':None, '--pypi-server':None,
'--use-local': None, '--force': True, '--proxy': None, '--diff': None, '--clean': None, '--mode': None})
'--pypi-server': None, '--diff': None, '--clean': None}
)
assert os.path.exists(self.requirements_path) == 1 assert os.path.exists(self.requirements_path) == 1
with open(self.requirements_path, "r") as f: with open(self.requirements_path, "r") as f:
data = f.read().lower() data = f.read().lower()
@ -132,11 +130,9 @@ class TestPipreqs(unittest.TestCase):
Test that items listed in requirements.text are the same Test that items listed in requirements.text are the same
as locals expected as locals expected
""" """
pipreqs.init( pipreqs.init({'<path>': self.project, '--savepath': None, '--print': False,
{'<path>': self.project, '--savepath': None, '--print': False, '--use-local': True, '--force': True, '--proxy':None, '--pypi-server':None,
'--use-local': True, '--force': True, '--proxy': None, '--diff': None, '--clean': None, '--mode': None})
'--pypi-server': None, '--diff': None, '--clean': None}
)
assert os.path.exists(self.requirements_path) == 1 assert os.path.exists(self.requirements_path) == 1
with open(self.requirements_path, "r") as f: with open(self.requirements_path, "r") as f:
data = f.readlines() data = f.readlines()
@ -149,11 +145,9 @@ class TestPipreqs(unittest.TestCase):
Test that we can save requirements.txt correctly Test that we can save requirements.txt correctly
to a different path to a different path
""" """
pipreqs.init( pipreqs.init({'<path>': self.project, '--savepath': self.alt_requirement_path,
{'<path>': self.project, '--savepath': self.alt_requirement_path, '--use-local': None, '--proxy':None, '--pypi-server':None, '--print': False,
'--use-local': None, '--proxy': None, '--pypi-server': None, '--diff': None, '--clean': None, '--mode': None})
'--print': False, "--diff": None, "--clean": None}
)
assert os.path.exists(self.alt_requirement_path) == 1 assert os.path.exists(self.alt_requirement_path) == 1
with open(self.alt_requirement_path, "r") as f: with open(self.alt_requirement_path, "r") as f:
data = f.read().lower() data = f.read().lower()
@ -169,11 +163,9 @@ class TestPipreqs(unittest.TestCase):
""" """
with open(self.requirements_path, "w") as f: with open(self.requirements_path, "w") as f:
f.write("should_not_be_overwritten") f.write("should_not_be_overwritten")
pipreqs.init( pipreqs.init({'<path>': self.project, '--savepath': None, '--use-local': None,
{'<path>': self.project, '--savepath': None, '--use-local': None, '--force': None, '--proxy':None, '--pypi-server':None, '--print': False,
'--force': None, '--proxy': None, '--pypi-server': None, '--diff': None, '--clean': None, '--mode': None})
'--print': False, '--diff': None, '--clean': None}
)
assert os.path.exists(self.requirements_path) == 1 assert os.path.exists(self.requirements_path) == 1
with open(self.requirements_path, "r") as f: with open(self.requirements_path, "r") as f:
data = f.read().lower() data = f.read().lower()
@ -211,39 +203,79 @@ class TestPipreqs(unittest.TestCase):
Test --ignore parameter Test --ignore parameter
""" """
pipreqs.init( pipreqs.init(
{'<path>': self.project_with_ignore_directory, '--savepath': None, {'<path>': self.project_with_ignore_directory, '--savepath': None,
'--print': False, '--use-local': None, '--force': True, '--print': False, '--use-local': None, '--force': True,
'--proxy': None, '--pypi-server': None, '--proxy':None, '--pypi-server':None,
'--ignore': '.ignored_dir,.ignore_second', '--diff': None, '--ignore':'.ignored_dir,.ignore_second',
'--clean': None} '--diff': None,
) '--clean': None,
with open( '--mode': None
os.path.join( }
self.project_with_ignore_directory, )
"requirements.txt" with open(os.path.join(self.project_with_ignore_directory, "requirements.txt"), "r") as f:
), "r"
) as f:
data = f.read().lower() data = f.read().lower()
for item in ['click', 'getpass']: for item in ['click', 'getpass']:
self.assertFalse(item.lower() in data) self.assertFalse(item.lower() in data)
def test_omit_version(self): def test_dynamic_version_no_pin_scheme(self):
""" """
Test --no-pin parameter Test --mode=no-pin
""" """
pipreqs.init( pipreqs.init(
{'<path>': self.project_with_ignore_directory, '--savepath': None, {'<path>': self.project_with_ignore_directory, '--savepath': None,
'--print': False, '--use-local': None, '--force': True, '--print': False, '--use-local': None, '--force': True,
'--proxy': None, '--pypi-server': None, '--diff': None, '--proxy': None, '--pypi-server': None,
'--clean': None, '--no-pin': True} '--diff': None,
) '--clean': None,
with open(os.path.join( '--mode': 'no-pin'
self.project_with_ignore_directory, }
"requirements.txt"), "r" )
) as f: with open(os.path.join(self.project_with_ignore_directory, "requirements.txt"), "r") as f:
data = f.read().lower() data = f.read().lower()
for item in ['beautifulsoup4==4.8.1', 'boto==2.49.0']: for item in ['beautifulsoup4', 'boto']:
self.assertFalse(item.lower() in data) self.assertTrue(item.lower() in data)
def test_dynamic_version_gt_scheme(self):
"""
Test --mode=gt
"""
pipreqs.init(
{'<path>': self.project_with_ignore_directory, '--savepath': None, '--print': False,
'--use-local': None, '--force': True,
'--proxy': None,
'--pypi-server': None,
'--diff': None,
'--clean': None,
'--mode': 'gt'
}
)
with open(os.path.join(self.project_with_ignore_directory, "requirements.txt"), "r") as f:
data = f.readlines()
for item in data:
symbol = '>='
message = 'symbol is not in item'
self.assertIn(symbol, item, message)
def test_dynamic_version_compat_scheme(self):
"""
Test --mode=compat
"""
pipreqs.init(
{'<path>': self.project_with_ignore_directory, '--savepath': None, '--print': False,
'--use-local': None, '--force': True,
'--proxy': None,
'--pypi-server': None,
'--diff': None,
'--clean': None,
'--mode': 'compat'
}
)
with open(os.path.join(self.project_with_ignore_directory, "requirements.txt"), "r") as f:
data = f.readlines()
for item in data:
symbol = '~='
message = 'symbol is not in item'
self.assertIn(symbol, item, message)
def test_clean(self): def test_clean(self):
""" """
@ -252,14 +284,15 @@ class TestPipreqs(unittest.TestCase):
pipreqs.init( pipreqs.init(
{'<path>': self.project, '--savepath': None, '--print': False, {'<path>': self.project, '--savepath': None, '--print': False,
'--use-local': None, '--force': True, '--proxy': None, '--use-local': None, '--force': True, '--proxy': None,
'--pypi-server': None, '--diff': None, '--clean': None} '--pypi-server': None, '--diff': None, '--clean': None,
'--mode': None}
) )
assert os.path.exists(self.requirements_path) == 1 assert os.path.exists(self.requirements_path) == 1
pipreqs.init( pipreqs.init(
{'<path>': self.project, '--savepath': None, '--print': False, {'<path>': self.project, '--savepath': None, '--print': False,
'--use-local': None, '--force': None, '--proxy': None, '--use-local': None, '--force': None, '--proxy': None,
'--pypi-server': None, '--diff': None, '--pypi-server': None, '--diff': None,
'--clean': self.requirements_path, '--no-pin': True} '--clean': self.requirements_path, '--mode': 'non-pin'}
) )
with open(self.requirements_path, "r") as f: with open(self.requirements_path, "r") as f:
data = f.read().lower() data = f.read().lower()
@ -274,7 +307,8 @@ class TestPipreqs(unittest.TestCase):
pipreqs.init( pipreqs.init(
{'<path>': self.project, '--savepath': None, '--print': False, {'<path>': self.project, '--savepath': None, '--print': False,
'--use-local': None, '--force': True, '--proxy': None, '--use-local': None, '--force': True, '--proxy': None,
'--pypi-server': None, '--diff': None, '--clean': None} '--pypi-server': None, '--diff': None, '--clean': None,
'--mode': None}
) )
assert os.path.exists(self.requirements_path) == 1 assert os.path.exists(self.requirements_path) == 1
modules_clean = [m for m in self.modules if m != cleaned_module] modules_clean = [m for m in self.modules if m != cleaned_module]
@ -282,7 +316,7 @@ class TestPipreqs(unittest.TestCase):
{'<path>': self.project_clean, '--savepath': None, {'<path>': self.project_clean, '--savepath': None,
'--print': False, '--use-local': None, '--force': None, '--print': False, '--use-local': None, '--force': None,
'--proxy': None, '--pypi-server': None, '--diff': None, '--proxy': None, '--pypi-server': None, '--diff': None,
'--clean': self.requirements_path, '--no-pin': True} '--clean': self.requirements_path, '--mode': 'non-pin'}
) )
with open(self.requirements_path, "r") as f: with open(self.requirements_path, "r") as f:
data = f.read().lower() data = f.read().lower()