Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Nov 26, 2025

📄 6% (0.06x) speedup for _parse_letter_version in src/packaging/version.py

⏱️ Runtime : 2.82 milliseconds 2.67 milliseconds (best of 91 runs)

📝 Explanation and details

The optimization replaces a chain of if/elif statements with a dictionary lookup for letter normalization, yielding a 5% speedup overall.

Key optimization: The original code used sequential if/elif checks to normalize letter aliases (e.g., "alpha" → "a", "beta" → "b"), requiring up to 5 comparisons in the worst case. The optimized version uses a pre-built dictionary _LETTER_NORMALIZATION with .get(ltr, ltr) for O(1) lookup regardless of which alias is being normalized.

Performance impact by case type:

  • Alias normalization cases (rev, r, pre, preview, c): 15-58% faster - these benefit most as they previously required multiple string comparisons
  • Direct letters (alpha, beta): slightly slower due to dictionary lookup overhead vs. immediate first/second comparison hits
  • Non-normalized letters (rc, post, dev): 15-40% faster - avoid the entire if/elif chain
  • None/number-only cases: minimal impact

Hot path relevance: This function is called 3 times per version parsing (for pre, post, and dev components) in the Version.__init__() constructor. Given that version parsing is fundamental to package management operations, this optimization provides meaningful cumulative benefits across dependency resolution, version comparison, and package installation workflows.

Test results show the optimization excels with less common aliases and unrecognized letters, while having minimal overhead for the most frequent cases (alpha/beta), making it a net win for real-world version parsing workloads.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 6851 Passed
⏪ Replay Tests 255 Passed
🔎 Concolic Coverage Tests 4 Passed
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
from __future__ import annotations

from typing import SupportsInt

# imports
import pytest
from src.packaging.version import _parse_letter_version

# unit tests

# -------------------------
# Basic Test Cases
# -------------------------

def test_alpha_with_number():
    # Basic: "alpha" letter with number
    codeflash_output = _parse_letter_version("alpha", "1") # 1.21μs -> 1.39μs (12.6% slower)

def test_beta_with_number():
    # Basic: "beta" letter with number
    codeflash_output = _parse_letter_version("beta", 5) # 1.18μs -> 1.25μs (5.60% slower)

def test_rc_with_number():
    # Basic: "rc" letter with number
    codeflash_output = _parse_letter_version("rc", "2") # 1.52μs -> 1.30μs (17.1% faster)

def test_post_with_number():
    # Basic: "post" letter with number
    codeflash_output = _parse_letter_version("post", "10") # 1.55μs -> 1.34μs (15.8% faster)

def test_letter_with_no_number():
    # Basic: letter provided, no number (should default to 0)
    codeflash_output = _parse_letter_version("a", None) # 1.46μs -> 1.12μs (30.6% faster)

def test_none_letter_number():
    # Basic: both letter and number are None (should return None)
    codeflash_output = _parse_letter_version(None, None) # 499ns -> 511ns (2.35% slower)

def test_number_no_letter():
    # Basic: number provided, no letter (should default to "post")
    codeflash_output = _parse_letter_version(None, "7") # 865ns -> 874ns (1.03% slower)

def test_number_no_letter_int():
    # Basic: number provided as int, no letter
    codeflash_output = _parse_letter_version(None, 3) # 701ns -> 679ns (3.24% faster)

# -------------------------
# Edge Test Cases
# -------------------------

def test_letter_case_insensitivity():
    # Edge: letter case insensitivity
    codeflash_output = _parse_letter_version("ALPHA", "2") # 1.18μs -> 1.47μs (19.7% slower)
    codeflash_output = _parse_letter_version("Beta", "4") # 759ns -> 752ns (0.931% faster)
    codeflash_output = _parse_letter_version("RC", "5") # 866ns -> 607ns (42.7% faster)
    codeflash_output = _parse_letter_version("PoSt", "6") # 611ns -> 497ns (22.9% faster)

