Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Oct 29, 2025

📄 12% (0.12x) speedup for BaseSchemaGenerator._remove_converter in starlette/schemas.py

⏱️ Runtime : 409 microseconds 365 microseconds (best of 346 runs)

📝 Explanation and details

The optimization adds a fast-path early return that avoids expensive regex operations for paths without closing braces (}).

Key optimization: Before calling the regex _remove_converter_pattern.sub("}", path), the code now checks if '}' not in path: return path. This simple string containment check is much faster than regex pattern matching.

Why this works: The converter pattern being removed has the format :{converter_name}, which always ends with }. If there's no } in the path, there can't be any converters to remove, so the original path can be returned immediately.

Performance impact:

  • Paths without }: Get 79-648% speedups (see test cases like test_edge_empty_string, test_large_path_no_converters) because they skip regex entirely
  • Paths with converters: Experience small 2-15% slowdowns due to the additional check, but this is minimal compared to the regex cost
  • Overall: 12% speedup because many real-world paths don't contain converters

This optimization is particularly effective for applications with many simple routes (like /users, /health, /api/status) that don't use path parameters, which is common in REST APIs where only a subset of endpoints need dynamic path segments.

Correctness verification report:

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

import re

# imports
import pytest  # used for our unit tests
from starlette.schemas import BaseSchemaGenerator

_remove_converter_pattern = re.compile(r":\w+}")
from starlette.schemas import BaseSchemaGenerator

# unit tests

@pytest.fixture
def schema_generator():
    # Fixture to provide an instance of BaseSchemaGenerator for tests
    return BaseSchemaGenerator()

# 1. Basic Test Cases

def test_remove_single_converter(schema_generator):
    # Test a single converter in the path
    codeflash_output = schema_generator._remove_converter("/users/{id:int}") # 1.55μs -> 1.78μs (12.9% slower)

def test_remove_multiple_converters(schema_generator):
    # Test multiple converters in the path
    codeflash_output = schema_generator._remove_converter("/users/{id:int}/posts/{post_id:uuid}") # 1.70μs -> 1.74μs (2.30% slower)

def test_no_converter(schema_generator):
    # Test a path with no converters (should remain unchanged)
    codeflash_output = schema_generator._remove_converter("/users/{id}/posts/{post_id}") # 890ns -> 1.01μs (12.1% slower)

def test_converter_with_underscore(schema_generator):
    # Test a converter name with underscores
    codeflash_output = schema_generator._remove_converter("/items/{item_id:item_type}") # 1.44μs -> 1.52μs (5.45% slower)

def test_converter_at_start(schema_generator):
    # Test a path starting with a converter
    codeflash_output = schema_generator._remove_converter("/{id:int}/edit") # 1.41μs -> 1.62μs (13.2% slower)

def test_converter_at_end(schema_generator):
    # Test a path ending with a converter
    codeflash_output = schema_generator._remove_converter("/edit/{id:int}") # 1.30μs -> 1.43μs (9.36% slower)

# 2. Edge Test Cases

def test_empty_string(schema_generator):
    # Test an empty path string
    codeflash_output = schema_generator._remove_converter("") # 789ns -> 399ns (97.7% faster)

def test_only_converter(schema_generator):
    # Test a path that is only a converter
    codeflash_output = schema_generator._remove_converter("{id:int}") # 1.32μs -> 1.48μs (10.4% slower)

def test_adjacent_converters(schema_generator):
    # Test adjacent converters in the path
    codeflash_output = schema_generator._remove_converter("/a/{x:int}{y:str}/z") # 1.72μs -> 1.95μs (11.8% slower)

def test_colon_but_not_converter(schema_generator):
    # Test a colon in the path that is not a converter
    codeflash_output = schema_generator._remove_converter("/foo:bar/baz") # 1.23μs -> 399ns (207% faster)

def test_curly_braces_but_no_colon(schema_generator):
    # Test curly braces without a colon (should remain unchanged)
    codeflash_output = schema_generator._remove_converter("/users/{id}/posts/{post_id}") # 818ns -> 975ns (16.1% slower)

