Skip to content

Conversation

@codeflash-ai
Copy link

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

📄 28% (0.28x) speedup for Time.validate in src/bokeh/core/property/datetime.py

⏱️ Runtime : 4.31 milliseconds 3.38 milliseconds (best of 79 runs)

📝 Explanation and details

The optimized code achieves a 27% speedup by making two key changes to the Time.validate() method:

1. Removed superclass call overhead: The original code calls super().validate(value, detail), but the base Property.validate() method is just a pass statement (confirmed by line profiler showing it takes 60% of total time). Removing this eliminates unnecessary method call overhead.

2. Hoisted attribute lookup: The optimized code extracts datetime.time.fromisoformat into a local variable fromisoformat before the try block. This avoids repeatedly resolving the attribute access datetime.time.fromisoformat on each validation call, reducing attribute lookup overhead.

The line profiler shows the superclass call (super().validate(value, detail)) consumed 60.2% of the original runtime but is completely eliminated in the optimized version. The attribute hoisting provides additional micro-optimization benefits.

Performance gains vary by input type:

  • datetime.time objects: 71-88% faster (early return path benefits most from removing superclass overhead)
  • Valid ISO strings: 29-47% faster (benefits from both optimizations)
  • Invalid inputs: 9-41% faster (still benefits from removing superclass overhead before error handling)

The validate() method is likely called frequently during data validation workflows, making these optimizations valuable for applications processing large datasets with time values. The changes maintain identical functionality and error handling behavior while significantly reducing validation overhead.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 8246 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import datetime

# imports
import pytest
from bokeh.core.property.datetime import Time
# function to test (from bokeh.core.property.datetime.py)
from bokeh.core.property.singletons import Undefined

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

def test_validate_accepts_datetime_time_object():
    # Should accept a valid datetime.time object
    t = Time()
    value = datetime.time(12, 34, 56)
    t.validate(value) # 1.22μs -> 712ns (71.6% faster)

def test_validate_accepts_iso_string_hh_mm():
    # Should accept ISO string "HH:MM"
    t = Time()
    value = "09:15"
    t.validate(value) # 2.10μs -> 1.43μs (47.3% faster)

def test_validate_accepts_iso_string_hh_mm_ss():
    # Should accept ISO string "HH:MM:SS"
    t = Time()
    value = "23:59:59"
    t.validate(value) # 1.63μs -> 1.22μs (33.8% faster)

def test_validate_accepts_iso_string_with_microseconds():
    # Should accept ISO string "HH:MM:SS.ssssss"
    t = Time()
    value = "01:02:03.123456"
    t.validate(value) # 1.61μs -> 1.21μs (32.9% faster)

def test_validate_accepts_iso_string_with_leading_zeros():
    # Should accept ISO string "HH:MM:SS" with leading zeros
    t = Time()
    value = "00:00:00"
    t.validate(value) # 1.53μs -> 1.14μs (34.6% faster)

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

def test_validate_rejects_invalid_string_format():
    # Should reject strings not in ISO format
    t = Time()
    invalid_strings = [
        "25:00",         # hour out of range
        "12:60",         # minute out of range
        "12:34:60",      # second out of range
        "12:34:56:78",   # too many components
        "12-34-56",      # wrong separator
        "abc",           # not a time
        "",              # empty string
        "12:34:56.",     # trailing dot
        "12:34:56.abcdef", # non-numeric microseconds
        "12:34:56.1234567", # too many microseconds digits
    ]
    for s in invalid_strings:
        with pytest.raises(ValueError):
            t.validate(s)

def test_validate_rejects_non_string_non_time_types():
    # Should reject types other than str or datetime.time
    t = Time()
    invalid_types = [
        123,                    # int
        12.34,                  # float
        None,                   # NoneType
        object(),               # generic object
        datetime.datetime(2023, 1, 1, 12, 34, 56), # datetime.datetime
        [12, 34, 56],           # list
        {"hour": 12, "minute": 34}, # dict
        (12, 34, 56),           # tuple
        True,                   # bool
        b"12:34:56",            # bytes
    ]
    for v in invalid_types:
        with pytest.raises(ValueError):
            t.validate(v)

def test_validate_accepts_iso_string_with_min_max_time():
    # Should accept minimum and maximum valid times
    t = Time()
    t.validate("00:00:00") # 1.63μs -> 1.35μs (20.5% faster)
    t.validate("23:59:59.999999") # 635ns -> 574ns (10.6% faster)

def test_validate_accepts_time_object_with_min_max_time():
    # Should accept datetime.time objects at boundaries
    t = Time()
    t.validate(datetime.time(0, 0, 0)) # 873ns -> 493ns (77.1% faster)
    t.validate(datetime.time(23, 59, 59, 999999)) # 380ns -> 216ns (75.9% faster)

