diff --git a/Dockerfile b/Dockerfile index c3f8367..68875cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,12 @@ FROM registry.gitlab.com/mbarkhau/pycalver/base ADD src/ src/ ADD stubs/ stubs/ ADD test/ test/ +ADD requirements/ requirements/ ADD setup.cfg setup.cfg +ADD setup.py setup.py +ADD README.md README.md +ADD CHANGELOG.md CHANGELOG.md +ADD LICENSE LICENSE ADD makefile makefile ADD makefile.config.make makefile.config.make ADD makefile.extra.make makefile.extra.make diff --git a/makefile.extra.make b/makefile.extra.make index 6ac0cd3..1f2e91b 100644 --- a/makefile.extra.make +++ b/makefile.extra.make @@ -31,7 +31,7 @@ test_compat: $(COMPAT_TEST_FILES) IFS=' ' read -r -a env_paths <<< "$(CONDA_ENV_PATHS)"; \ for i in $${!env_paths[@]}; do \ env_py=$${env_paths[i]}/bin/python; \ - $${env_py} -m pip install --upgrade build/test_wheel/*.whl; \ + $${env_py} -m pip install --upgrade pytest build/test_wheel/*.whl; \ PYTHONPATH="" ENV=$${ENV-dev} \ $${env_py} -m pytest --verbose compat_test/; \ done; diff --git a/requirements/integration.txt b/requirements/integration.txt index 67015ca..a584ba4 100644 --- a/requirements/integration.txt +++ b/requirements/integration.txt @@ -16,7 +16,6 @@ pylint mypy pytest pytest-cov -pylint readme_renderer[md] twine diff --git a/src/pycalver/__main__.py b/src/pycalver/__main__.py index ca8001c..6d7388f 100644 --- a/src/pycalver/__main__.py +++ b/src/pycalver/__main__.py @@ -24,14 +24,14 @@ from . import rewrite _VERBOSE = 0 -# try: -# import backtrace +try: + import backtrace -# # To enable pretty tracebacks: -# # echo "export ENABLE_BACKTRACE=1;" >> ~/.bashrc -# backtrace.hook(align=True, strip_path=True, enable_on_envvar_only=True) -# except ImportError: -# pass + # To enable pretty tracebacks: + # echo "export ENABLE_BACKTRACE=1;" >> ~/.bashrc + backtrace.hook(align=True, strip_path=True, enable_on_envvar_only=True) +except ImportError: + pass click.disable_unicode_literals_warning = True @@ -183,10 +183,10 @@ def init(verbose: int = 0, dry: bool = False) -> None: sys.exit(1) if dry: - print("Exiting because of '--dry'. Would have written to setup.cfg:") + print("Exiting because of '--dry'. Would have written to {ctx.config_filepath}:") cfg_lines = config.default_config(ctx) print("\n " + "\n ".join(cfg_lines)) - return + sys.exit(0) config.write_content(ctx) diff --git a/src/pycalver/config.py b/src/pycalver/config.py index f341c0c..942abe2 100644 --- a/src/pycalver/config.py +++ b/src/pycalver/config.py @@ -222,8 +222,8 @@ def _parse_config(raw_cfg: RawConfig) -> Config: version_pattern = raw_cfg.get('version_pattern', "{pycalver}") version_pattern = raw_cfg['version_pattern'] = version_pattern.strip("'\" ") - # NOTE (mb 2019-01-05): trigger ValueError if version_pattern - # and current_version don't work together. + # NOTE (mb 2019-01-05): Provoke ValueError if version_pattern + # and current_version are not compatible. version.parse_version_info(version_str, version_pattern) pep440_version = version.to_pep440(version_str) @@ -245,7 +245,15 @@ def _parse_config(raw_cfg: RawConfig) -> Config: file_patterns = _normalize_file_patterns(raw_cfg) - cfg = Config(version_str, version_pattern, pep440_version, tag, commit, push, file_patterns) + cfg = Config( + current_version=version_str, + version_pattern=version_pattern, + pep440_version=pep440_version, + commit=commit, + tag=tag, + push=push, + file_patterns=file_patterns, + ) log.debug(_debug_str(cfg)) return cfg @@ -263,7 +271,8 @@ def parse(ctx: ProjectContext) -> MaybeConfig: elif ctx.config_format == 'cfg': raw_cfg = _parse_cfg(fh) else: - return None + err_msg = "Invalid config_format='{ctx.config_format}'" + raise RuntimeError(err_msg) return _parse_config(raw_cfg) except ValueError as ex: diff --git a/src/pycalver/rewrite.py b/src/pycalver/rewrite.py index 39dc799..86c7d6c 100644 --- a/src/pycalver/rewrite.py +++ b/src/pycalver/rewrite.py @@ -109,6 +109,8 @@ def iter_rewritten( ... ] >>> ''' + fh: typ.IO[str] + for filepath, patterns in file_patterns.items(): with io.open(filepath, mode="rt", encoding="utf-8") as fh: content = fh.read() @@ -151,6 +153,8 @@ def diff(new_version: str, file_patterns: config.PatternsByFilePath) -> str: full_diff = "" file_path: str + fh : typ.IO[str] + for file_path, patterns in sorted(file_patterns.items()): with io.open(file_path, mode="rt", encoding="utf-8") as fh: content = fh.read() @@ -165,6 +169,7 @@ def diff(new_version: str, file_patterns: config.PatternsByFilePath) -> str: def rewrite(new_version: str, file_patterns: config.PatternsByFilePath) -> None: """Rewrite project files, updating each with the new version.""" + fh: typ.IO[str] for file_data in iter_rewritten(file_patterns, new_version): new_content = file_data.line_sep.join(file_data.new_lines) diff --git a/src/pycalver/vcs.py b/src/pycalver/vcs.py index ff8f9ec..ae7ab14 100644 --- a/src/pycalver/vcs.py +++ b/src/pycalver/vcs.py @@ -50,6 +50,9 @@ VCS_SUBCOMMANDS_BY_NAME = { } +Env = typ.Dict[str, str] + + class VCS: """VCS absraction for git and mercurial.""" @@ -60,7 +63,7 @@ class VCS: else: self.subcommands = subcommands - def __call__(self, cmd_name: str, env=None, **kwargs: str) -> str: + def __call__(self, cmd_name: str, env: Env = None, **kwargs: str) -> str: """Invoke subcommand and return output.""" cmd_tmpl = self.subcommands[cmd_name] cmd_str = cmd_tmpl.format(**kwargs) @@ -68,7 +71,7 @@ class VCS: log.info(cmd_str) else: log.debug(cmd_str) - output_data = sp.check_output(cmd_str.split(), env=env, stderr=sp.STDOUT) + output_data: bytes = sp.check_output(cmd_str.split(), env=env, stderr=sp.STDOUT) # TODO (mb 2018-11-15): Detect encoding of output? _encoding = "utf-8" @@ -141,12 +144,11 @@ class VCS: tmp_file = tempfile.NamedTemporaryFile("wb", delete=False) assert " " not in tmp_file.name + fh : typ.IO[bytes] with tmp_file as fh: fh.write(message_data) - env = os.environ.copy() - # TODO (mb 2018-09-04): check that this works on py27, - # might need to be bytes there, idk. + env: Env = os.environ.copy() env['HGENCODING'] = "utf-8" self('commit', env=env, path=tmp_file.name) os.unlink(tmp_file.name) diff --git a/src/pycalver/version.py b/src/pycalver/version.py index f0ca298..a30b2bc 100644 --- a/src/pycalver/version.py +++ b/src/pycalver/version.py @@ -430,7 +430,12 @@ def incr( 'old_version' is assumed to be a string that matches 'pattern' """ - old_ver_nfo = parse_version_info(old_version, pattern) + try: + old_ver_nfo = parse_version_info(old_version, pattern) + except ValueError as ex: + log.error(str(ex)) + return None + cur_ver_nfo = old_ver_nfo cur_cal_nfo = cal_info() diff --git a/test/test_cli.py b/test/test_cli.py index 0e882e6..4807bc3 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -128,6 +128,15 @@ def test_incr_semver(runner): assert f"Version: {new_version}\n" in result.output +def test_incr_semver_invalid(runner, caplog): + result = runner.invoke(pycalver.cli, ['test', "--verbose", "--patch", "0.1.1"]) + assert result.exit_code == 1 + assert len(caplog.records) > 0 + log_record = caplog.records[0] + assert "Invalid version string" in log_record.message + assert "for pattern '{pycalver}'" in log_record.message + + def test_incr_to_beta(runner): old_version = "v201701.0999-alpha" initial_version = config._initial_version() @@ -148,7 +157,7 @@ def test_incr_to_final(runner): assert f"Version: {new_version}\n" in result.output -def test_incr_invalid(runner, caplog): +def test_incr_invalid(runner): old_version = "v201701.0999-alpha" result = runner.invoke(pycalver.cli, ['test', old_version, "--verbose", "--release", "alfa"]) @@ -183,11 +192,35 @@ def test_nocfg(runner, caplog): ) -def test_novcs_nocfg_init(runner): +def test_novcs_nocfg_init(runner, caplog, capsys): _add_project_files("README.md") + # dry mode test + result = runner.invoke(pycalver.cli, ['init', "--verbose", "--dry"]) + assert result.exit_code == 0 + assert not os.path.exists("pycalver.toml") + + # check logging + assert len(caplog.records) == 1 + log = caplog.records[0] + assert log.levelname == 'WARNING' + assert "File not found" in log.message + + # print("moep") + # captured = capsys.readouterr() + # assert not captured.err + # assert "Would have written to pycalver.toml:" in captured.out + + # non dry mode result = runner.invoke(pycalver.cli, ['init', "--verbose"]) assert result.exit_code == 0 + # check logging + assert len(caplog.records) == 2 + log = caplog.records[1] + assert log.levelname == 'WARNING' + assert "File not found" in log.message + + assert os.path.exists("pycalver.toml") with io.open("pycalver.toml", mode="r", encoding="utf-8") as fh: cfg_content = fh.read() @@ -200,6 +233,15 @@ def test_novcs_nocfg_init(runner): assert f"Current Version: {config._initial_version()}\n" in result.output assert f"PEP440 : {config._initial_version_pep440()}\n" in result.output + result = runner.invoke(pycalver.cli, ['init', "--verbose"]) + assert result.exit_code == 1 + + # check logging + assert len(caplog.records) == 3 + log = caplog.records[2] + assert log.levelname == 'ERROR' + assert "Configuration already initialized" in log.message + def test_novcs_setupcfg_init(runner): _add_project_files("README.md", "setup.cfg") diff --git a/test/test_config.py b/test/test_config.py index 22c86b0..830d386 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -5,7 +5,7 @@ from pycalver import config from . import util -PYCALVER_TOML_FIXTURE = """ +PYCALVER_TOML_FIXTURE_1 = """ [pycalver] current_version = "v201808.0123-alpha" commit = true @@ -23,6 +23,22 @@ push = true """ +PYCALVER_TOML_FIXTURE_2 = """ +[pycalver] +current_version = "1.2.3" +version_pattern = "{semver}" + +[pycalver.file_patterns] +"README.md" = [ + "{version}", + "{pep440_version}", +] +"pycalver.toml" = [ + 'current_version = "{version}"', +] +""" + + SETUP_CFG_FIXTURE = """ [pycalver] current_version = "v201808.0456-beta" @@ -46,13 +62,14 @@ def mk_buf(text): return buf -def test_parse_toml(): - buf = mk_buf(PYCALVER_TOML_FIXTURE) +def test_parse_toml_1(): + buf = mk_buf(PYCALVER_TOML_FIXTURE_1) raw_cfg = config._parse_toml(buf) cfg = config._parse_config(raw_cfg) assert cfg.current_version == "v201808.0123-alpha" + assert cfg.version_pattern == "{pycalver}" assert cfg.commit is True assert cfg.tag is True assert cfg.push is True @@ -62,6 +79,23 @@ def test_parse_toml(): assert cfg.file_patterns["pycalver.toml"] == ['current_version = "{pycalver}"'] +def test_parse_toml_2(): + buf = mk_buf(PYCALVER_TOML_FIXTURE_2) + + raw_cfg = config._parse_toml(buf) + cfg = config._parse_config(raw_cfg) + + assert cfg.current_version == "1.2.3" + assert cfg.version_pattern == "{semver}" + assert cfg.commit is False + assert cfg.tag is False + assert cfg.push is False + + assert "pycalver.toml" in cfg.file_patterns + assert cfg.file_patterns["README.md" ] == ["{semver}", "{semver}"] + assert cfg.file_patterns["pycalver.toml"] == ['current_version = "{semver}"'] + + def test_parse_cfg(): buf = mk_buf(SETUP_CFG_FIXTURE) @@ -154,7 +188,7 @@ def test_parse_project_cfg(): def test_parse_toml_file(tmpdir): project_path = tmpdir.mkdir("minimal") setup_cfg = project_path.join("pycalver.toml") - setup_cfg.write(PYCALVER_TOML_FIXTURE) + setup_cfg.write(PYCALVER_TOML_FIXTURE_1) ctx = config.init_project_ctx(project_path) assert ctx == config.ProjectContext(project_path, setup_cfg, 'toml', None)