def test_colon_inside_braces_but_not_converter(schema_generator):
    # Test colon inside braces but not a valid converter (e.g., {id:})
    codeflash_output = schema_generator._remove_converter("/users/{id:}/posts/{post_id}") # 1.23μs -> 1.33μs (7.66% slower)

def test_converter_with_numbers(schema_generator):
    # Test converter name with numbers
    codeflash_output = schema_generator._remove_converter("/foo/{bar123:baz456}") # 1.31μs -> 1.54μs (14.8% slower)

def test_converter_with_uppercase(schema_generator):
    # Test converter with uppercase letters (should match)
    codeflash_output = schema_generator._remove_converter("/foo/{bar:UUID}") # 1.37μs -> 1.52μs (10.1% slower)

def test_converter_with_long_name(schema_generator):
    # Test a long converter name
    codeflash_output = schema_generator._remove_converter("/foo/{bar:averylongconvertername}") # 1.42μs -> 1.55μs (8.89% slower)

def test_converter_with_special_characters(schema_generator):
    # Test converter with special characters (should not match, as pattern is \w+)
    codeflash_output = schema_generator._remove_converter("/foo/{bar:uuid-v4}") # 1.18μs -> 1.39μs (15.1% slower)

def test_converter_with_non_ascii(schema_generator):
    # Test converter with non-ascii characters (should not match, as pattern is \w+)
    codeflash_output = schema_generator._remove_converter("/foo/{bar:üñîçødë}") # 1.50μs -> 1.60μs (6.31% slower)

def test_nested_braces(schema_generator):
    # Test nested braces (should not match, as pattern expects closing brace)
    codeflash_output = schema_generator._remove_converter("/foo/{{bar:int}}") # 1.47μs -> 1.56μs (5.97% slower)

def test_converter_with_trailing_text(schema_generator):
    # Test converter followed by text inside the same braces (should only remove :type})
    codeflash_output = schema_generator._remove_converter("/foo/{bar:int}_suffix") # 1.48μs -> 1.56μs (5.63% slower)

def test_converter_with_colon_in_param_name(schema_generator):
    # Test colon in param name (should not match, as pattern expects colon after param name)
    codeflash_output = schema_generator._remove_converter("/foo/{bar:baz:int}") # 1.41μs -> 1.57μs (10.7% slower)

# 3. Large Scale Test Cases

def test_large_number_of_converters(schema_generator):
    # Test a path with many converters (up to 1000)
    path = "/" + "/".join([f"{{id{i}:int}}" for i in range(1000)])
    expected = "/" + "/".join([f"{{id{i}}}" for i in range(1000)])
    codeflash_output = schema_generator._remove_converter(path) # 89.8μs -> 88.7μs (1.19% faster)

def test_large_path_with_mixed_converters(schema_generator):
    # Test a large path with a mix of converters and plain params
    segments = []
    expected_segments = []
    for i in range(500):
        if i % 2 == 0:
            segments.append(f"{{foo{i}:str}}")
            expected_segments.append(f"{{foo{i}}}")
        else:
            segments.append(f"{{bar{i}}}")
            expected_segments.append(f"{{bar{i}}}")
    path = "/" + "/".join(segments)
    expected = "/" + "/".join(expected_segments)
    codeflash_output = schema_generator._remove_converter(path) # 29.1μs -> 29.3μs (0.571% slower)

def test_performance_many_replacements(schema_generator):
    # Test performance with a long string and many replacements
    # Should run quickly and not hang
    path = "/".join([f"users/{{id{i}:int}}" for i in range(1000)])
    expected = "/".join([f"users/{{id{i}}}" for i in range(1000)])
    codeflash_output = schema_generator._remove_converter(path) # 90.1μs -> 91.5μs (1.58% slower)

def test_large_path_no_converters(schema_generator):
    # Test a large path with no converters (should remain unchanged)
    path = "/" + "/".join([f"segment{i}" for i in range(1000)])
    codeflash_output = schema_generator._remove_converter(path) # 4.30μs -> 575ns (648% faster)

def test_large_path_all_non_matching_colons(schema_generator):
    # Test a large path with many colons but not in converter form
    path = "/" + "/".join([f"foo:bar{i}" for i in range(1000)])
    codeflash_output = schema_generator._remove_converter(path) # 40.6μs -> 555ns (7215% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import re