def test_validate_rejects_iso_string_with_negative_values():
    # Should reject negative values in ISO string
    t = Time()
    invalid = [
        "-01:00",
        "12:-34",
        "12:34:-56",
        "-12:-34:-56",
    ]
    for s in invalid:
        with pytest.raises(ValueError):
            t.validate(s)

def test_validate_rejects_iso_string_with_extra_spaces():
    # Should reject ISO string with spaces
    t = Time()
    invalid = [
        " 12:34:56",
        "12:34:56 ",
        "12 :34:56",
        "12: 34:56",
        "12:34 :56",
        "12:34: 56",
        "12:34:56. 123456",
        "12:34:56 .123456",
    ]
    for s in invalid:
        with pytest.raises(ValueError):
            t.validate(s)

def test_validate_accepts_iso_string_with_single_digit_hour_minute_second():
    # Should accept single digit hour, minute, second (ISO allows this)
    t = Time()
    valid = [
        "1:2:3",
        "9:8:7.654321",
        "0:0:0",
    ]
    for s in valid:
        t.validate(s)  # Should not raise

def test_validate_accepts_iso_string_with_microseconds_boundary():
    # Should accept microseconds at boundaries
    t = Time()
    t.validate("12:34:56.0")
    t.validate("12:34:56.999999")

def test_validate_rejects_iso_string_with_t_separator():
    # Should reject ISO string with 'T' separator (not allowed for time only)
    t = Time()
    with pytest.raises(ValueError):
        t.validate("12T34:56") # 3.73μs -> 3.40μs (9.62% faster)

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

def test_validate_many_valid_times():
    # Should accept a large number of valid time strings
    t = Time()
    # Generate 1000 valid times: "HH:MM:SS"
    for hour in range(0, 24):
        for minute in range(0, 60, 12):  # step to keep total under 1000
            for second in range(0, 60, 20):
                s = f"{hour:02}:{minute:02}:{second:02}"
                t.validate(s)  # Should not raise

def test_validate_many_invalid_times():
    # Should reject a large number of invalid time strings
    t = Time()
    # Generate 1000 invalid times: hour/minute/second out of range
    count = 0
    for hour in range(24, 34):  # invalid hour
        for minute in range(60, 70):  # invalid minute
            for second in range(60, 70):  # invalid second
                if count >= 1000:
                    break
                s = f"{hour:02}:{minute:02}:{second:02}"
                with pytest.raises(ValueError):
                    t.validate(s)
                count += 1

def test_validate_performance_on_large_batch():
    # Should process 1000 valid and 1000 invalid values quickly
    t = Time()
    valid = [f"{h:02}:{m:02}:{s:02}" for h in range(10) for m in range(10) for s in range(10)]
    invalid = [f"{h:02}:{m:02}:{s:02}" for h in range(24, 34) for m in range(60, 70) for s in range(60, 70)]
    # Check valid
    for s in valid:
        t.validate(s) # 388μs -> 292μs (33.1% faster)
    # Check invalid
    for s in invalid[:1000]:
        with pytest.raises(ValueError):
            t.validate(s)

def test_validate_accepts_large_number_of_time_objects():
    # Should accept a large number of datetime.time objects
    t = Time()
    times = [datetime.time(h, m, s) for h in range(10) for m in range(10) for s in range(10)]
    for time_obj in times:
        t.validate(time_obj) # 277μs -> 176μs (57.0% faster)

def test_validate_rejects_large_number_of_non_time_objects():
    # Should reject a large number of non-time objects
    t = Time()
    invalid_objects = [None, 123, 12.34, True, object()] * 200  # 1000 elements
    for v in invalid_objects:
        with pytest.raises(ValueError):
            t.validate(v)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
import datetime

# imports
import pytest
from bokeh.core.property.datetime import Time

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

def test_validate_accepts_valid_time_object():
    # Should accept a valid datetime.time object
    t = Time()
    t.validate(datetime.time(12, 34, 56)) # 1.16μs -> 625ns (85.6% faster)

def test_validate_accepts_valid_iso_string():
    # Should accept valid ISO formatted time strings
    t = Time()
    t.validate("12:34:56") # 1.91μs -> 1.40μs (36.6% faster)

def test_validate_accepts_iso_string_with_microseconds():
    # Should accept ISO formatted time string with microseconds
    t = Time()
    t.validate("12:34:56.123456") # 1.71μs -> 1.20μs (43.2% faster)

def test_validate_accepts_iso_string_with_minute_and_hour_only():
    # Should accept ISO formatted time string with hour and minute only
    t = Time()
    t.validate("23:59") # 1.63μs -> 1.14μs (43.1% faster)

def test_validate_accepts_midnight_time_object():
    # Should accept midnight as a time object
    t = Time()
    t.validate(datetime.time(0, 0, 0)) # 971ns -> 515ns (88.5% faster)

