From b783606ce32ce3351bb12cac0618ccec08375844 Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 15:34:40 -0600 Subject: [PATCH 01/10] updated README Signed-off-by: Bryant Finney --- README.md | 137 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index cc181ab..36b90cb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Pipenv-Setup + [![travis-badge](https://travis-ci.org/Madoshakalaka/pipenv-setup.svg?branch=master)](https://travis-ci.org/Madoshakalaka/pipenv-setup) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/pipenv-setup.svg)](https://pypi.python.org/pypi/pipenv-setup/) [![codecov](https://codecov.io/gh/Madoshakalaka/pipenv-setup/branch/master/graph/badge.svg)](https://codecov.io/gh/Madoshakalaka/pipenv-setup) @@ -6,22 +7,25 @@ [![Very popular](https://img.shields.io/pypi/dm/pipenv-setup)](https://pypistats.org/packages/pipenv-setup) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -Sync dependencies in Pipfile or Lockfile to setup.py +A beautiful python package development tool: sync dependencies in `Pipfile` or `Pipfile.lock` to `setup.py`. + +Never need again to change dependencies manually in `setup.py`, and enjoy the same +dependency locking or semantic versioning. -A beautiful python package development tool. +Or just check whether `setup.py` and `Pipfile` are consistent and sync dependency when +necessary. -Never need again to change dependencies -manually in `setup.py` and enjoy the same - dependency locking or semantic versioning - - Or just check whether setup.py and pipfile are consistent and sync dependency when necessary -### Install +## Installation -`$ pipenv install --dev pipenv-setup` +Create a command line entry point `pipenv-setup`, and add `pipenv-setup` as a `dev` +package in `Pipfile`: -it creates command line entry `$ pipenv-setup` +```bash +pipenv install --dev pipenv-setup +``` ## Features + ### Beautiful pipenv flavored help `$ pipenv-setup` @@ -29,7 +33,9 @@ it creates command line entry `$ pipenv-setup` ![help](https://raw.githubusercontent.com/Madoshakalaka/pipenv-setup/master/readme_assets/help.PNG) ### Sync to `setup.py` + - supports assorted package configuration. You can have a pipfile as ugly as you want: + ```Pipfile [package] requests = { extras = ['socks'] } @@ -39,17 +45,21 @@ it creates command line entry `$ pipenv-setup` "e1839a8" = {path = ".", editable = true} pywinusb = { version = "*", os_name = "=='nt'", index="pypi"} ``` + `pipenv-setup` will still figure things out: - - `$ pipenv-setup sync` - ``` + + ```bash + $ pipenv-setup sync package e1839a8 is local, omitted in setup.py setup.py successfully updated 23 packages from Pipfile.lock synced to setup.py ``` - And things will be where they should be + + And things will be where they should be: + ```python # setup.py + setup(..., install_requires=[ "certifi==2017.7.27.1", "chardet==3.0.4", @@ -62,47 +72,70 @@ it creates command line entry `$ pipenv-setup` "git+https://github.com/django/django.git@1.11.4#egg=django", "https://github.com/divio/django-cms/archive/release/3.4.x.zip", ], + ) ``` -- provide `--dev` flag to sync development packages to `extras_require` - ``` + +- provide `--dev` flag to sync development packages with `extras_require`: + + ```bash $ pipenv-setup sync --dev - setup.py successfully updated 1 default packages from Pipfile.lock synced to setup.py 1 dev packages from Pipfile.lock synced to setup.py ``` - ``` - # produced setup.py - extras_require={"dev": ["pytest==1.1.3",]}, - install_requires=["xml-subsetter==0.0.1"], + ```python + # produced setup.py + setup(..., + extras_require={"dev": ["pytest==1.1.3",]}, + install_requires=["xml-subsetter==0.0.1"], + ) ``` -- provide `--pipfile` flag to sync Pipfile instead of lockfile, which syncs semantic versioning in pipfile instead of pinned versions in lockfile. -- Beautiful [Blackened](https://github.com/psf/black) setup.py file. -- [Template](https://github.com/pypa/sampleproject/blob/master/setup.py) generation with filled dependencies in the absence of a setup file. - `$ pipenv-setup sync` - ``` +- produce beautiful [Blackened](https://github.com/psf/black) `setup.py` file + +- [Template](https://github.com/pypa/sampleproject/blob/master/setup.py) generation with + filled dependencies in the absence of a setup file. + + ```bash + $ pipenv-setup sync setup.py not found under current directory Creating boilerplate setup.py... - setup.py successfully generated under current directory - 23 packages moved from Pipfile.lock to setup.py + setup.py was successfully generated + 23 packages synced from Pipfile.lock to setup.py Please edit the required fields in the generated file ``` -> Note: by default $ pipenv-setup syncs lockfile instead of pipfile + + > Note: by default, `pipenv-setup` syncs lockfile instead of pipfile + +#### Sync `Pipfile` vs. `Pipfile.lock` + +Provide `--pipfile` flag to sync `Pipfile` instead of `Pipfile.lock`. + +`pipenv-setup` +will perform a liquid sync using semantic versioning taken from `Pipfile` (instead of +using frozen pinned versions from `Pipfile.lock`): + +```bash +$ pipenv-setup sync --pipfile +setup.py was successfully updated +23 packages synced from Pipfile to setup.py +``` + ### Checks Only + run `$ pipenv-setup check` + - checks four items - - local package in default pipfile packages - - Package version requirements in `install_requires` in setup.py that potentially violates Pipfile - - Package version requirements in `dependency_links` in setup.py that differs from Pipfile - - Default package in pipfile missing in `install_requires` or `dependency_links` in setup.py + - local package in default pipfile packages + - Package version requirements in `install_requires` in setup.py that potentially violates Pipfile + - Package version requirements in `dependency_links` in setup.py that differs from Pipfile + - Default package in pipfile missing in `install_requires` or `dependency_links` in setup.py - exits with non-zero code when conflict found (can be used in travis-ci) -- here is a somewhat extreme example - - ``` +- here is a somewhat extreme example: + + ```bash $ pipenv-setup check - package 'numpy' has version string: >=1.2 in setup.py, which potentially violates >=1.5 in pipfile package 'pywinusb' has version string: ==0.4.2 in setup.py, which is disjoint from ~=0.3.0 in pipfile package 'records' has version string: >=0.4.2,<0.5 in setup.py, which is disjoint from >0.5.0 in pipfile @@ -112,36 +145,34 @@ run `$ pipenv-setup check` (exits with 1) ``` - - provide `--ignore-local` flag to allow local packages in pipfile - - ``` + + ```bash $ pipenv-setup check - local package found in default dependency: e1839a8. - Do you mean to make it dev dependency + Do you mean to make it dev dependency (exits with 1) ``` - ``` + ```bash $ pipenv-setup check --ignore-local - No version conflict or missing packages/dependencies found in setup.py! (exits with 0) ``` - provide `--strict` flag to only pass identical version requirements - By default `pipenv-setup check` passes when the version `setup.py` specifies is "compatible" with `Pipfile`, i.e. is a subset of it. + By default `pipenv-setup check` passes when the version `setup.py` specifies is + "compatible" with `Pipfile`, i.e. is a subset of it. For example, a Pipfile + specifying `django~=1.1` with `setup.py` requiring `django==1.2` is such a case. + + Provide `--strict` to allow only identical requirements; *i.e.* for `Pipfile`'s + `django~=1.1`, `setup.py` must require `django>=1.1,<2.0` - For example, `pipfile`: django~=1.1 `setup.py`: django==1.2 is such a case. - - provide `--strict` to allow only identical requirements such as `pipfile`: django~=1.1 `setup.py`: django>=1.1,<2.0 - Example output: - ``` - pipenv-setup check --strict - + + ```bash + $ pipenv-setup check --strict package 'pywinusb' has version string: ==0.4.2 in setup.py, which specifies a subset of * in pipfile package 'django' has version string: >=0.5 in setup.py, which is disjoint from ~=0.3.0 in pipfile package 'records' has version string: ==0.5.2 in setup.py, which specifies a subset of >0.5.0 in pipfile @@ -151,4 +182,4 @@ run `$ pipenv-setup check` ## Contributing -If you'd like to contribute to `pipenv-setup`. See [Contribution Guide](CONTRIBUTING.md) +If you'd like to contribute to `pipenv-setup`, see [Contribution Guide](CONTRIBUTING.md) From 4ff17b8b5de1d25fbd0026a5ecd38a622b9f8969 Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 13:46:23 -0600 Subject: [PATCH 02/10] added test for Pipfile/Pipfile.lock success msg test fails: `pipenv-setup sync --pipfile` displays that setup was synced with Pipfile.lock Signed-off-by: Bryant Finney --- tests/sync_pipfile_test.py | 49 ++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/tests/sync_pipfile_test.py b/tests/sync_pipfile_test.py index e2dc55d..7d3c814 100644 --- a/tests/sync_pipfile_test.py +++ b/tests/sync_pipfile_test.py @@ -1,16 +1,55 @@ -from vistir.compat import Path from .conftest import data +from .main_test import copy_file, compare_list_of_string_kw_arg from pipenv_setup.main import cmd +import pytest +from vistir.compat import Path + + +@pytest.mark.parametrize( + ("source_pipfile_dirname", "update_count"), + [("nasty_0", 23), ("no_original_kws_0", 23)], +) +def test_sync_pipfile_no_original( + capsys, tmp_path, shared_datadir, source_pipfile_dirname, update_count +): + """ + sync --pipfile should reference Pipfile (not Pipfile.lock) when printing results + """ + pipfile_dir = shared_datadir / source_pipfile_dirname + for filename in ("Pipfile", "Pipfile.lock", "setup.py"): + copy_file(pipfile_dir / filename, tmp_path) + + with data(pipfile_dir, tmp_path) as path: + setup_file = path / "setup.py" # type: Path + cmd(["", "sync", "--pipfile"]) + text = setup_file.read_text() + generated_setup = Path("setup.py") + assert generated_setup.exists() + generated_setup_text = generated_setup.read_text() + expected_setup_text = Path("setup.py").read_text() + + for kw_arg_names in ("install_requires", "dependency_links"): + assert compare_list_of_string_kw_arg( + generated_setup_text, + expected_setup_text, + kw_arg_names, + ordering_matters=False, + ) + + captured = capsys.readouterr() + assert "Pipfile.lock" not in captured.out, captured.out + -def test_sync_dev_no_original(tmp_path): +def test_sync_dev_pipfile_no_original(tmp_path): """ - sync --dev should add extras_require: {"dev": [blah]} in the absence of a extras_require keyword + sync --dev --pipfile should add extras_require: {"dev": [blah]} in the absence of an + extras_require keyword """ # todo: this test is too simple with data("self_0", tmp_path) as path: setup_file = path / "setup.py" # type: Path cmd(["", "sync", "--dev", "--pipfile"]) text = setup_file.read_text() - assert "pytest~=5.1" in text - assert "requirementslib~=1.5" in text + assert "pytest~=5.1" in text, text + assert "requirementslib~=1.5" in text, text From a20919875dac6825f94a78dd52674756ca8976db Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 14:12:56 -0600 Subject: [PATCH 03/10] add pipfile arg to msg_formatter.update_success Signed-off-by: Bryant Finney --- pipenv_setup/msg_formatter.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pipenv_setup/msg_formatter.py b/pipenv_setup/msg_formatter.py index beb24a2..7b93a32 100644 --- a/pipenv_setup/msg_formatter.py +++ b/pipenv_setup/msg_formatter.py @@ -44,20 +44,23 @@ def checked_no_problem(): def update_success( - default_package_count, dev_package_count=0 -): # type: (int, int) -> str + default_package_count, dev_package_count=0, pipfile=False +): # type: (int, int, bool) -> str """ :param default_package_count: The number of updated default packages :param dev_package_count: The number of updated dev packages + :param bool lockfile: indicate that Pipfile was used to update setup.py """ + src = "Pipfile" if pipfile else "Pipfile.lock" string = ( "setup.py successfully updated" - + "\n%d default packages from Pipfile.lock synced to setup.py" - % default_package_count + + "\n%d default packages from %s synced to setup.py" + % (default_package_count, src) ) + if dev_package_count != 0: - string += ( - "\n%d dev packages from Pipfile.lock synced to setup.py" - % default_package_count + string += "\n%d dev packages from %s synced to setup.py" % ( + default_package_count, + src, ) return string From de3731ed0b434577421228db1b7596ac22a666e8 Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 14:16:31 -0600 Subject: [PATCH 04/10] add function msg_formatter.generate_success used when setup.py is instead of updated Signed-off-by: Bryant Finney --- pipenv_setup/main.py | 12 +++++------- pipenv_setup/msg_formatter.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/pipenv_setup/main.py b/pipenv_setup/main.py index 9d4f746..c24861b 100644 --- a/pipenv_setup/main.py +++ b/pipenv_setup/main.py @@ -244,11 +244,12 @@ def sync(argv): dependency_arguments[destination_kw].append(value) if only_setup_missing: + # TODO add test coverage for this branch print(msg_formatter.setup_not_found()) print("Creating boilerplate setup.py...") setup_code = setup_filler.fill_boilerplate(dependency_arguments) if setup_code is None: - fatal_error("Can not find setup.py template file") + fatal_error("Cannot find setup.py template file") try: with open(setup_file_path, "w") as new_setup_file: new_setup_file.write(setup_code) @@ -257,12 +258,9 @@ def sync(argv): fatal_error([str(e), "failed to write setup.py file"]) else: congratulate( - [ - "setup.py generated", - "%d packages moved from %s to setup.py" - % (default_package_success_count, file.name), - "Please edit the required fields in the generated file", - ] + msg_formatter.generate_success( + default_package_success_count, dev_package_success_count + ) ) else: # all files exist. Update setup.py diff --git a/pipenv_setup/msg_formatter.py b/pipenv_setup/msg_formatter.py index 7b93a32..3932ddd 100644 --- a/pipenv_setup/msg_formatter.py +++ b/pipenv_setup/msg_formatter.py @@ -43,6 +43,29 @@ def checked_no_problem(): return "No version conflict or missing packages/dependencies found in setup.py!" +def generate_success( + default_package_count, dev_package_count=0, pipfile=False +): # type: (int, int, bool) -> str + """ + :param default_package_count: The number of updated default packages + :param dev_package_count: The number of updated dev packages + :param bool lockfile: indicate that Pipfile was used to update setup.py + """ + src = "Pipfile" if pipfile else "Pipfile.lock" + string = ( + "setup.py generated" + "\n%d default packages moved from %s to setup.py" % (default_package_count, src) + ) + + if dev_package_count != 0: + string += "\n%d dev packages from %s synced to setup.py" % ( + default_package_count, + src, + ) + + string += "\nPlease edit the required fields in the generated file" + return string + def update_success( default_package_count, dev_package_count=0, pipfile=False ): # type: (int, int, bool) -> str From 9085e1ddcb2149dc4606465eced47dd8edd159b1 Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 14:20:28 -0600 Subject: [PATCH 05/10] implement pipfile arg in success messages Signed-off-by: Bryant Finney --- pipenv_setup/main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pipenv_setup/main.py b/pipenv_setup/main.py index c24861b..288c0d3 100644 --- a/pipenv_setup/main.py +++ b/pipenv_setup/main.py @@ -259,7 +259,9 @@ def sync(argv): else: congratulate( msg_formatter.generate_success( - default_package_success_count, dev_package_success_count + default_package_success_count, + dev_package_success_count, + argv.pipfile, ) ) @@ -272,7 +274,9 @@ def sync(argv): fatal_error([str(e), msg_formatter.no_sync_performed()]) congratulate( msg_formatter.update_success( - default_package_success_count, dev_package_success_count + default_package_success_count, + dev_package_success_count, + argv.pipfile, ) ) else: From 502bc29e77234acd2a531a7a457ec975cd518494 Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 14:58:38 -0600 Subject: [PATCH 06/10] added test case for missing setup.py Signed-off-by: Bryant Finney --- pipenv_setup/main.py | 1 - pipenv_setup/msg_formatter.py | 2 +- tests/main_test.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/pipenv_setup/main.py b/pipenv_setup/main.py index 288c0d3..e4d1ea3 100644 --- a/pipenv_setup/main.py +++ b/pipenv_setup/main.py @@ -244,7 +244,6 @@ def sync(argv): dependency_arguments[destination_kw].append(value) if only_setup_missing: - # TODO add test coverage for this branch print(msg_formatter.setup_not_found()) print("Creating boilerplate setup.py...") setup_code = setup_filler.fill_boilerplate(dependency_arguments) diff --git a/pipenv_setup/msg_formatter.py b/pipenv_setup/msg_formatter.py index 3932ddd..c0aaefe 100644 --- a/pipenv_setup/msg_formatter.py +++ b/pipenv_setup/msg_formatter.py @@ -54,7 +54,7 @@ def generate_success( src = "Pipfile" if pipfile else "Pipfile.lock" string = ( "setup.py generated" - "\n%d default packages moved from %s to setup.py" % (default_package_count, src) + "\n%d default packages synced from %s to setup.py" % (default_package_count, src) ) if dev_package_count != 0: diff --git a/tests/main_test.py b/tests/main_test.py index 39a9130..7ea869f 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -103,6 +103,37 @@ def test_update( assert msg_formatter.update_success(update_count) in captured.out +@pytest.mark.parametrize( + ("source_pipfile_dirname", "update_count"), + [("nasty_0", 23), ("no_original_kws_0", 23)], +) +def test_only_setup_missing( + capsys, tmp_path, shared_datadir, source_pipfile_dirname, update_count +): # type: (Any, Path, Path, str, int) -> None + """ + test generate setup.py (when it is missing) + """ + pipfile_dir = shared_datadir / source_pipfile_dirname + with data(source_pipfile_dirname, tmp_path): + # delete the setup.py file that was copied to the tmp_path + (tmp_path / "setup.py").unlink() + cmd(argv=["", "sync"]) + generated_setup = Path("setup.py") + assert generated_setup.exists() + generated_setup_text = generated_setup.read_text() + expected_setup_text = Path("setup.py").read_text() + for kw_arg_names in ("install_requires", "dependency_links"): + + assert compare_list_of_string_kw_arg( + generated_setup_text, + expected_setup_text, + kw_arg_names, + ordering_matters=False, + ) + captured = capsys.readouterr() + assert msg_formatter.generate_success(update_count) in captured.out, captured.out + + @pytest.mark.parametrize(("source_pipfile_dirname",), [("nasty_0",)]) @pytest.mark.parametrize( ("missing_filenames",), From d23b3d3c7d4d9ce1ec886c23ad2ea5f1d784a33a Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 15:05:31 -0600 Subject: [PATCH 07/10] success msg nitpicks Signed-off-by: Bryant Finney --- pipenv_setup/msg_formatter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pipenv_setup/msg_formatter.py b/pipenv_setup/msg_formatter.py index c0aaefe..8f2b37f 100644 --- a/pipenv_setup/msg_formatter.py +++ b/pipenv_setup/msg_formatter.py @@ -53,7 +53,7 @@ def generate_success( """ src = "Pipfile" if pipfile else "Pipfile.lock" string = ( - "setup.py generated" + "setup.py was successfully generated" "\n%d default packages synced from %s to setup.py" % (default_package_count, src) ) @@ -76,7 +76,7 @@ def update_success( """ src = "Pipfile" if pipfile else "Pipfile.lock" string = ( - "setup.py successfully updated" + "setup.py was successfully updated" + "\n%d default packages from %s synced to setup.py" % (default_package_count, src) ) From e08680e19f9c450803e92af3ffff979745cb99c1 Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 15:10:19 -0600 Subject: [PATCH 08/10] added assert Pipfile stays in success msg Signed-off-by: Bryant Finney --- tests/sync_pipfile_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/sync_pipfile_test.py b/tests/sync_pipfile_test.py index 7d3c814..845f30f 100644 --- a/tests/sync_pipfile_test.py +++ b/tests/sync_pipfile_test.py @@ -39,6 +39,7 @@ def test_sync_pipfile_no_original( captured = capsys.readouterr() assert "Pipfile.lock" not in captured.out, captured.out + assert "Pipfile" in captured.out, captured.out def test_sync_dev_pipfile_no_original(tmp_path): From 52f4bc0c44a0f3856642e46e98f2a89b4a5546e1 Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 18:45:06 -0600 Subject: [PATCH 09/10] wrap setup_file_path with `str()` in `open()` Signed-off-by: Bryant Finney --- pipenv_setup/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipenv_setup/main.py b/pipenv_setup/main.py index e4d1ea3..994df73 100644 --- a/pipenv_setup/main.py +++ b/pipenv_setup/main.py @@ -250,7 +250,7 @@ def sync(argv): if setup_code is None: fatal_error("Cannot find setup.py template file") try: - with open(setup_file_path, "w") as new_setup_file: + with open(str(setup_file_path), "w") as new_setup_file: new_setup_file.write(setup_code) format_file(setup_file_path) except OSError as e: From 07f69157eb36a2174b90638953434f46d8611e67 Mon Sep 17 00:00:00 2001 From: Bryant Finney Date: Sat, 16 Nov 2019 20:36:56 -0600 Subject: [PATCH 10/10] wrapped Path obj with str() in sync_pipfile_test Signed-off-by: Bryant Finney --- tests/sync_pipfile_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sync_pipfile_test.py b/tests/sync_pipfile_test.py index 845f30f..4a6b062 100644 --- a/tests/sync_pipfile_test.py +++ b/tests/sync_pipfile_test.py @@ -20,7 +20,7 @@ def test_sync_pipfile_no_original( for filename in ("Pipfile", "Pipfile.lock", "setup.py"): copy_file(pipfile_dir / filename, tmp_path) - with data(pipfile_dir, tmp_path) as path: + with data(str(pipfile_dir), tmp_path) as path: setup_file = path / "setup.py" # type: Path cmd(["", "sync", "--pipfile"]) text = setup_file.read_text()