# imports
import pytest  # used for our unit tests
from starlette.schemas import BaseSchemaGenerator

_remove_converter_pattern = re.compile(r":\w+}")
from starlette.schemas import BaseSchemaGenerator

# unit tests

# --- Basic Test Cases ---

def test_basic_single_converter():
    # Basic: single parameter with converter
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:int}") # 1.31μs -> 1.49μs (11.5% slower)

def test_basic_multiple_converters():
    # Basic: multiple parameters with converters
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:int}/items/{item_id:str}") # 1.58μs -> 1.73μs (8.68% slower)

def test_basic_no_converter():
    # Basic: no converters present
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id}/items/{item_id}") # 825ns -> 1.01μs (18.0% slower)

def test_basic_trailing_slash():
    # Basic: converter at the end with trailing slash
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:int}/") # 1.39μs -> 1.51μs (8.13% slower)

def test_basic_converter_middle():
    # Basic: converter in the middle of the path
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/a/{b:str}/c/{d:int}/e") # 1.68μs -> 1.78μs (5.84% slower)

# --- Edge Test Cases ---

def test_edge_empty_string():
    # Edge: empty string input
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("") # 719ns -> 400ns (79.8% faster)

def test_edge_no_braces():
    # Edge: path with no braces at all
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/id/items/item_id") # 848ns -> 426ns (99.1% faster)

def test_edge_colon_not_converter():
    # Edge: colon present but not in converter format
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/:id/items/{item_id}") # 1.19μs -> 1.41μs (15.6% slower)

def test_edge_converter_with_numbers():
    # Edge: converter name with numbers
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:int2}") # 1.30μs -> 1.50μs (13.2% slower)

def test_edge_multiple_colons():
    # Edge: multiple colons in a parameter
    gen = BaseSchemaGenerator()
    # Only the first colon triggers the regex, so only ":int" is removed
    codeflash_output = gen._remove_converter("/users/{id:int:str}") # 1.38μs -> 1.44μs (4.04% slower)

def test_edge_nested_braces():
    # Edge: nested braces (should not happen, but test for robustness)
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{{id:int}}") # 1.39μs -> 1.46μs (5.19% slower)

def test_edge_converter_with_underscore():
    # Edge: converter name with underscore
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:custom_type}") # 1.33μs -> 1.39μs (4.46% slower)

def test_edge_converter_with_long_name():
    # Edge: very long converter name
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:verylongconvertername}") # 1.29μs -> 1.41μs (8.33% slower)

def test_edge_multiple_same_param():
    # Edge: multiple parameters with same name and converter
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:int}/posts/{id:int}") # 1.49μs -> 1.61μs (7.10% slower)

def test_edge_converter_at_start():
    # Edge: converter at the start of the path
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/{id:int}/users") # 1.36μs -> 1.53μs (10.6% slower)

def test_edge_converter_with_dash():
    # Edge: converter name with dash (should not match, as \w does not match '-')
    gen = BaseSchemaGenerator()
    # The regex will not match ":my-type}", so it will remain unchanged
    codeflash_output = gen._remove_converter("/users/{id:my-type}") # 1.17μs -> 1.33μs (12.1% slower)

def test_edge_converter_with_special_characters():
    # Edge: converter name with special characters (should not match)
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:!@#}") # 1.09μs -> 1.20μs (9.48% slower)

def test_edge_converter_with_uppercase():
    # Edge: converter name with uppercase letters (should match)
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:INT}") # 1.26μs -> 1.39μs (9.22% slower)

def test_edge_converter_with_mixed_case():
    # Edge: converter name with mixed case
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{id:InT}") # 1.26μs -> 1.39μs (9.40% slower)

def test_edge_braces_in_param_name():
    # Edge: braces inside param name (should not match, but test for robustness)
    gen = BaseSchemaGenerator()
    codeflash_output = gen._remove_converter("/users/{i{d}:int}") # 1.29μs -> 1.34μs (3.22% slower)

# --- Large Scale Test Cases ---

