From 5a65ac4f47281a63a32d4386a42ba2897f00caaa Mon Sep 17 00:00:00 2001 From: Gabriele Pongelli Date: Mon, 30 Jan 2023 10:07:19 +0100 Subject: [PATCH 1/5] feat: support for dotted env in tox --- src/check_python_versions/sources/tox.py | 7 +++++-- tests/sources/test_tox.py | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/check_python_versions/sources/tox.py b/src/check_python_versions/sources/tox.py index 1c441ed..09a94b8 100644 --- a/src/check_python_versions/sources/tox.py +++ b/src/check_python_versions/sources/tox.py @@ -104,8 +104,11 @@ def tox_env_to_py_version(env: str) -> Optional[Version]: env = env.partition('-')[0] if env.startswith('pypy'): return Version.from_string('PyPy' + env[4:]) - elif env.startswith('py') and len(env) >= 4 and env[2:].isdigit(): - return Version.from_string(f'{env[2]}.{env[3:]}') + elif env.startswith('py'): + if len(env) >= 4 and env[2:].isdigit(): + return Version.from_string(f'{env[2]}.{env[3:]}') + if '.' in env: + return Version.from_string(f'{env[2]}.{env[4:]}') else: return None diff --git a/tests/sources/test_tox.py b/tests/sources/test_tox.py index d83723f..80bbd50 100644 --- a/tests/sources/test_tox.py +++ b/tests/sources/test_tox.py @@ -28,6 +28,15 @@ def test_get_tox_ini_python_versions(tmp_path): assert get_tox_ini_python_versions(tox_ini) == v(['2.7', '3.6', '3.10']) +def test_get_tox_ini_dotted_python_versions(tmp_path): + tox_ini = tmp_path / "tox.ini" + tox_ini.write_text(textwrap.dedent("""\ + [tox] + envlist = py2.7,py3.6,py27-docs,pylint,py3.10 + """)) + assert get_tox_ini_python_versions(tox_ini) == v(['2.7', '3.6', '3.10']) + + def test_get_tox_ini_python_versions_syntax_error(tmp_path): tox_ini = tmp_path / "tox.ini" tox_ini.write_text(textwrap.dedent("""\ From fd6a29ce7e33b8b9dc0daddae403ac253b1e1a6c Mon Sep 17 00:00:00 2001 From: Gabriele Pongelli Date: Mon, 30 Jan 2023 10:59:57 +0100 Subject: [PATCH 2/5] manage dotted env in update case --- src/check_python_versions/sources/tox.py | 21 +++++++++++++++++-- tests/sources/test_tox.py | 26 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/check_python_versions/sources/tox.py b/src/check_python_versions/sources/tox.py index 09a94b8..daa24e3 100644 --- a/src/check_python_versions/sources/tox.py +++ b/src/check_python_versions/sources/tox.py @@ -25,6 +25,7 @@ TOX_INI = 'tox.ini' +has_dotted_env = False def get_tox_ini_python_versions( @@ -99,6 +100,8 @@ def tox_env_to_py_version(env: str) -> Optional[Version]: If the environment name has dashes, only the first part is considered, e.g. py34-django20 becomes '3.4', and jython-docs becomes 'jython'. """ + global has_dotted_env + has_dotted_env = False # reset global state before check if '-' in env: # e.g. py34-coverage, pypy-subunit env = env.partition('-')[0] @@ -108,6 +111,7 @@ def tox_env_to_py_version(env: str) -> Optional[Version]: if len(env) >= 4 and env[2:].isdigit(): return Version.from_string(f'{env[2]}.{env[3:]}') if '.' in env: + has_dotted_env = True return Version.from_string(f'{env[2]}.{env[4:]}') else: return None @@ -160,6 +164,11 @@ def update_tox_envlist(envlist: str, new_versions: SortedVersionList) -> str: trailing_comma = envlist.rstrip().endswith(',') + global has_dotted_env + has_dotted_env = False # reset global state before check + if '.' in envlist: + has_dotted_env = True + new_envs = [ toxenv_for_version(ver) for ver in new_versions @@ -226,7 +235,11 @@ def update_tox_envlist(envlist: str, new_versions: SortedVersionList) -> str: def toxenv_for_version(ver: Version) -> str: """Compute a tox environment name for a Python version.""" - return f"py{ver.major}{ver.minor if ver.minor >= 0 else ''}" + global has_dotted_env + _ret_str = f"py{ver.major}" \ + f"{'.' if has_dotted_env else ''}" \ + f"{ver.minor if ver.minor >= 0 else ''}" + return _ret_str def should_keep(env: str, new_versions: VersionList) -> bool: @@ -239,7 +252,11 @@ def should_keep(env: str, new_versions: VersionList) -> bool: or 3.x version respectively in ``new_versions``. """ - if not re.match(r'py(py)?\d*($|-)', env): + global has_dotted_env + _base_regex = r'py(py)?\d*($|-)' + if has_dotted_env: + _base_regex = r'py(py)?\d*(\.\d*)?($|-)' + if not re.match(_base_regex, env): return True if env == 'pypy': return any(ver.major == 2 for ver in new_versions) diff --git a/tests/sources/test_tox.py b/tests/sources/test_tox.py index 80bbd50..86a3cef 100644 --- a/tests/sources/test_tox.py +++ b/tests/sources/test_tox.py @@ -116,6 +116,32 @@ def test_update_tox_ini_python_versions(): """) +def test_update_tox_ini_dotted_python_versions(): + fp = StringIO(textwrap.dedent("""\ + [tox] + envlist = py2.6, py2.7 + """)) + fp.name = 'tox.ini' + result = update_tox_ini_python_versions(fp, v(['3.6', '3.7', '3.10'])) + assert "".join(result) == textwrap.dedent("""\ + [tox] + envlist = py3.6, py3.7, py3.10 + """) + + +def test_update_tox_ini_one_dotted_python_versions(): + fp = StringIO(textwrap.dedent("""\ + [tox] + envlist = py26, py2.7 + """)) + fp.name = 'tox.ini' + result = update_tox_ini_python_versions(fp, v(['3.6', '3.7', '3.10'])) + assert "".join(result) == textwrap.dedent("""\ + [tox] + envlist = py3.6, py3.7, py3.10 + """) + + def test_update_tox_ini_python_syntax_error(capsys): fp = StringIO(textwrap.dedent("""\ [tox From 9654bde1aaba8b357ece12a5fbbe37574a555bb8 Mon Sep 17 00:00:00 2001 From: gpongelli Date: Sat, 4 Feb 2023 19:49:34 +0100 Subject: [PATCH 3/5] start removing the global --- src/check_python_versions/sources/tox.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/check_python_versions/sources/tox.py b/src/check_python_versions/sources/tox.py index daa24e3..60f5054 100644 --- a/src/check_python_versions/sources/tox.py +++ b/src/check_python_versions/sources/tox.py @@ -233,11 +233,10 @@ def update_tox_envlist(envlist: str, new_versions: SortedVersionList) -> str: return new_envlist -def toxenv_for_version(ver: Version) -> str: +def toxenv_for_version(ver: Version, use_dots: bool = False) -> str: """Compute a tox environment name for a Python version.""" - global has_dotted_env _ret_str = f"py{ver.major}" \ - f"{'.' if has_dotted_env else ''}" \ + f"{'.' if use_dots else ''}" \ f"{ver.minor if ver.minor >= 0 else ''}" return _ret_str @@ -252,11 +251,7 @@ def should_keep(env: str, new_versions: VersionList) -> bool: or 3.x version respectively in ``new_versions``. """ - global has_dotted_env - _base_regex = r'py(py)?\d*($|-)' - if has_dotted_env: - _base_regex = r'py(py)?\d*(\.\d*)?($|-)' - if not re.match(_base_regex, env): + if not re.match(r'py(py)?(\d[.])?\d*($|-)', env): return True if env == 'pypy': return any(ver.major == 2 for ver in new_versions) From 7a6c81d9b9fb26382fe07792611f23670ee5f4d5 Mon Sep 17 00:00:00 2001 From: gpongelli Date: Sat, 4 Feb 2023 21:24:56 +0100 Subject: [PATCH 4/5] removed global var in favor of Version attribute --- src/check_python_versions/sources/tox.py | 22 ++++++---------------- src/check_python_versions/versions.py | 5 ++++- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/check_python_versions/sources/tox.py b/src/check_python_versions/sources/tox.py index 60f5054..df076cf 100644 --- a/src/check_python_versions/sources/tox.py +++ b/src/check_python_versions/sources/tox.py @@ -25,7 +25,6 @@ TOX_INI = 'tox.ini' -has_dotted_env = False def get_tox_ini_python_versions( @@ -100,19 +99,15 @@ def tox_env_to_py_version(env: str) -> Optional[Version]: If the environment name has dashes, only the first part is considered, e.g. py34-django20 becomes '3.4', and jython-docs becomes 'jython'. """ - global has_dotted_env - has_dotted_env = False # reset global state before check if '-' in env: # e.g. py34-coverage, pypy-subunit env = env.partition('-')[0] if env.startswith('pypy'): return Version.from_string('PyPy' + env[4:]) - elif env.startswith('py'): - if len(env) >= 4 and env[2:].isdigit(): - return Version.from_string(f'{env[2]}.{env[3:]}') - if '.' in env: - has_dotted_env = True - return Version.from_string(f'{env[2]}.{env[4:]}') + elif env.startswith('py') and len(env) >= 4 and env[2:].isdigit(): + return Version.from_string(f'{env[2]}.{env[3:]}') + elif env.startswith('py') and '.' in env: + return Version.from_string(f'{env[2:]}', has_dot=True) else: return None @@ -164,11 +159,6 @@ def update_tox_envlist(envlist: str, new_versions: SortedVersionList) -> str: trailing_comma = envlist.rstrip().endswith(',') - global has_dotted_env - has_dotted_env = False # reset global state before check - if '.' in envlist: - has_dotted_env = True - new_envs = [ toxenv_for_version(ver) for ver in new_versions @@ -233,10 +223,10 @@ def update_tox_envlist(envlist: str, new_versions: SortedVersionList) -> str: return new_envlist -def toxenv_for_version(ver: Version, use_dots: bool = False) -> str: +def toxenv_for_version(ver: Version) -> str: """Compute a tox environment name for a Python version.""" _ret_str = f"py{ver.major}" \ - f"{'.' if use_dots else ''}" \ + f"{'.' if ver.has_dot else ''}" \ f"{ver.minor if ver.minor >= 0 else ''}" return _ret_str diff --git a/src/check_python_versions/versions.py b/src/check_python_versions/versions.py index 0aff51c..38f5a4f 100644 --- a/src/check_python_versions/versions.py +++ b/src/check_python_versions/versions.py @@ -43,9 +43,10 @@ class Version(NamedTuple): major: int = -1 # I'd've preferred to use None, but it complicates sorting minor: int = -1 suffix: str = '' + has_dot: bool = False @classmethod - def from_string(cls, v: str) -> 'Version': + def from_string(cls, v: str, has_dot: bool = False) -> 'Version': m = VERSION_RX.match(v) assert m is not None prefix, major, minor, suffix = m.groups() @@ -54,6 +55,7 @@ def from_string(cls, v: str) -> 'Version': int(major) if major else -1, int(minor[1:]) if minor else -1, suffix, + has_dot, ) def __repr__(self) -> str: @@ -62,6 +64,7 @@ def __repr__(self) -> str: f'major={self.major!r}' if self.major != -1 else '', f'minor={self.minor!r}' if self.minor != -1 else '', f'suffix={self.suffix!r}' if self.suffix else '', + f'dot={self.has_dot!r}' if self.has_dot else '', ] if part)) def __str__(self) -> str: From 877b77ce59f7b4b22f378e26052b2bc2fa2abb0f Mon Sep 17 00:00:00 2001 From: gpongelli Date: Sat, 4 Feb 2023 21:25:20 +0100 Subject: [PATCH 5/5] tests for version and tox --- tests/sources/test_tox.py | 42 +++++++++++++++++++++++++++++++++------ tests/test_versions.py | 6 ++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/tests/sources/test_tox.py b/tests/sources/test_tox.py index 86a3cef..a34063d 100644 --- a/tests/sources/test_tox.py +++ b/tests/sources/test_tox.py @@ -15,8 +15,11 @@ from check_python_versions.versions import Version -def v(versions: List[str]) -> List[Version]: - return [Version.from_string(v) for v in versions] +def v(versions: List[str], has_dot: List[bool] = []) -> List[Version]: + if not has_dot: + has_dot = [False] * len(versions) + return [Version.from_string(v, has_dot=d) + for v, d in zip(versions, has_dot)] def test_get_tox_ini_python_versions(tmp_path): @@ -32,9 +35,10 @@ def test_get_tox_ini_dotted_python_versions(tmp_path): tox_ini = tmp_path / "tox.ini" tox_ini.write_text(textwrap.dedent("""\ [tox] - envlist = py2.7,py3.6,py27-docs,pylint,py3.10 + envlist = py2.7,py3.6,py2.7-docs,pylint,py3.10 """)) - assert get_tox_ini_python_versions(tox_ini) == v(['2.7', '3.6', '3.10']) + assert get_tox_ini_python_versions(tox_ini) == \ + v(['2.7', '3.6', '3.10'], has_dot=[True, True, True]) def test_get_tox_ini_python_versions_syntax_error(tmp_path): @@ -122,7 +126,9 @@ def test_update_tox_ini_dotted_python_versions(): envlist = py2.6, py2.7 """)) fp.name = 'tox.ini' - result = update_tox_ini_python_versions(fp, v(['3.6', '3.7', '3.10'])) + result = update_tox_ini_python_versions(fp, + v(['3.6', '3.7', '3.10'], + has_dot=[True, True, True])) assert "".join(result) == textwrap.dedent("""\ [tox] envlist = py3.6, py3.7, py3.10 @@ -135,13 +141,30 @@ def test_update_tox_ini_one_dotted_python_versions(): envlist = py26, py2.7 """)) fp.name = 'tox.ini' - result = update_tox_ini_python_versions(fp, v(['3.6', '3.7', '3.10'])) + result = update_tox_ini_python_versions(fp, + v(['3.6', '3.7', '3.10'], + has_dot=[True, True, True])) assert "".join(result) == textwrap.dedent("""\ [tox] envlist = py3.6, py3.7, py3.10 """) +def test_update_tox_ini_mixed_dotted_python_versions(): + fp = StringIO(textwrap.dedent("""\ + [tox] + envlist = py26, py2.7 + """)) + fp.name = 'tox.ini' + result = update_tox_ini_python_versions(fp, + v(['3.6', '3.7', '3.10'], + has_dot=[True, False, True])) + assert "".join(result) == textwrap.dedent("""\ + [tox] + envlist = py3.6, py37, py3.10 + """) + + def test_update_tox_ini_python_syntax_error(capsys): fp = StringIO(textwrap.dedent("""\ [tox @@ -189,6 +212,13 @@ def test_update_tox_envlist_with_spaces(): assert result == 'py36, py37, pypy3' +def test_update_tox_envlist_with_dots_and_spaces(): + result = update_tox_envlist( + 'py27, py34, py35', + v(['3.6', '3.7'], has_dot=[True, False])) + assert result == 'py3.6, py37' + + @pytest.mark.parametrize('s, expected', [ # note that configparser trims leading whitespace, so \n is never # followed by a space diff --git a/tests/test_versions.py b/tests/test_versions.py index d814613..9da3598 100644 --- a/tests/test_versions.py +++ b/tests/test_versions.py @@ -13,6 +13,10 @@ def test_version_from_string() -> None: assert Version.from_string('3') == Version(major=3) assert Version.from_string('3.0') == Version(major=3, minor=0) assert Version.from_string('3.6') == Version(major=3, minor=6) + assert Version.from_string('3.6') == \ + Version(major=3, minor=6, has_dot=False) + assert Version.from_string('3.6', has_dot=True) == \ + Version(major=3, minor=6, has_dot=True) assert Version.from_string('3.10-dev') == Version('', 3, 10, '-dev') assert Version.from_string('PyPy') == Version('PyPy') assert Version.from_string('PyPy3') == Version('PyPy', 3) @@ -26,6 +30,8 @@ def test_version_repr() -> None: assert repr(Version(major=3)) == 'Version(major=3)' assert repr(Version(major=3, minor=0)) == 'Version(major=3, minor=0)' assert repr(Version(major=3, minor=6)) == 'Version(major=3, minor=6)' + assert repr(Version(major=3, minor=6, has_dot=True)) == \ + 'Version(major=3, minor=6, dot=True)' assert repr(Version(major=3, minor=10, suffix='-dev')) == \ "Version(major=3, minor=10, suffix='-dev')" assert repr(Version(prefix='PyPy')) == "Version(prefix='PyPy')"