Skip to content
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

feat: Implement Flask-Limiter on the search module #37

Merged
merged 18 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ jobs:

- name: Test Python
run: |
python3 setup.py install --user test
pip install -e .
pip install httpretty
python -m unittest discover tests
check-inclusive-naming:
Expand Down
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ v1.2.5, 2022-07-12 -- Block some more bot useragents
v1.2.6, 2022-07-13 -- Block one more useragent - "gh"
v1.2.7, 2022-07-15 -- Block more user agents - "Petalbot"
v1.3.0, 2023-02-20 -- Add rate limits
v1.4.0, 2-24-06-21 -- Migrate to Flask.Limiter for rate limits
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ app.add_url_rule(
"/docs/search",
"docs-search",
build_search_view(
app=app
session=session,
site="maas.io/docs",
template_path="docs/search.html",
search_engine_id="xxxxxxxxxx", # Optional argument, required by some of our sites
request_limit="500/day", # Allows your to configure the limit at which the user will be forbidden to query more. Defaults to 500 per day
request_limit="500/day", # Allows your to configure the limit at which the user will be forbidden to query more. Defaults to 2000 per day
)
)
```
Expand Down
46 changes: 22 additions & 24 deletions canonicalwebteam/search/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,28 @@

# Packages
import flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

# Local
from canonicalwebteam.search.models import get_search_results
from limits import storage, strategies, parse

memory_storage = storage.MemoryStorage()
fixed_window = strategies.MovingWindowRateLimiter(memory_storage)


class NoAPIKeyError(Exception):
pass


limiter = Limiter(get_remote_address)


def build_search_view(
app,
session,
site=None,
template_path="search.html",
search_engine_id="009048213575199080868:i3zoqdwqk8o",
site_restricted_search=False,
request_limit="500/day",
request_limit="2000/day",
):
"""
Build and return a view function that will query the
Expand All @@ -33,31 +35,26 @@ def build_search_view(

from canonicalwebteam.search import build_search_view

app = Flask()
app = Flask(__name__)
session = talisker.requests.get_session()
app.add_url_rule(
"/search",
"search",
build_search_view(
app,
session=session,
site="snapcraft.io",
template_path="search.html"
)
)
"""

limiter.init_app(app)

def search_view():
"""
Get search results from Google Custom Search
"""
# Rate limit requests to protect from spamming
# To adjust this rate visit
# https://limits.readthedocs.io/en/latest/quickstart.html#examples
limit = parse(request_limit)
rate_limit = fixed_window.hit(limit)
if not rate_limit:
return flask.abort(429, f"The rate limit is: {request_limit}")

# API key should always be provided as an environment variable
search_api_key = os.getenv("SEARCH_API_KEY")

Expand All @@ -72,16 +69,17 @@ def search_view():
results = None

if query:
results = get_search_results(
session=session,
api_key=search_api_key,
search_engine_id=search_engine_id,
siteSearch=site_search,
site_restricted_search=site_restricted_search,
query=query,
start=start,
num=num,
)
with limiter.limit(request_limit):
results = get_search_results(
session=session,
api_key=search_api_key,
search_engine_id=search_engine_id,
siteSearch=site_search,
site_restricted_search=site_restricted_search,
query=query,
start=start,
num=num,
)

return (
flask.render_template(
Expand Down
8 changes: 6 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

setup(
name="canonicalwebteam.search",
version="1.3.0",
version="1.4.0",
author="Canonical webteam",
author_email="webteam@canonical.com",
url="https://github.com/canonical/canonicalwebteam.search",
Expand All @@ -15,6 +15,10 @@
packages=find_packages(),
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
install_requires=["Flask>=1.0.2", "user-agents>=2.0.0", "limits>=3.2.0"],
install_requires=[
"canonicalwebteam.flask-base>=2.0.0",
"user-agents>=2.0.0",
"Flask-Limiter>=3.8.0",
],
tests_require=["httpretty"],
)
5 changes: 4 additions & 1 deletion tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ def setUp(self):

# Default use-case
self.app.add_url_rule(
"/search", "search", build_search_view(session=session)
"/search", "search", build_search_view(self.app, session=session)
)

# Custom use-case
self.app.add_url_rule(
"/docs/search",
"docs-search",
build_search_view(
self.app,
session=session,
site="maas.io/docs",
template_path="docs/search.html",
Expand All @@ -67,6 +68,7 @@ def setUp(self):
"/server/docs/search",
"server-docs-search",
build_search_view(
self.app,
session=session,
template_path="docs/search.html",
site_restricted_search=True,
Expand All @@ -78,6 +80,7 @@ def setUp(self):
"/server/docs/limited/search",
"server-docs-search-limited",
build_search_view(
self.app,
session=session,
template_path="docs/search.html",
site_restricted_search=True,
Expand Down
Loading