def test_letter_aliases():
    # Edge: alternate spellings
    codeflash_output = _parse_letter_version("pre", "1") # 1.48μs -> 1.41μs (5.05% faster)
    codeflash_output = _parse_letter_version("preview", "2") # 822ns -> 714ns (15.1% faster)
    codeflash_output = _parse_letter_version("c", "3") # 593ns -> 536ns (10.6% faster)
    codeflash_output = _parse_letter_version("rev", "4") # 681ns -> 481ns (41.6% faster)
    codeflash_output = _parse_letter_version("r", "5") # 619ns -> 435ns (42.3% faster)

def test_number_as_bytes():
    # Edge: number as bytes
    codeflash_output = _parse_letter_version("b", b"7") # 1.66μs -> 1.36μs (22.2% faster)
    codeflash_output = _parse_letter_version(None, b"8") # 495ns -> 501ns (1.20% slower)

def test_number_as_SupportsInt():
    # Edge: number as object with __int__ (SupportsInt)
    class DummyInt:
        def __int__(self):
            return 42
    codeflash_output = _parse_letter_version("a", DummyInt()) # 1.91μs -> 1.66μs (15.1% faster)
    codeflash_output = _parse_letter_version(None, DummyInt()) # 586ns -> 559ns (4.83% faster)

def test_letter_empty_string():
    # Edge: letter is empty string (should treat as no letter)
    codeflash_output = _parse_letter_version("", "9") # 845ns -> 871ns (2.99% slower)

def test_number_empty_string():
    # Edge: number is empty string (should default to 0 if letter given)
    codeflash_output = _parse_letter_version("b", "")
    # If letter is None and number is "", int("") raises ValueError
    with pytest.raises(ValueError):
        _parse_letter_version(None, "")

def test_number_zero():
    # Edge: explicit zero as number
    codeflash_output = _parse_letter_version("rc", 0) # 1.48μs -> 1.21μs (22.6% faster)
    codeflash_output = _parse_letter_version(None, 0) # 316ns -> 304ns (3.95% faster)

def test_letter_none_number_zero():
    # Edge: letter None, number 0
    codeflash_output = _parse_letter_version(None, 0) # 476ns -> 469ns (1.49% faster)

def test_letter_and_number_none():
    # Edge: both arguments None
    codeflash_output = _parse_letter_version(None, None) # 473ns -> 479ns (1.25% slower)

def test_invalid_number_type():
    # Edge: number is not int/str/bytes/SupportsInt (e.g. list)
    with pytest.raises(TypeError):
        _parse_letter_version("a", [1, 2, 3]) # 3.23μs -> 3.06μs (5.53% faster)
    with pytest.raises(TypeError):
        _parse_letter_version(None, [1, 2, 3]) # 1.66μs -> 1.60μs (3.49% faster)

def test_invalid_letter_type():
    # Edge: letter is not str/None
    with pytest.raises(AttributeError):
        _parse_letter_version(123, "1") # 2.27μs -> 2.26μs (0.487% faster)

def test_letter_with_leading_trailing_spaces():
    # Edge: letter with spaces (should not normalize, will not match aliases)
    # Should lower, but not strip
    codeflash_output = _parse_letter_version(" alpha ", "1") # 1.60μs -> 1.24μs (29.0% faster)

def test_number_with_leading_trailing_spaces():
    # Edge: number with spaces (int(" 1 ") is valid)
    codeflash_output = _parse_letter_version("a", " 1 ") # 1.62μs -> 1.32μs (23.0% faster)
    codeflash_output = _parse_letter_version(None, " 2 ") # 513ns -> 502ns (2.19% faster)

def test_number_invalid_string():
    # Edge: number is invalid string (should raise ValueError)
    with pytest.raises(ValueError):
        _parse_letter_version("a", "notanumber") # 5.03μs -> 4.61μs (9.22% faster)
    with pytest.raises(ValueError):
        _parse_letter_version(None, "notanumber") # 2.00μs -> 1.86μs (7.08% faster)

