diff --git a/tdd-ai-agents/.claude/settings.json b/tdd-ai-agents/.claude/settings.json new file mode 100644 index 0000000000..4f1bb96858 --- /dev/null +++ b/tdd-ai-agents/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "permissions": { + "deny": ["Edit(tests/**)"] + } +} \ No newline at end of file diff --git a/tdd-ai-agents/.gitignore b/tdd-ai-agents/.gitignore new file mode 100644 index 0000000000..38876dcbd2 --- /dev/null +++ b/tdd-ai-agents/.gitignore @@ -0,0 +1,13 @@ +# Virtual environment +venv/ +.venv/ + +# Python cache +__pycache__/ +*.py[cod] + +# Testing +.pytest_cache/ + +# OS files +.DS_Store diff --git a/tdd-ai-agents/README.md b/tdd-ai-agents/README.md new file mode 100644 index 0000000000..5df6af529f --- /dev/null +++ b/tdd-ai-agents/README.md @@ -0,0 +1,2 @@ +# TDD With AI Agents: Design the Tests First, Let the Agent Implement +This folder contains sample code for the Real Python tutorial [TDD With AI Agents: Design the Tests First, Let the Agent Implement]. diff --git a/tdd-ai-agents/pyproject.toml b/tdd-ai-agents/pyproject.toml new file mode 100644 index 0000000000..20fb19b765 --- /dev/null +++ b/tdd-ai-agents/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "version-check" +version = "0.1.0" + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" diff --git a/tdd-ai-agents/tests/test_core.py b/tdd-ai-agents/tests/test_core.py new file mode 100644 index 0000000000..4a3d60b49e --- /dev/null +++ b/tdd-ai-agents/tests/test_core.py @@ -0,0 +1,70 @@ +import pytest +from version_check import Version + + +def test_equal_versions_compare_equal(): + assert Version("1.2.0") == Version("1.2.0") + + +def test_smaller_release_sorts_first(): + assert Version("1.2.0") < Version("1.3.0") + + +def test_versions_with_different_lengths_compare(): + assert Version("1.2") < Version("1.2.1") + + +def test_greater_than_works(): + assert Version("2.0.0") > Version("1.9.9") + + +def test_unequal_versions_are_not_equal(): + assert Version("1.0.0") != Version("1.0.1") + + +def test_double_digit_segments_sort_numerically(): + assert Version("3.9") < Version("3.10") + + +def test_sorting_a_list_of_versions(): + versions = [Version("1.10.0"), Version("1.2.0"), Version("1.9.0")] + assert sorted(versions) == [ + Version("1.2.0"), + Version("1.9.0"), + Version("1.10.0"), + ] + + +@pytest.mark.parametrize( + "lower, higher", + [ + ("1.0.dev1", "1.0"), + ("1.0a1", "1.0"), + ("1.0", "1.0.post1"), + ("1.0.dev1", "1.0a1"), + ("1.0a1", "1.0b1"), + ("1.0b1", "1.0rc1"), + ("1.0.dev1", "1.0.post1"), + ], +) +def test_phase_ordering(lower, higher): + assert Version(lower) < Version(higher) + + +@pytest.mark.parametrize( + "text", + [ + "", + " ", + "1.x.0", + "banana", + "1..0", + ], +) +def test_invalid_input_raises(text): + with pytest.raises(ValueError): + Version(text) + + +def test_comparing_against_other_types_returns_notimplemented(): + assert Version("1.0").__eq__("1.0") is NotImplemented diff --git a/tdd-ai-agents/version_check/__init__.py b/tdd-ai-agents/version_check/__init__.py new file mode 100644 index 0000000000..ec4fbc4920 --- /dev/null +++ b/tdd-ai-agents/version_check/__init__.py @@ -0,0 +1,3 @@ +from version_check.core import Version + +__all__ = ["Version"] diff --git a/tdd-ai-agents/version_check/core.py b/tdd-ai-agents/version_check/core.py new file mode 100644 index 0000000000..d4cfbc2480 --- /dev/null +++ b/tdd-ai-agents/version_check/core.py @@ -0,0 +1,48 @@ +import re +from functools import total_ordering + +_VERSION = re.compile( + r"^\s*(?P\d+(?:\.\d+)*)" + r"(?:(?Pa|b|rc)(?P\d+))?" + r"(?:\.post(?P\d+))?" + r"(?:\.dev(?P\d+))?\s*$" +) +_PRE_ORDER = {"a": 0, "b": 1, "rc": 2} + + +@total_ordering +class Version: + def __init__(self, text): + match = _VERSION.match(text) + if match is None: + raise ValueError(f"invalid version string: {text!r}") + self._release = tuple( + int(part) for part in match["release"].split(".") + ) + if match["pre_label"] is not None: + self._pre = (_PRE_ORDER[match["pre_label"]], int(match["pre_num"])) + else: + self._pre = None + self._post = int(match["post"]) if match["post"] else None + self._dev = int(match["dev"]) if match["dev"] else None + + def _key(self): + if self._dev is not None and self._pre is None: + phase = (0, self._dev) + elif self._pre is not None: + phase = (1, self._pre) + elif self._post is not None: + phase = (3, self._post) + else: + phase = (2, 0) + return (self._release, phase) + + def __eq__(self, other): + if not isinstance(other, Version): + return NotImplemented + return self._key() == other._key() + + def __lt__(self, other): + if not isinstance(other, Version): + return NotImplemented + return self._key() < other._key()