def test_validate_accepts_midnight_iso_string():
    # Should accept midnight as a string
    t = Time()
    t.validate("00:00:00") # 1.65μs -> 1.28μs (29.3% faster)

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

def test_validate_rejects_non_time_object():
    # Should reject objects that are not time or string
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate(12345) # 1.77μs -> 1.35μs (30.5% faster)

def test_validate_rejects_invalid_iso_string():
    # Should reject invalid ISO time strings
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("not-a-time") # 3.10μs -> 2.60μs (19.2% faster)

def test_validate_rejects_invalid_time_string_format():
    # Should reject strings that are not ISO formatted
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("12-34-56") # 2.82μs -> 2.34μs (20.6% faster)

def test_validate_rejects_date_string():
    # Should reject date strings
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("2023-06-01") # 2.88μs -> 2.22μs (29.6% faster)

def test_validate_rejects_empty_string():
    # Should reject empty string
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("") # 2.76μs -> 2.22μs (24.5% faster)

def test_validate_rejects_none():
    # Should reject None
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate(None) # 1.81μs -> 1.28μs (41.6% faster)

def test_validate_accepts_time_with_tzinfo():
    # Should accept time object with tzinfo
    t = Time()
    tz = datetime.timezone.utc
    t.validate(datetime.time(12, 34, 56, tzinfo=tz)) # 1.00μs -> 539ns (86.1% faster)

def test_validate_accepts_iso_string_with_fractional_seconds():
    # Should accept ISO formatted time string with fractional seconds
    t = Time()
    t.validate("12:34:56.1")  # No exception expected

def test_validate_accepts_iso_string_with_zero_fractional_seconds():
    # Should accept ISO formatted time string with .0 fractional seconds
    t = Time()
    t.validate("12:34:56.0")  # No exception expected

def test_validate_rejects_iso_string_with_invalid_fractional_seconds():
    # Should reject ISO string with invalid fractional seconds
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("12:34:56.x") # 3.78μs -> 3.35μs (12.6% faster)

def test_validate_rejects_iso_string_with_extra_colon():
    # Should reject ISO string with extra colon
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("12:34:56:78") # 2.96μs -> 2.41μs (22.6% faster)

def test_validate_accepts_iso_string_with_leading_zero_hour():
    # Should accept ISO string with leading zero hour
    t = Time()
    t.validate("01:02:03") # 1.91μs -> 1.42μs (34.4% faster)

def test_validate_accepts_iso_string_with_leading_zero_minute():
    # Should accept ISO string with leading zero minute
    t = Time()
    t.validate("12:01:03") # 1.76μs -> 1.24μs (42.3% faster)

def test_validate_accepts_iso_string_with_leading_zero_second():
    # Should accept ISO string with leading zero second
    t = Time()
    t.validate("12:34:01") # 1.71μs -> 1.19μs (44.4% faster)

def test_validate_accepts_iso_string_with_max_time():
    # Should accept ISO string with max time
    t = Time()
    t.validate("23:59:59.999999") # 1.62μs -> 1.20μs (35.5% faster)

def test_validate_accepts_time_object_with_max_time():
    # Should accept time object with max time
    t = Time()
    t.validate(datetime.time(23, 59, 59, 999999)) # 906ns -> 552ns (64.1% faster)

def test_validate_accepts_iso_string_with_min_time():
    # Should accept ISO string with min time
    t = Time()
    t.validate("00:00:00.000000") # 1.71μs -> 1.28μs (34.1% faster)

def test_validate_accepts_time_object_with_min_time():
    # Should accept time object with min time
    t = Time()
    t.validate(datetime.time(0, 0, 0, 0)) # 946ns -> 521ns (81.6% faster)

def test_validate_rejects_string_with_spaces():
    # Should reject string with spaces
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate(" 12:34:56 ") # 3.19μs -> 2.75μs (16.0% faster)

def test_validate_rejects_string_with_tab():
    # Should reject string with tab
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("\t12:34:56") # 2.93μs -> 2.50μs (16.8% faster)

def test_validate_rejects_string_with_newline():
    # Should reject string with newline
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("12:34:56\n") # 2.88μs -> 2.26μs (27.1% faster)

def test_validate_rejects_string_with_comma_separator():
    # Should reject string with comma separator
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("12,34,56") # 2.68μs -> 2.30μs (16.3% faster)

def test_validate_rejects_list():
    # Should reject a list
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate([12, 34, 56]) # 2.86μs -> 2.42μs (18.3% faster)

def test_validate_rejects_dict():
    # Should reject a dict
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate({'hour': 12, 'minute': 34, 'second': 56}) # 3.14μs -> 2.70μs (16.3% faster)