def test_letter_numeric_string():
    # Edge: letter is a numeric string (should not normalize, just lower)
    codeflash_output = _parse_letter_version("123", "5") # 1.53μs -> 1.42μs (8.40% faster)

def test_number_none_explicit():
    # Edge: number is explicitly None (should default to 0 if letter given)
    codeflash_output = _parse_letter_version("b", None) # 1.38μs -> 1.14μs (21.4% faster)

def test_number_false():
    # Edge: number is False (int(False) == 0)
    codeflash_output = _parse_letter_version("rc", False) # 1.35μs -> 1.11μs (21.2% faster)
    codeflash_output = _parse_letter_version(None, False) # 315ns -> 321ns (1.87% slower)

def test_letter_false():
    # Edge: letter is False (should treat as no letter)
    codeflash_output = _parse_letter_version(False, "1") # 875ns -> 838ns (4.42% faster)

# -------------------------
# Large Scale Test Cases
# -------------------------

def test_many_numeric_versions():
    # Large scale: many numeric post-releases
    for i in range(0, 1000):
        codeflash_output = _parse_letter_version(None, str(i)) # 310μs -> 299μs (3.84% faster)

def test_many_letter_versions():
    # Large scale: many lettered pre-releases
    for i in range(0, 1000):
        codeflash_output = _parse_letter_version("rc", i) # 390μs -> 345μs (13.2% faster)

def test_many_aliases():
    # Large scale: all aliases, many numbers
    aliases = ["alpha", "beta", "c", "pre", "preview", "rev", "r"]
    expected = {
        "alpha": "a",
        "beta": "b",
        "c": "rc",
        "pre": "rc",
        "preview": "rc",
        "rev": "post",
        "r": "post",
    }
    for alias in aliases:
        for i in range(0, 100):
            codeflash_output = _parse_letter_version(alias, i)

def test_large_inputs_with_bytes():
    # Large scale: numbers as bytes
    for i in range(0, 1000):
        codeflash_output = _parse_letter_version("b", str(i).encode()) # 442μs -> 380μs (16.3% faster)

def test_large_supportsint_objects():
    # Large scale: numbers as SupportsInt objects
    class DummyInt:
        def __init__(self, value):
            self.value = value
        def __int__(self):
            return self.value
    for i in range(0, 1000):
        codeflash_output = _parse_letter_version("a", DummyInt(i)) # 461μs -> 402μs (14.6% faster)

def test_large_mixed_case_letters():
    # Large scale: mixed case letters
    for i in range(0, 1000):
        letter = "ALPHA" if i % 2 == 0 else "alpha"
        codeflash_output = _parse_letter_version(letter, i) # 327μs -> 364μs (10.0% slower)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from typing import SupportsInt

# imports
import pytest
from src.packaging.version import _parse_letter_version

# unit tests

# ========== BASIC TEST CASES ==========

def test_basic_alpha_with_number():
    # Test 'alpha' normalization with number
    codeflash_output = _parse_letter_version("alpha", "2") # 1.19μs -> 1.44μs (17.1% slower)

def test_basic_beta_with_number():
    # Test 'beta' normalization with number
    codeflash_output = _parse_letter_version("beta", 3) # 1.21μs -> 1.25μs (2.88% slower)

def test_basic_rc_with_number():
    # Test 'rc' with number
    codeflash_output = _parse_letter_version("rc", 4) # 1.34μs -> 1.15μs (15.8% faster)

def test_basic_post_with_number():
    # Test 'post' with number
    codeflash_output = _parse_letter_version("post", 5) # 1.38μs -> 1.18μs (17.0% faster)

def test_basic_letter_none_number_none():
    # Both letter and number are None
    codeflash_output = _parse_letter_version(None, None) # 457ns -> 490ns (6.73% slower)

def test_basic_letter_none_number_int():
    # Only number provided, letter is None
    codeflash_output = _parse_letter_version(None, 7) # 674ns -> 665ns (1.35% faster)

