diff --git a/AUTHORS.rst b/AUTHORS.rst index eaa038a..2f31bac 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,4 +10,5 @@ Development Lead Contributors ------------ -None yet. Why not be the first? +* Jake Teo +* Jerome Chan diff --git a/HISTORY.rst b/HISTORY.rst index b9d17e7..cb4d47b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,11 @@ History ------- +0.4.11 (2020-03-29) +-------------------- + +* Implement '--mode' (Jake Teo, Jerome Chan) + 0.4.8 (2017-06-30) -------------------- diff --git a/README.rst b/README.rst index 2a33b60..07fee33 100644 --- a/README.rst +++ b/README.rst @@ -49,7 +49,10 @@ Usage --force Overwrite existing requirements.txt --diff Compare modules in requirements.txt to project imports. --clean Clean up requirements.txt by removing modules that are not imported in project. - --no-pin Omit version of output packages. + --mode Enables dynamic versioning with , or schemes. + | e.g. Flask~=1.1.2 + | e.g. Flask>=1.1.2 + | e.g. Flask Example ------- diff --git a/pipreqs/pipreqs.py b/pipreqs/pipreqs.py old mode 100755 new mode 100644 index 7e95175..20aae68 --- a/pipreqs/pipreqs.py +++ b/pipreqs/pipreqs.py @@ -31,7 +31,11 @@ Options: imports. --clean Clean up requirements.txt by removing modules that are not imported in project. - --no-pin Omit version of output packages. + --mode Enables dynamic versioning with , + or schemes. + | e.g. Flask~=1.1.2 + | e.g. Flask>=1.1.2 + | e.g. Flask """ from __future__ import print_function, absolute_import from contextlib import contextmanager @@ -161,21 +165,21 @@ def filter_line(line): 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: logging.debug('Writing {num} requirements: {imports} to {file}'.format( num=len(imports), file=path, imports=", ".join([x['name'] for x in imports]) )) - fmt = '{name}=={version}' + fmt = '{name}' + symbol + '{version}' out_file.write('\n'.join( fmt.format(**item) if item['version'] else '{name}'.format(**item) for item in imports) + '\n') -def output_requirements(imports): - generate_requirements_file('-', imports) +def output_requirements(imports, symbol): + generate_requirements_file('-', imports, symbol) def get_imports_info( @@ -397,6 +401,18 @@ def clean(file_, imports): logging.info("Successfully cleaned up requirements in " + file_) +def dynamic_versioning(scheme, imports): + """Enables dynamic versioning with , or 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): encoding = args.get('--encoding') extra_ignore_dirs = args.get('--ignore') @@ -457,14 +473,21 @@ def init(args): "use --force to overwrite it") return - if args.get('--no-pin'): - imports = [{'name': item["name"], 'version': ''} for item in imports] + if args["--mode"]: + 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"]: - output_requirements(imports) + output_requirements(imports, symbol) logging.info("Successfully output requirements") else: - generate_requirements_file(path, imports) + generate_requirements_file(path, imports, symbol) logging.info("Successfully saved requirements file in " + path) diff --git a/tests/test_pipreqs.py b/tests/test_pipreqs.py old mode 100755 new mode 100644 index e099e84..f82d3db --- a/tests/test_pipreqs.py +++ b/tests/test_pipreqs.py @@ -113,11 +113,9 @@ class TestPipreqs(unittest.TestCase): """ Test that all modules we will test upon are in requirements file """ - pipreqs.init( - {'': self.project, '--savepath': None, '--print': False, - '--use-local': None, '--force': True, '--proxy': None, - '--pypi-server': None, '--diff': None, '--clean': None} - ) + pipreqs.init({'': self.project, '--savepath': None, '--print': False, + '--use-local': None, '--force': True, '--proxy':None, '--pypi-server':None, + '--diff': None, '--clean': None, '--mode': None}) assert os.path.exists(self.requirements_path) == 1 with open(self.requirements_path, "r") as f: data = f.read().lower() @@ -132,11 +130,9 @@ class TestPipreqs(unittest.TestCase): Test that items listed in requirements.text are the same as locals expected """ - pipreqs.init( - {'': self.project, '--savepath': None, '--print': False, - '--use-local': True, '--force': True, '--proxy': None, - '--pypi-server': None, '--diff': None, '--clean': None} - ) + pipreqs.init({'': self.project, '--savepath': None, '--print': False, + '--use-local': True, '--force': True, '--proxy':None, '--pypi-server':None, + '--diff': None, '--clean': None, '--mode': None}) assert os.path.exists(self.requirements_path) == 1 with open(self.requirements_path, "r") as f: data = f.readlines() @@ -149,11 +145,9 @@ class TestPipreqs(unittest.TestCase): Test that we can save requirements.txt correctly to a different path """ - pipreqs.init( - {'': self.project, '--savepath': self.alt_requirement_path, - '--use-local': None, '--proxy': None, '--pypi-server': None, - '--print': False, "--diff": None, "--clean": None} - ) + pipreqs.init({'': self.project, '--savepath': self.alt_requirement_path, + '--use-local': None, '--proxy':None, '--pypi-server':None, '--print': False, + '--diff': None, '--clean': None, '--mode': None}) assert os.path.exists(self.alt_requirement_path) == 1 with open(self.alt_requirement_path, "r") as f: data = f.read().lower() @@ -169,11 +163,9 @@ class TestPipreqs(unittest.TestCase): """ with open(self.requirements_path, "w") as f: f.write("should_not_be_overwritten") - pipreqs.init( - {'': self.project, '--savepath': None, '--use-local': None, - '--force': None, '--proxy': None, '--pypi-server': None, - '--print': False, '--diff': None, '--clean': None} - ) + pipreqs.init({'': self.project, '--savepath': None, '--use-local': None, + '--force': None, '--proxy':None, '--pypi-server':None, '--print': False, + '--diff': None, '--clean': None, '--mode': None}) assert os.path.exists(self.requirements_path) == 1 with open(self.requirements_path, "r") as f: data = f.read().lower() @@ -211,39 +203,79 @@ class TestPipreqs(unittest.TestCase): Test --ignore parameter """ pipreqs.init( - {'': self.project_with_ignore_directory, '--savepath': None, - '--print': False, '--use-local': None, '--force': True, - '--proxy': None, '--pypi-server': None, - '--ignore': '.ignored_dir,.ignore_second', '--diff': None, - '--clean': None} - ) - with open( - os.path.join( - self.project_with_ignore_directory, - "requirements.txt" - ), "r" - ) as f: + {'': self.project_with_ignore_directory, '--savepath': None, + '--print': False, '--use-local': None, '--force': True, + '--proxy':None, '--pypi-server':None, + '--ignore':'.ignored_dir,.ignore_second', + '--diff': None, + '--clean': None, + '--mode': None + } + ) + with open(os.path.join(self.project_with_ignore_directory, "requirements.txt"), "r") as f: data = f.read().lower() for item in ['click', 'getpass']: 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( - {'': self.project_with_ignore_directory, '--savepath': None, + {'': self.project_with_ignore_directory, '--savepath': None, '--print': False, '--use-local': None, '--force': True, - '--proxy': None, '--pypi-server': None, '--diff': None, - '--clean': None, '--no-pin': True} - ) - with open(os.path.join( - self.project_with_ignore_directory, - "requirements.txt"), "r" - ) as f: + '--proxy': None, '--pypi-server': None, + '--diff': None, + '--clean': None, + '--mode': 'no-pin' + } + ) + with open(os.path.join(self.project_with_ignore_directory, "requirements.txt"), "r") as f: data = f.read().lower() - for item in ['beautifulsoup4==4.8.1', 'boto==2.49.0']: - self.assertFalse(item.lower() in data) + for item in ['beautifulsoup4', 'boto']: + self.assertTrue(item.lower() in data) + + def test_dynamic_version_gt_scheme(self): + """ + Test --mode=gt + """ + pipreqs.init( + {'': 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( + {'': 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): """ @@ -252,14 +284,15 @@ class TestPipreqs(unittest.TestCase): pipreqs.init( {'': self.project, '--savepath': None, '--print': False, '--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 pipreqs.init( {'': self.project, '--savepath': None, '--print': False, '--use-local': None, '--force': 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: data = f.read().lower() @@ -274,7 +307,8 @@ class TestPipreqs(unittest.TestCase): pipreqs.init( {'': self.project, '--savepath': None, '--print': False, '--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 modules_clean = [m for m in self.modules if m != cleaned_module] @@ -282,7 +316,7 @@ class TestPipreqs(unittest.TestCase): {'': self.project_clean, '--savepath': None, '--print': False, '--use-local': None, '--force': 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: data = f.read().lower()