New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rate limiting for scheduling triggers #587
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: F.N. Claessen <felix@seita.nl>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found two arguments against protecting a whole resource in one way.
In this solution (also using a syntactic sugar on top of Flask), they found a way to add decorators per method. Not sure if Flask-Classful has that.
Have you tried to decorate an endpoint outside of Flask-Classful's reach?
flexmeasures/api/v3_0/__init__.py
Outdated
# Apply rate limit: a schedule can be triggered once per 5 minutes per sensor per account | ||
SensorAPI.decorators.append( | ||
app.limiter.limit( | ||
"1 per 5 minutes", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We apply this here for all endpoints, but the timing we want to allow should differ per endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But given the cost function, it is only effectively applied to one endpoint. We are free to add more limiter.limit()
decorators with other rate limits that affect other endpoints (via the cost function).
flexmeasures/api/v3_0/__init__.py
Outdated
@@ -11,6 +12,21 @@ def register_at(app: Flask): | |||
|
|||
v3_0_api_prefix = "/api/v3_0" | |||
|
|||
def cost_function() -> int: | |||
if request.endpoint == "SensorAPI:trigger_schedule": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid that if for some reason the endpoint's identification string changes, we suddenly stop limiting it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can probably find a way to obtain this string in a way that directly references the class method.
SensorAPI.__name__ + ":" + SensorAPI.trigger_schedule.__name__
would be my first try.
I'd definitely prefer that, but did they find a way though? I don't see any comment claiming they were able to add a decorator for a distinct class method. Only for all of the methods in the class, by adding the What is possible is to tell |
Have you tried yet to decorate an endpoint outside of Flask-Classful? |
I have. That had the same problem of not having the app context available. Applying it to a whole blueprint did work, though. I had accomplished the latter in the register function of |
I don't recall why the app context is needed for... |
Signed-off-by: F.N. Claessen <felix@seita.nl>
It's needed to set up the |
I'm stuck here because it seems weird that such core functionality (adding the It's passed to the |
On this branch you'll find the rest of my tech spike where I test out limiting a non-FlaskView endpoint by decorating it directly, and also limiting an entire blueprint. The former doesn't work (the decorator seems to be ignored), the latter does work. To test out, run
In my branch, the issue with the decorated endpoint approach seems not to be in getting the limit string from the app (I just hardcoded a string), but setting up the limiter to work with the app. I guess you could carry on my tech spike, or we should come to a decision on how I can move forward. Otherwise, I'm stuck, too. I've made two suggestions:
|
I believe I'm willing to pursue the cost function approach. I did not yet understand at which (accumulated) costs the rate limiter stops access. Is that simply 1? Or can this be some setting? And are costs accumulated per endpoint? Maybe you can point me to the place in the rate-limiter docs where I can learn this. I could not find it. In any case, I'd still want to:
I'm willing to work on that. |
For |
…e to the actual endpoints) Signed-off-by: Nicolas Höning <nicolas@seita.nl>
Current list of ideas of TODOs:
|
Signed-off-by: Nicolas Höning <nicolas@seita.nl>
I completed my tech spike.
I couldn't (figure out how to) decorate only the trigger method of our FlaskView, so I worked around that by decorating the entire SensorAPI and using the cost function to only count towards the limit for the trigger method. It's not the most elegant solution, but it does look easily extensible this way.
We also discussed offline on what to apply the limit (using
key_func
): users or accounts. Here I decided to combine the account and the sensor id, just to let a test pass (test_trigger_and_get_schedule
first schedules a battery and then schedules a charging station). But it might also make sense to apply a limit per sensor.