Skip to content

Commit 231039e

Browse files
authored
Add a search service (#17783)
1 parent 7885fce commit 231039e

File tree

6 files changed

+145
-8
lines changed

6 files changed

+145
-8
lines changed

tests/conftest.py

+9
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
from warehouse.organizations.interfaces import IOrganizationService
6363
from warehouse.packaging import services as packaging_services
6464
from warehouse.packaging.interfaces import IProjectService
65+
from warehouse.search import services as search_services
66+
from warehouse.search.interfaces import ISearchService
6567
from warehouse.subscriptions import services as subscription_services
6668
from warehouse.subscriptions.interfaces import IBillingService, ISubscriptionService
6769

@@ -163,6 +165,7 @@ def pyramid_services(
163165
macaroon_service,
164166
helpdesk_service,
165167
notification_service,
168+
search_service,
166169
):
167170
services = _Services()
168171

@@ -186,6 +189,7 @@ def pyramid_services(
186189
services.register_service(macaroon_service, IMacaroonService, None, name="")
187190
services.register_service(helpdesk_service, IHelpDeskService, None)
188191
services.register_service(notification_service, IAdminNotificationService)
192+
services.register_service(search_service, ISearchService)
189193

190194
return services
191195

@@ -524,6 +528,11 @@ def notification_service():
524528
return helpdesk_services.ConsoleAdminNotificationService()
525529

526530

531+
@pytest.fixture
532+
def search_service():
533+
return search_services.NullSearchService()
534+
535+
527536
class QueryRecorder:
528537
def __init__(self):
529538
self.queries = []

tests/unit/search/test_init.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,24 @@ def test_includeme(monkeypatch):
155155
]
156156

157157
assert config.register_service_factory.calls == [
158-
pretend.call(RateLimit("10 per second"), IRateLimiter, name="search")
158+
pretend.call(RateLimit("10 per second"), IRateLimiter, name="search"),
159+
pretend.call(
160+
search.services.SearchService.create_service,
161+
iface=search.interfaces.ISearchService,
162+
),
163+
]
164+
165+
166+
def test_execute_reindex_no_service():
167+
@pretend.call_recorder
168+
def find_service_factory(interface):
169+
raise LookupError
170+
171+
config = pretend.stub(find_service_factory=find_service_factory)
172+
session = pretend.stub()
173+
174+
search.execute_project_reindex(config, session)
175+
176+
assert find_service_factory.calls == [
177+
pretend.call(search.interfaces.ISearchService)
159178
]

tests/unit/search/test_services.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
import pretend
14+
15+
from warehouse.search.services import NullSearchService
16+
17+
18+
class TestSearchService:
19+
def test_null_service(self):
20+
service = NullSearchService.create_service(pretend.stub(), pretend.stub())
21+
config = pretend.stub()
22+
23+
assert service.reindex(config, ["foo", "bar"]) is None
24+
assert service.unindex(config, ["foo", "bar"]) is None

warehouse/search/__init__.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from warehouse import db
2323
from warehouse.packaging.models import Project, Release
2424
from warehouse.rate_limiting import IRateLimiter, RateLimit
25+
from warehouse.search.interfaces import ISearchService
26+
from warehouse.search.services import SearchService
2527
from warehouse.search.utils import get_index
2628

2729

@@ -53,16 +55,17 @@ def store_projects_for_project_reindex(config, session, flush_context):
5355

5456
@db.listens_for(db.Session, "after_commit")
5557
def execute_project_reindex(config, session):
58+
try:
59+
search_service_factory = config.find_service_factory(ISearchService)
60+
except LookupError:
61+
return
62+
5663
projects_to_update = session.info.pop("warehouse.search.project_updates", set())
5764
projects_to_delete = session.info.pop("warehouse.search.project_deletes", set())
5865

59-
from warehouse.search.tasks import reindex_project, unindex_project
60-
61-
for project in projects_to_update:
62-
config.task(reindex_project).delay(project.normalized_name)
63-
64-
for project in projects_to_delete:
65-
config.task(unindex_project).delay(project.normalized_name)
66+
search_service = search_service_factory(None, config)
67+
search_service.reindex(config, projects_to_update)
68+
search_service.unindex(config, projects_to_delete)
6669

6770

6871
def opensearch(request):
@@ -116,3 +119,5 @@ def includeme(config):
116119
from warehouse.search.tasks import reindex
117120

118121
config.add_periodic_task(crontab(minute=0, hour=6), reindex)
122+
123+
config.register_service_factory(SearchService.create_service, iface=ISearchService)

warehouse/search/interfaces.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
from zope.interface import Interface
14+
15+
16+
class ISearchService(Interface):
17+
def create_service(context, request):
18+
"""
19+
Create the service, given the context and request for which it is being
20+
created for.
21+
"""
22+
23+
def reindex(config, projects_to_update):
24+
"""
25+
Reindexes any projects provided
26+
"""
27+
28+
def unindex(config, projects_to_delete):
29+
"""
30+
Unindexes any projects provided
31+
"""

warehouse/search/services.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
from zope.interface import implementer
14+
15+
from warehouse.search import interfaces, tasks
16+
17+
18+
@implementer(interfaces.ISearchService)
19+
class SearchService:
20+
def __init__(self, **kwargs):
21+
pass
22+
23+
@classmethod
24+
def create_service(cls, context, request):
25+
return cls()
26+
27+
def reindex(self, config, projects_to_update):
28+
for project in projects_to_update:
29+
config.task(tasks.reindex_project).delay(project.normalized_name)
30+
31+
def unindex(self, config, projects_to_delete):
32+
for project in projects_to_delete:
33+
config.task(tasks.unindex_project).delay(project.normalized_name)
34+
35+
36+
@implementer(interfaces.ISearchService)
37+
class NullSearchService:
38+
def __init__(self, **kwargs):
39+
pass
40+
41+
@classmethod
42+
def create_service(cls, context, request):
43+
return cls()
44+
45+
def reindex(self, config, projects_to_update):
46+
pass
47+
48+
def unindex(self, config, projects_to_delete):
49+
pass

0 commit comments

Comments
 (0)