Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions tdd-ai-agents/.claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -e '.tool_input.file_path | contains(\"/tests/\")' >/dev/null && echo 'tests/ is read-only during implementation' >&2 && exit 2 || exit 0"
}
]
}
]
}
}
13 changes: 13 additions & 0 deletions tdd-ai-agents/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Virtual environment
venv/
.venv/

# Python cache
__pycache__/
*.py[cod]

# Testing
.pytest_cache/

# OS files
.DS_Store
2 changes: 2 additions & 0 deletions tdd-ai-agents/README.md
Original file line number Diff line number Diff line change
@@ -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].
7 changes: 7 additions & 0 deletions tdd-ai-agents/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
name = "version-check"
version = "0.1.0"

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
46 changes: 46 additions & 0 deletions tdd-ai-agents/tests/test_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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_double_digit_segments_sort_numerically():
assert Version("3.9") < Version("3.10")


@pytest.mark.parametrize(
"lower, higher",
[
("1.0.dev1", "1.0"), # Dev before final
("1.0a1", "1.0"), # Pre-release before final
("1.0", "1.0.post1"), # Post-release after final
("1.0.dev1", "1.0a1"), # Dev before pre-release
("1.0a1", "1.0b1"), # Alpha before beta
("1.0b1", "1.0rc1"), # Beta before release candidate
("1.0.dev1", "1.0.post1"), # Dev before post, spanning the release
],
)
def test_phase_ordering(lower, higher):
assert Version(lower) < Version(higher)


@pytest.mark.parametrize(
"text",
[
"", # Empty string
" ", # Whitespace only
"1.x.0", # Non-numeric segment
"banana", # Not a version at all
"1..0", # Empty segment
"v1.0", # Leading junk
],
)
def test_invalid_input_raises(text):
with pytest.raises(ValueError):
Version(text)
3 changes: 3 additions & 0 deletions tdd-ai-agents/version_check/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from version_check.core import Version

__all__ = ["Version"]
48 changes: 48 additions & 0 deletions tdd-ai-agents/version_check/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import re
from functools import total_ordering

_VERSION = re.compile(
r"^\s*(?P<release>\d+(?:\.\d+)*)"
r"(?:(?P<pre_label>a|b|rc)(?P<pre_num>\d+))?"
r"(?:\.dev(?P<dev>\d+))?"
r"(?:\.post(?P<post>\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._dev = int(match["dev"]) if match["dev"] else None
self._post = int(match["post"]) if match["post"] else None

def _key(self):
if self._dev is not None and self._pre is None and self._post 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()
Loading