def test_large_many_converters():
    # Large Scale: path with many converters
    gen = BaseSchemaGenerator()
    path = "/" + "/".join([f"{{param{i}:int}}" for i in range(100)])
    expected = "/" + "/".join([f"{{param{i}}}" for i in range(100)])
    codeflash_output = gen._remove_converter(path) # 12.7μs -> 13.0μs (2.47% slower)

def test_large_long_path_no_converters():
    # Large Scale: very long path with no converters
    gen = BaseSchemaGenerator()
    path = "/" + "/".join([f"segment{i}" for i in range(1000)])
    codeflash_output = gen._remove_converter(path) # 4.36μs -> 609ns (615% faster)

def test_large_long_path_some_converters():
    # Large Scale: very long path with converters every 10 segments
    gen = BaseSchemaGenerator()
    segments = []
    for i in range(1000):
        if i % 10 == 0:
            segments.append(f"{{param{i}:str}}")
        else:
            segments.append(f"segment{i}")
    path = "/" + "/".join(segments)
    expected_segments = []
    for i in range(1000):
        if i % 10 == 0:
            expected_segments.append(f"{{param{i}}}")
        else:
            expected_segments.append(f"segment{i}")
    expected = "/" + "/".join(expected_segments)
    codeflash_output = gen._remove_converter(path) # 17.8μs -> 17.8μs (0.349% faster)

def test_large_path_with_edge_converters():
    # Large Scale: path with edge-case converters mixed in
    gen = BaseSchemaGenerator()
    path = "/" + "/".join(
        [f"{{param{i}:int}}" if i % 2 == 0 else f"{{param{i}:my-type}}" for i in range(100)]
    )
    expected = "/" + "/".join(
        [f"{{param{i}}}" if i % 2 == 0 else f"{{param{i}:my-type}}" for i in range(100)]
    )
    codeflash_output = gen._remove_converter(path) # 9.44μs -> 9.67μs (2.41% slower)

def test_large_path_all_types_of_converters():
    # Large Scale: path with various converter names
    gen = BaseSchemaGenerator()
    path = "/" + "/".join([
        "{a:int}", "{b:str}", "{c:custom_type}", "{d:INT}", "{e:my-type}", "{f:!@#}", "{g:123abc}"
    ] * 100)
    expected = "/" + "/".join([
        "{a}", "{b}", "{c}", "{d}", "{e:my-type}", "{f:!@#}", "{g}"
    ] * 100)
    codeflash_output = gen._remove_converter(path) # 58.6μs -> 58.6μs (0.130% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
from starlette.schemas import BaseSchemaGenerator

def test_BaseSchemaGenerator__remove_converter():
    BaseSchemaGenerator._remove_converter(BaseSchemaGenerator(), '')
🔎 Concolic Coverage Tests and Runtime
Test File::Test Function Original ⏱️ Optimized ⏱️ Speedup
codeflash_concolic_b9ikc1l3/tmpbzno8bzd/test_concolic_coverage.py::test_BaseSchemaGenerator__remove_converter 809ns 401ns 102%✅

To edit these changes git checkout codeflash/optimize-BaseSchemaGenerator._remove_converter-mhcbrps5 and push.

Codeflash

The optimization adds a **fast-path early return** that avoids expensive regex operations for paths without closing braces (`}`). 

**Key optimization**: Before calling the regex `_remove_converter_pattern.sub("}", path)`, the code now checks `if '}' not in path: return path`. This simple string containment check is much faster than regex pattern matching.

**Why this works**: The converter pattern being removed has the format `:{converter_name}`, which always ends with `}`. If there's no `}` in the path, there can't be any converters to remove, so the original path can be returned immediately.

**Performance impact**: 
- **Paths without `}`**: Get 79-648% speedups (see test cases like `test_edge_empty_string`, `test_large_path_no_converters`) because they skip regex entirely
- **Paths with converters**: Experience small 2-15% slowdowns due to the additional check, but this is minimal compared to the regex cost
- **Overall**: 12% speedup because many real-world paths don't contain converters

This optimization is particularly effective for applications with many simple routes (like `/users`, `/health`, `/api/status`) that don't use path parameters, which is common in REST APIs where only a subset of endpoints need dynamic path segments.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 October 29, 2025 18:24
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Oct 29, 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: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant