Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions hadith_number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def matches_hadith_number(stored, number):
"""Check if `number` is one part of a combined number like "6924, 6925" or "6924-6925"."""
return any(part.strip() == number for separator in (",", "-") for part in stored.split(separator))
20 changes: 17 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from sqlalchemy import and_, func, or_
from werkzeug.exceptions import HTTPException

from hadith_number import matches_hadith_number

app = Flask(__name__)
app.config.from_object("config.Config")

Expand Down Expand Up @@ -76,6 +78,18 @@ def ref_match(results, collection, hadith_number):
return None


def filter_hadith_number(query, hadith_number):
"""Match `hadith_number` exactly, else as one part of a combined number like "6924, 6925"."""
exact = query.filter(Hadith.hadithNumber == hadith_number)

if exact.first() is not None:
return exact

candidates = query.filter(Hadith.hadithNumber.contains(hadith_number, autoescape=True)).with_entities(Hadith.arabicURN, Hadith.hadithNumber)
urns = [urn for urn, stored in candidates if matches_hadith_number(stored, hadith_number)]
return query.filter(Hadith.arabicURN.in_(urns))


@app.before_request
def verify_secret():
if not app.debug and request.headers.get("x-aws-secret") != app.config["AWS_SECRET"]:
Expand Down Expand Up @@ -198,12 +212,12 @@ def api_collection_book_hadiths(collection_name, bookNumber):
def api_collection_hadith(collection_name, hadithNumber):
collection, book_number = resolve_collection(collection_name)
# `forty` numbers each of its books from 1, so order to keep the pick stable
query = Hadith.query.filter_by(collection=collection, hadithNumber=hadithNumber).order_by(Hadith.englishURN)
query = Hadith.query.filter_by(collection=collection).order_by(Hadith.englishURN)

if book_number is not None:
query = query.filter_by(bookNumber=book_number)

return query, serialize_as(collection_name)
return filter_hadith_number(query, hadithNumber), serialize_as(collection_name)


@app.route("/v1/collections/<string:collection_name>/books/<string:bookNumber>/chapters", methods=["GET"])
Expand Down Expand Up @@ -245,7 +259,7 @@ def api_hadiths():

hadith_number = request.args.get("hadithNumber")
if hadith_number:
query = query.filter_by(hadithNumber=hadith_number)
query = filter_hadith_number(query, hadith_number)

# Order by URN for consistent results
return query.order_by(Hadith.englishURN), serialize_as(collection)
Expand Down
28 changes: 28 additions & 0 deletions tests/test_hadith_number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import unittest

from hadith_number import matches_hadith_number


class TestMatchesHadithNumber(unittest.TestCase):
"""Regression test for sunnah-com/api#614.

Some hadiths are stored under a combined number such as "6924, 6925",
so looking up either number on its own must still find that hadith.
"""

def test_comma_separated_parts(self):
for number in ("6924", "6925"):
self.assertTrue(matches_hadith_number("6924, 6925", number))
self.assertTrue(matches_hadith_number("6924,6925,6926", "6926"))

def test_dash_separated_parts(self):
for number in ("6924", "6925"):
self.assertTrue(matches_hadith_number("6924 - 6925", number))

def test_exact_number(self):
self.assertTrue(matches_hadith_number("6924", "6924"))

def test_substring_is_not_a_match(self):
for number in ("692", "924", "69246", ""):
self.assertFalse(matches_hadith_number("6924, 6925", number))
self.assertFalse(matches_hadith_number("16924", "6924"))
Loading