def test_validate_rejects_bool():
    # Should reject a boolean
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate(True) # 2.08μs -> 1.57μs (32.5% faster)

def test_validate_rejects_float():
    # Should reject a float
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate(12.34) # 3.31μs -> 2.98μs (11.0% faster)

def test_validate_rejects_bytes():
    # Should reject bytes
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate(b"12:34:56") # 1.94μs -> 1.45μs (34.2% faster)

def test_validate_rejects_object():
    # Should reject an arbitrary object
    t = Time()
    class Dummy: pass
    with pytest.raises(ValueError) as excinfo:
        t.validate(Dummy()) # 3.08μs -> 2.36μs (30.8% faster)

def test_validate_detail_false_returns_empty_message():
    # Should raise ValueError with empty message when detail=False
    t = Time()
    with pytest.raises(ValueError) as excinfo:
        t.validate("not-a-time", detail=False) # 3.06μs -> 2.66μs (15.2% faster)

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

def test_validate_many_valid_time_objects():
    # Should accept many valid time objects
    t = Time()
    for h in range(0, 24):
        for m in range(0, 60, 10):  # step to limit to <1000 cases
            for s in range(0, 60, 30):
                t.validate(datetime.time(h, m, s))  # No exception expected

def test_validate_many_valid_iso_strings():
    # Should accept many valid ISO strings
    t = Time()
    for h in range(0, 24):
        for m in range(0, 60, 10):
            for s in range(0, 60, 30):
                iso_str = f"{h:02}:{m:02}:{s:02}"
                t.validate(iso_str)  # No exception expected

def test_validate_many_invalid_iso_strings():
    # Should reject many invalid ISO strings
    t = Time()
    invalids = [f"{h}:{m}:{s}" for h in range(24, 30) for m in range(60, 65) for s in range(60, 65)]
    for s in invalids[:100]:  # Limit to 100 to keep test fast
        with pytest.raises(ValueError):
            t.validate(s)

def test_validate_mixed_valid_and_invalid_inputs():
    # Should correctly handle a mix of valid and invalid inputs
    t = Time()
    valid_times = [datetime.time(1, 2, 3), "04:05:06", "23:59:59.999999"]
    invalid_times = [None, 123, "notatime", "25:00:00", "", [], {}, True]
    for v in valid_times:
        t.validate(v) # 2.57μs -> 1.93μs (33.5% faster)
    for v in invalid_times:
        with pytest.raises(ValueError):
            t.validate(v)

def test_validate_performance_on_large_batch():
    # Should efficiently validate a batch of 1000 valid time strings
    t = Time()
    valid_iso_strings = [f"{h:02}:{m:02}:{s:02}" for h in range(0, 24) for m in range(0, 60, 6) for s in range(0, 60, 10)]
    # Limit to 1000
    for s in valid_iso_strings[:1000]:
        t.validate(s) # 392μs -> 292μs (34.4% faster)

def test_validate_performance_on_large_batch_invalid():
    # Should efficiently reject a batch of 1000 invalid time strings
    t = Time()
    invalid_iso_strings = [f"{h}:{m}:{s}" for h in range(24, 44) for m in range(60, 80, 2) for s in range(60, 80, 2)]
    for s in invalid_iso_strings[:1000]:
        with pytest.raises(ValueError):
            t.validate(s)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-Time.validate-mhwr7qmu and push.

Codeflash Static Badge

The optimized code achieves a **27% speedup** by making two key changes to the `Time.validate()` method:

**1. Removed superclass call overhead**: The original code calls `super().validate(value, detail)`, but the base `Property.validate()` method is just a `pass` statement (confirmed by line profiler showing it takes 60% of total time). Removing this eliminates unnecessary method call overhead.

**2. Hoisted attribute lookup**: The optimized code extracts `datetime.time.fromisoformat` into a local variable `fromisoformat` before the try block. This avoids repeatedly resolving the attribute access `datetime.time.fromisoformat` on each validation call, reducing attribute lookup overhead.

The line profiler shows the superclass call (`super().validate(value, detail)`) consumed 60.2% of the original runtime but is completely eliminated in the optimized version. The attribute hoisting provides additional micro-optimization benefits.

**Performance gains vary by input type**:
- **datetime.time objects**: 71-88% faster (early return path benefits most from removing superclass overhead)
- **Valid ISO strings**: 29-47% faster (benefits from both optimizations)
- **Invalid inputs**: 9-41% faster (still benefits from removing superclass overhead before error handling)

The `validate()` method is likely called frequently during data validation workflows, making these optimizations valuable for applications processing large datasets with time values. The changes maintain identical functionality and error handling behavior while significantly reducing validation overhead.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 November 13, 2025 01:32
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash labels Nov 13, 2025
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: Medium Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant