Skip to content

🔊 #68 Events in Rate Limiters#97

Open
roma-glushko wants to merge 6 commits intomainfrom
68-events-in-ratelimiters
Open

🔊 #68 Events in Rate Limiters#97
roma-glushko wants to merge 6 commits intomainfrom
68-events-in-ratelimiters

Conversation

@roma-glushko
Copy link
Owner

No description provided.

@roma-glushko roma-glushko self-assigned this Nov 7, 2023
@roma-glushko roma-glushko linked an issue Nov 9, 2023 that may be closed by this pull request
# Conflicts:
#	hyx/events.py
#	hyx/ratelimit/api.py
#	hyx/ratelimit/managers.py
#	hyx/retry/api.py
# Conflicts:
#	hyx/telemetry/otel.py
#	hyx/telemetry/prometheus.py
#	hyx/telemetry/statsd.py
#	tests/test_otel.py
#	tests/test_prometheus.py
#	tests/test_statsd.py
@roma-glushko roma-glushko requested a review from Copilot January 11, 2026 20:36
@github-actions
Copy link

github-actions bot commented Jan 11, 2026

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
2062 1936 94% 0% 🟢

New Files

File Coverage Status
hyx/ratelimit/events.py 86% 🟢
TOTAL 86% 🟢

Modified Files

File Coverage Status
hyx/ratelimit/api.py 100% 🟢
hyx/ratelimit/managers.py 88% 🟢
hyx/telemetry/otel.py 78% 🟢
hyx/telemetry/prometheus.py 77% 🟢
hyx/telemetry/statsd.py 75% 🟢
tests/test_otel.py 98% 🟢
tests/test_prometheus.py 98% 🟢
tests/test_ratelimiter/test_api.py 100% 🟢
tests/test_statsd.py 98% 🟢
TOTAL 90% 🟢

updated for commit: 1fced76 by action🐍

@roma-glushko roma-glushko added the type:documentation Improvements or additions to documentation label Jan 11, 2026
@roma-glushko roma-glushko changed the title #68 Events in Rate Limiters 🔊 #68 Events in Rate Limiters Jan 11, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds event system support to rate limiter components, enabling telemetry integration for rate limiting operations. It follows issue #68 to bring rate limiters in line with other Hyx components like retry, circuit breaker, timeout, and bulkhead.

Changes:

  • Introduced RateLimiterListener event system for rate limiters with on_rate_limited event
  • Added telemetry integrations for StatsD, Prometheus, and OpenTelemetry
  • Updated rate limiter managers and API to support event dispatching and component naming

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
hyx/ratelimit/events.py New file defining RateLimiterListener base class and global listener registry
hyx/ratelimit/managers.py Added name property and event_dispatcher support to RateLimiter classes
hyx/ratelimit/api.py Updated tokenbucket to support listeners, event_manager, and naming
hyx/telemetry/statsd.py Added RateLimiterListener for StatsD metrics and updated register_listeners
hyx/telemetry/prometheus.py Added RateLimiterListener for Prometheus metrics and updated register_listeners
hyx/telemetry/otel.py Added RateLimiterListener for OpenTelemetry metrics and updated register_listeners
tests/test_statsd.py Added test for rate limiter StatsD metrics
tests/test_prometheus.py Added test for rate limiter Prometheus metrics
tests/test_otel.py Added test for rate limiter OpenTelemetry metrics
tests/test_ratelimiter/test_api.py Updated tests to use explicit limiter names for clarity

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +14 to +16
@property
def name(self) -> str | None:
return self._name
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name property returns str | None, which is inconsistent with other components in the codebase. For example, TimeoutManager's name property returns str (non-optional) with a default of empty string. This inconsistency could cause issues if code expects all component names to be non-None strings. Consider either making the return type consistent with other components or ensuring that None values are handled appropriately throughout the codebase.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +113
if self._limiter._name is None:
self._limiter._name = get_default_name(func)
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directly assigning to the private attribute _name on self._limiter violates encapsulation and is problematic because the RateLimiter base class defines _name but doesn't provide a setter. This creates tight coupling between the tokenbucket class and the internal implementation details of the limiter. Consider either adding a setter property to RateLimiter or restructuring to pass the name through the constructor as intended.

Suggested change
if self._limiter._name is None:
self._limiter._name = get_default_name(func)
if self._name is None:
self._name = get_default_name(func)

Copilot uses AI. Check for mistakes.
self._client = _get_client(client)

async def on_rate_limited(self, limiter: "RateLimiter") -> None:
self._client.incr(f"ratelimiter.{limiter.name}.rejected")
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If limiter.name is None, the string formatting will produce a metric name like "ratelimiter.None.rejected". While this follows the existing pattern used by other components in the codebase (e.g., TimeoutListener, BulkheadListener), it could still produce confusing metric names. Consider documenting this behavior or ensuring name is always populated with a default value.

Suggested change
self._client.incr(f"ratelimiter.{limiter.name}.rejected")
# Use a clear default when limiter.name is not set to avoid 'ratelimiter.None.*' metrics.
limiter_name = limiter.name or "unnamed"
self._client.incr(f"ratelimiter.{limiter_name}.rejected")

Copilot uses AI. Check for mistakes.
)

async def on_rate_limited(self, limiter: "RateLimiter") -> None:
self._rate_limited_counter.labels(component=limiter.name).inc()
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If limiter.name is None, the label will be set to None. While this follows the existing pattern used by other components in the codebase (e.g., CircuitBreakerListener), it could still potentially cause issues with Prometheus. Consider documenting this behavior or ensuring name is always populated with a default value like an empty string.

Suggested change
self._rate_limited_counter.labels(component=limiter.name).inc()
component_name = limiter.name or ""
self._rate_limited_counter.labels(component=component_name).inc()

Copilot uses AI. Check for mistakes.
Comment on lines 78 to 92
event_dispatcher: EventDispatcher[TokenBucketLimiter, RateLimiterListener] = EventDispatcher(
listeners,
_RATELIMITER_LISTENERS,
event_manager=event_manager,
)

def __init__(self, max_executions: float, per_time_secs: float, bucket_size: float | None = None) -> None:
self._limiter = TokenBucketLimiter(
max_executions=max_executions,
per_time_secs=per_time_secs,
bucket_size=bucket_size,
name=name,
event_dispatcher=event_dispatcher.as_listener,
)

event_dispatcher.set_component(self._limiter)
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tokenbucket class manually constructs the EventDispatcher and wires it to the TokenBucketLimiter, instead of using the create_manager helper function. This approach is inconsistent with other components in the codebase like retry, timeout, and bulkhead, which all use create_manager. Consider refactoring to use create_manager for consistency and to reduce boilerplate code.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🔊 Implement listeners API for rate limiters

2 participants