def test_basic_letter_none_number_str():
    # Only number provided as string
    codeflash_output = _parse_letter_version(None, "8") # 894ns -> 861ns (3.83% faster)

def test_basic_letter_case_insensitivity():
    # Letter should be normalized to lower case
    codeflash_output = _parse_letter_version("ALPHA", 1) # 1.13μs -> 1.32μs (14.5% slower)
    codeflash_output = _parse_letter_version("Beta", "2") # 795ns -> 905ns (12.2% slower)
    codeflash_output = _parse_letter_version("RC", 3) # 812ns -> 574ns (41.5% faster)
    codeflash_output = _parse_letter_version("PoSt", 4) # 561ns -> 453ns (23.8% faster)

def test_basic_letter_aliases():
    # Test alternate spellings/aliases
    codeflash_output = _parse_letter_version("pre", 1) # 1.39μs -> 1.24μs (12.2% faster)
    codeflash_output = _parse_letter_version("preview", 2) # 724ns -> 699ns (3.58% faster)
    codeflash_output = _parse_letter_version("c", 3) # 550ns -> 487ns (12.9% faster)
    codeflash_output = _parse_letter_version("rev", 4) # 642ns -> 405ns (58.5% faster)
    codeflash_output = _parse_letter_version("r", 5) # 571ns -> 382ns (49.5% faster)

def test_basic_implicit_zero():
    # If number is None, should default to 0
    codeflash_output = _parse_letter_version("alpha", None) # 1.07μs -> 1.27μs (16.3% slower)
    codeflash_output = _parse_letter_version("beta", None) # 652ns -> 667ns (2.25% slower)
    codeflash_output = _parse_letter_version("rc", None) # 787ns -> 518ns (51.9% faster)
    codeflash_output = _parse_letter_version("post", None) # 520ns -> 449ns (15.8% faster)
    codeflash_output = _parse_letter_version("pre", None) # 543ns -> 496ns (9.48% faster)
    codeflash_output = _parse_letter_version("rev", None) # 560ns -> 381ns (47.0% faster)

# ========== EDGE TEST CASES ==========

def test_edge_letter_empty_string():
    # Empty string as letter should not match any normalization
    codeflash_output = _parse_letter_version("", 1) # 741ns -> 687ns (7.86% faster)

def test_edge_number_empty_string():
    # Empty string as number should default to 0 if letter is present
    codeflash_output = _parse_letter_version("alpha", "")
    # If letter is None and number is empty string, should raise ValueError
    with pytest.raises(ValueError):
        _parse_letter_version(None, "")

def test_edge_number_bytes():
    # Number as bytes should be converted to int
    codeflash_output = _parse_letter_version("alpha", b"12") # 1.40μs -> 1.53μs (9.06% slower)
    codeflash_output = _parse_letter_version(None, b"34") # 476ns -> 489ns (2.66% slower)

def test_edge_number_supportsint():
    # Custom class implementing SupportsInt
    class MyInt:
        def __int__(self):
            return 42
    codeflash_output = _parse_letter_version("beta", MyInt()) # 1.57μs -> 1.77μs (11.6% slower)
    codeflash_output = _parse_letter_version(None, MyInt()) # 488ns -> 507ns (3.75% slower)

def test_edge_number_zero_string():
    # String "0" as number
    codeflash_output = _parse_letter_version("rc", "0") # 1.60μs -> 1.35μs (18.5% faster)
    codeflash_output = _parse_letter_version(None, "0") # 540ns -> 542ns (0.369% slower)

def test_edge_letter_unusual():
    # Unrecognized letter should be lowercased and returned as-is
    codeflash_output = _parse_letter_version("dev", 1) # 1.43μs -> 1.26μs (13.5% faster)
    codeflash_output = _parse_letter_version("zzz", 2) # 661ns -> 535ns (23.6% faster)
    codeflash_output = _parse_letter_version("A", 3) # 606ns -> 429ns (41.3% faster)

