GitHub
04/24/2026, 7:14 PMPropertiesList.items in singersdk/typing.py
⏱️ Runtime : 69.7 microseconds → 48.6 microseconds (best of 250 runs)
📝 Explanation and details
This optimization achieves a 43% runtime improvement (from 69.7μs to 48.6μs) by introducing a cache for the ItemsView object returned by PropertiesList.items().
Key optimization:
The original implementation created a new ItemsView object on every call to items() by directly returning self.wrapped.items(). The optimized version caches this view in self._items_cache and reuses it across calls, eliminating the overhead of repeatedly constructing the view object.
Why this improves runtime:
1. Reduces object allocation overhead: Creating an ItemsView involves Python's internal machinery for building view objects. By caching it after the first call, subsequent calls skip this allocation entirely.
2. Eliminates repeated method calls: The original version calls .items() on every access (1037 hits in profiling). The cache reduces this to just 34 first-time calls, with 1003 cache hits simply returning the stored reference.
How this benefits existing workloads:
The test suite demonstrates this optimization is particularly effective for:
• Repeated iterations: The test test_iterating_items_repeatedly_is_deterministic_and_performant shows a 54.4% speedup when calling items() 1000 times in a loop—exactly the pattern this cache targets.
• Large property lists: Tests with 100-1000 properties still benefit from the cache when items() is called multiple times.
• Schema introspection: Any code that queries items() repeatedly (e.g., validation, serialization, or UI generation) will see reduced overhead.
Trade-offs:
The cache is safe because ItemsView is a live view over the underlying self.wrapped dict—it automatically reflects mutations without needing invalidation. The only cost is one additional attribute (_items_cache) and minor slowdown on first access (208ns vs 166ns), but subsequent calls become nearly free (cache lookup vs dict method call). This is an excellent trade-off when items() is called multiple times per PropertiesList instance, which is common in schema processing pipelines.
✅ Correctness verification report:
| Test | Status |
| ----------------------------- | --------------- |
| ⚙️ Existing Unit Tests | ✅ 5 Passed |
| 🌀 Generated Regression Tests | ✅ 1070 Passed |
| ⏪ Replay Tests | 🔘 None Found |
| 🔎 Concolic Coverage Tests | 🔘 None Found |
| 📊 Tests Coverage | 100.0% |
⚙️ Click to see Existing Unit Tests 🌀 Click to see Generated Regression Tests
from future import annotations
from singer_sdk.typing import ( # import the real classes under test
PropertiesList,
Property,
)
def test_items_returns_items_view_and_preserves_order_and_identity():
# Create two Property instances with distinct names (use built-in types for the schema arg).
p1 = Property("alpha", int)
p2 = Property("beta", int)
# Construct a PropertiesList using the real Property instances.
pl = PropertiesList(p1, p2)
# Retrieve the items view from the PropertiesList.
codeflash_output = pl.items()
items_view = codeflash_output # 166ns -> 208ns (20.2% slower)
# The returned object should be a dict-items view type (same type as {}.items()).
codeflash_output = type({}.items()) # 42ns -> 42ns (0.000% faster)
def test_items_on_empty_propertieslist_is_empty():
# Construct an empty PropertiesList (no properties passed).
pl = PropertiesList()
# items() should be empty.
codeflash_output = pl.items()
items = codeflash_output # 125ns -> 208ns (39.9% slower)
def test_items_reflects_mutations_of_underlying_wrapped_dict():
# Start with one property.
p1 = Property("one", int)
pl = PropertiesList(p1)
codeflash_output = pl.items()
items_view = codeflash_output # 166ns -> 208ns (20.2% slower)
# Add a new Property directly into the wrapped dict (real attribute on the instance).
p2 = Property("two", int)
pl.wrapped["two"] = p2
# Remove the original property and ensure the view reflects the deletion.
del pl.wrapped["one"]
def test_duplicate_property_names_last_one_wins():
# Two Property instances with the same name should result in the last one overriding the first.
first = Property("dup", int)
second = Property("dup", float)
pl = PropertiesList(first, second)
items_list = list(pl.items()) # 166ns -> 208ns (20.2% slower)
def test_property_names_with_special_characters_and_numbers():
# Use a variety of ASCII-safe edge-like names (dash, underscore, numeric prefix).
p_dash = Property("name-with-dash", int)
p_under = Property("name_with_underscore", int)
p_num = Property("123numeric", int)
pl = PropertiesList(p_dash, p_under, p_num)
# Ensure keys are preserved exactly as provided.
keys = [k for k, _ in pl.items()]
def test_items_with_many_properties_preserves_count_and_order():
# Create 1000 Property instances with unique names.
count = 1000
props = [Property(f"p{i}", int) for i in range(count)]
# Construct a PropertiesList using the large list via argument unpacking.
pl = PropertiesList(*props)
items_list = list(pl.items()) # 166ns -> 208ns (20.2% slower)
def test_iterating_items_repeatedly_is_deterministic_and_performant():
# Build a moderate-size PropertiesList for repeated iteration.
size = 200 # 200 keeps the test fast but still exercises iteration cost
pl = PropertiesList(*(Property(f"k{i}", int) for i in range(size)))
# Sum up the lengths of the items view across 1000 iterations to ensure deterministic behavior.
iterations = 1000
total = 0
for _ in range(iterations):
# Each iteration should be quick and return the same size.
total += len(pl.items()) # 64.6μs -> 41.8μs (54.4% faster)
def test_iter_returns_only_property_values_and_matches_items_values():
# Verify the iter implementation returns the same Property values as the items view.
pA = Property("A", int)
pB = Property("B", int)
pl = PropertiesList(pA, pB)
# iter yields Property values; compare against the values from items().
iter_values = list(pl) # uses PropertiesList.__iter__()
items_values = [v for _, v in pl.items()]
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from future import annotations
from singer_sdk.typing import (
BooleanType,
DecimalType,
IntegerType,
PropertiesList,
Property,
StringType,
)
def test_items_returns_items_view():
"""Test that items() returns an ItemsView object."""
# Create a PropertiesList with one property
prop_list = PropertiesList(Property("name", StringType))
# Call items() and verify it returns an ItemsView
codeflash_output = prop_list.items()
result = codeflash_output # 166ns -> 208ns (20.2% slower)
def test_items_empty_properties_list():
"""Test items() on an empty PropertiesList."""
# Create a PropertiesList with no properties
prop_list = PropertiesList()
# Call items() and verify it returns an empty view
codeflash_output = prop_list.items()
result = codeflash_output # 166ns -> 208ns (20.2% slower)
# Convert to list to check contents
items_list = list(result)
def test_items_single_property():
"""Test items() with a single property."""
# Create a PropertiesList with one property
prop = Property("username", StringType, required=True)
prop_list = PropertiesList(prop)
# Call items() and check the result
codeflash_output = p…
meltano/sdkGitHub
04/24/2026, 7:14 PM