def test_edge_number_negative():
    # Negative number as string/int
    codeflash_output = _parse_letter_version("rc", "-1") # 1.51μs -> 1.31μs (14.9% faster)
    codeflash_output = _parse_letter_version(None, -2) # 546ns -> 560ns (2.50% slower)

def test_edge_number_float_string():
    # Should raise ValueError if number cannot be converted to int
    with pytest.raises(ValueError):
        _parse_letter_version("alpha", "1.5") # 4.62μs -> 4.83μs (4.37% slower)
    with pytest.raises(ValueError):
        _parse_letter_version(None, "2.7") # 1.99μs -> 1.93μs (3.27% faster)

def test_edge_number_non_numeric_string():
    # Should raise ValueError for non-numeric string
    with pytest.raises(ValueError):
        _parse_letter_version("beta", "abc") # 4.31μs -> 4.43μs (2.86% slower)
    with pytest.raises(ValueError):
        _parse_letter_version(None, "xyz") # 1.93μs -> 1.90μs (1.63% faster)

def test_edge_number_none_letter_unusual():
    # Letter present but number None, letter not normalized
    codeflash_output = _parse_letter_version("dev", None) # 1.44μs -> 1.21μs (19.4% faster)

def test_edge_number_none_letter_empty():
    # Both letter and number are empty
    codeflash_output = _parse_letter_version("", None) # 492ns -> 517ns (4.84% slower)

def test_edge_number_none_letter_whitespace():
    # Letter is whitespace, should not match normalization
    codeflash_output = _parse_letter_version(" ", None) # 1.46μs -> 1.16μs (26.0% faster)
    codeflash_output = _parse_letter_version(" ", "1") # 833ns -> 663ns (25.6% faster)

def test_edge_number_whitespace():
    # Number is whitespace string, should raise ValueError
    with pytest.raises(ValueError):
        _parse_letter_version("alpha", " ") # 4.37μs -> 4.58μs (4.71% slower)
    with pytest.raises(ValueError):
        _parse_letter_version(None, " ") # 1.86μs -> 1.91μs (2.30% slower)

# ========== LARGE SCALE TEST CASES ==========

def test_large_scale_many_versions():
    # Test scalability with a large number of inputs
    results = []
    for i in range(1000):
        # Alternate letters and numbers, including aliases and None
        if i % 5 == 0:
            results.append(_parse_letter_version("alpha", i))
        elif i % 5 == 1:
            results.append(_parse_letter_version("beta", str(i)))
        elif i % 5 == 2:
            results.append(_parse_letter_version("pre", i))
        elif i % 5 == 3:
            results.append(_parse_letter_version(None, i))
        else:
            results.append(_parse_letter_version("dev", i))

def test_large_scale_all_aliases():
    # Test all aliases in a large batch
    aliases = ["alpha", "beta", "pre", "preview", "c", "rev", "r"]
    expected = [("a", 0), ("b", 0), ("rc", 0), ("rc", 0), ("rc", 0), ("post", 0), ("post", 0)]
    for i, alias in enumerate(aliases):
        codeflash_output = _parse_letter_version(alias, None) # 4.48μs -> 4.11μs (9.10% faster)
        codeflash_output = _parse_letter_version(alias.upper(), None)

def test_large_scale_supportsint_objects():
    # Many SupportsInt objects
    class Num:
        def __init__(self, n): self.n = n
        def __int__(self): return self.n
    for i in range(0, 1000, 100):
        codeflash_output = _parse_letter_version("alpha", Num(i)) # 5.55μs -> 6.07μs (8.61% slower)
        codeflash_output = _parse_letter_version(None, Num(i))

def test_large_scale_bytes_numbers():
    # Many numbers as bytes
    for i in range(0, 1000, 100):
        b = str(i).encode()
        codeflash_output = _parse_letter_version("rc", b) # 6.18μs -> 5.30μs (16.6% faster)
        codeflash_output = _parse_letter_version(None, b)

def test_large_scale_unusual_letters():
    # Many unusual letters, should be lowercased and returned as-is
    for i in range(0, 1000, 100):
        letter = f"dev{i}"
        codeflash_output = _parse_letter_version(letter, i) # 5.71μs -> 5.08μs (12.4% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from src.packaging.version import _parse_letter_version

def test__parse_letter_version():
    _parse_letter_version('r', None)

def test__parse_letter_version_2():
    _parse_letter_version('', -1)

def test__parse_letter_version_3():
    _parse_letter_version('c', None)

def test__parse_letter_version_4():
    _parse_letter_version('', 0)
⏪ Replay Tests and Runtime
Test File::Test Function Original ⏱️ Optimized ⏱️ Speedup
test_pytest_teststest_version_py__replay_test_0.py::test_src_packaging_version__parse_letter_version 124μs 122μs 1.92%✅
🔎 Concolic Coverage Tests and Runtime
Test File::Test Function Original ⏱️ Optimized ⏱️ Speedup
codeflash_concolic_xxcmgf2t/tmp7s4ecq8p/test_concolic_coverage.py::test__parse_letter_version 1.62μs 1.31μs 23.8%✅
codeflash_concolic_xxcmgf2t/tmp7s4ecq8p/test_concolic_coverage.py::test__parse_letter_version_2 761ns 712ns 6.88%✅
codeflash_concolic_xxcmgf2t/tmp7s4ecq8p/test_concolic_coverage.py::test__parse_letter_version_3 1.37μs 1.28μs 6.78%✅
codeflash_concolic_xxcmgf2t/tmp7s4ecq8p/test_concolic_coverage.py::test__parse_letter_version_4 536ns 514ns 4.28%✅

To edit these changes git checkout codeflash/optimize-_parse_letter_version-migi3mpk and push.

Codeflash Static Badge

The optimization replaces a chain of `if/elif` statements with a dictionary lookup for letter normalization, yielding a **5% speedup** overall.

**Key optimization**: The original code used sequential `if/elif` checks to normalize letter aliases (e.g., "alpha" → "a", "beta" → "b"), requiring up to 5 comparisons in the worst case. The optimized version uses a pre-built dictionary `_LETTER_NORMALIZATION` with `.get(ltr, ltr)` for O(1) lookup regardless of which alias is being normalized.

**Performance impact by case type**:
- **Alias normalization cases** (rev, r, pre, preview, c): **15-58% faster** - these benefit most as they previously required multiple string comparisons
- **Direct letters** (alpha, beta): **slightly slower** due to dictionary lookup overhead vs. immediate first/second comparison hits
- **Non-normalized letters** (rc, post, dev): **15-40% faster** - avoid the entire if/elif chain
- **None/number-only cases**: minimal impact

**Hot path relevance**: This function is called **3 times per version parsing** (for pre, post, and dev components) in the `Version.__init__()` constructor. Given that version parsing is fundamental to package management operations, this optimization provides meaningful cumulative benefits across dependency resolution, version comparison, and package installation workflows.

**Test results show** the optimization excels with less common aliases and unrecognized letters, while having minimal overhead for the most frequent cases (alpha/beta), making it a net win for real-world version parsing workloads.
@codeflash-ai codeflash-ai bot requested a review from KRRT7 November 26, 2025 21:12
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Nov 26, 2025
@henryiii
Copy link

henryiii commented Dec 9, 2025

Changing letter to ltr is unnecessary, but otherwise I think this might be more readable, and it's probably a pretty hot path.

@KRRT7 KRRT7 closed this Dec 10, 2025
@codeflash-ai codeflash-ai bot deleted the codeflash/optimize-_parse_letter_version-migi3mpk branch December 10, 2025 01:26
@KRRT7
Copy link
Owner

KRRT7 commented Dec 10, 2025

merged with changes upstream

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants