Skip to content

Commit 1b44a58

Browse files
committed
feat: Add requests.Session support to SolrCoreAdmin and update tests
1 parent 4ae7185 commit 1b44a58

File tree

2 files changed

+113
-70
lines changed

2 files changed

+113
-70
lines changed

pysolr.py

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,16 @@ def __init__(
279279
self.always_commit = always_commit
280280

281281
def get_session(self):
282+
"""
283+
Returns a requests Session object to use for sending requests to Solr.
284+
285+
The session is created lazily on first call to this method, and is
286+
reused for all subsequent requests.
287+
288+
:return: requests.Session instance
289+
"""
282290
if self.session is None:
283291
self.session = requests.Session()
284-
self.session.stream = False
285292
self.session.verify = self.verify
286293
return self.session
287294

@@ -1250,18 +1257,70 @@ class SolrCoreAdmin(object):
12501257
8. LOAD (not currently implemented)
12511258
"""
12521259

1253-
def __init__(self, url, *args, **kwargs):
1254-
super(SolrCoreAdmin, self).__init__(*args, **kwargs)
1260+
def __init__(self, url, timeout=60, auth=None, verify=True, session=None):
12551261
self.url = url
1262+
self.timeout = timeout
1263+
self.log = self._get_log()
1264+
self.auth = auth
1265+
self.verify = verify
1266+
self.session = session
1267+
1268+
def get_session(self):
1269+
"""
1270+
Returns a requests Session object to use for sending requests to Solr.
1271+
1272+
The session is created lazily on first call to this method, and is
1273+
reused for all subsequent requests.
1274+
1275+
:return: requests.Session instance
1276+
"""
1277+
if self.session is None:
1278+
self.session = requests.Session()
1279+
self.session.verify = self.verify
1280+
return self.session
12561281

1257-
def _get_url(self, url, params=None, headers=None):
1282+
def _get_log(self):
1283+
return LOG
1284+
1285+
def _send_request(self, url, params=None, headers=None):
1286+
"""
1287+
Internal method to send a GET request to Solr.
1288+
1289+
:param url: Full URL to query
1290+
:param params: Dictionary of query parameters
1291+
:param headers: Dictionary of HTTP headers
1292+
:return: JSON response from Solr
1293+
:raises SolrError: if the request fails or the JSON response cannot be decoded
1294+
"""
12581295
if params is None:
12591296
params = {}
12601297
if headers is None:
12611298
headers = {}
12621299

1263-
resp = requests.get(url, params=params, headers=headers)
1264-
return force_unicode(resp.content)
1300+
session = self.get_session()
1301+
1302+
self.log.debug(
1303+
"Starting Solr admin request to '%s' with params %s",
1304+
url,
1305+
params,
1306+
)
1307+
1308+
try:
1309+
resp = session.get(
1310+
url,
1311+
params=params,
1312+
headers=headers,
1313+
auth=self.auth,
1314+
)
1315+
return resp.json()
1316+
except requests.exceptions.JSONDecodeError as e:
1317+
self.log.exception("Failed to decode JSON response from Solr at %s", url)
1318+
raise SolrError(
1319+
f"Failed to decode JSON response: {e}. Response text: {resp.text}"
1320+
)
1321+
except requests.exceptions.RequestException as e:
1322+
self.log.exception("Request to Solr failed for URL %s", url)
1323+
raise SolrError(f"Request failed: {e}")
12651324

12661325
def status(self, core=None):
12671326
"""
@@ -1274,7 +1333,7 @@ def status(self, core=None):
12741333
if core is not None:
12751334
params.update(core=core)
12761335

1277-
return self._get_url(self.url, params=params)
1336+
return self._send_request(self.url, params=params)
12781337

12791338
def create(
12801339
self, name, instance_dir=None, config="solrconfig.xml", schema="schema.xml"
@@ -1291,7 +1350,7 @@ def create(
12911350
else:
12921351
params.update(instanceDir=instance_dir)
12931352

1294-
return self._get_url(self.url, params=params)
1353+
return self._send_request(self.url, params=params)
12951354

12961355
def reload(self, core): # NOQA: A003
12971356
"""
@@ -1300,7 +1359,7 @@ def reload(self, core): # NOQA: A003
13001359
See https://wiki.apache.org/solr/CoreAdmin#RELOAD
13011360
"""
13021361
params = {"action": "RELOAD", "core": core}
1303-
return self._get_url(self.url, params=params)
1362+
return self._send_request(self.url, params=params)
13041363

13051364
def rename(self, core, other):
13061365
"""
@@ -1309,7 +1368,7 @@ def rename(self, core, other):
13091368
See http://wiki.apache.org/solr/CoreAdmin#RENAME
13101369
"""
13111370
params = {"action": "RENAME", "core": core, "other": other}
1312-
return self._get_url(self.url, params=params)
1371+
return self._send_request(self.url, params=params)
13131372

13141373
def swap(self, core, other):
13151374
"""
@@ -1318,7 +1377,7 @@ def swap(self, core, other):
13181377
See http://wiki.apache.org/solr/CoreAdmin#SWAP
13191378
"""
13201379
params = {"action": "SWAP", "core": core, "other": other}
1321-
return self._get_url(self.url, params=params)
1380+
return self._send_request(self.url, params=params)
13221381

13231382
def unload(self, core):
13241383
"""
@@ -1327,7 +1386,7 @@ def unload(self, core):
13271386
See http://wiki.apache.org/solr/CoreAdmin#UNLOAD
13281387
"""
13291388
params = {"action": "UNLOAD", "core": core}
1330-
return self._get_url(self.url, params=params)
1389+
return self._send_request(self.url, params=params)
13311390

13321391
def load(self, core):
13331392
raise NotImplementedError("Solr 1.4 and below do not support this operation.")

tests/test_admin.py

Lines changed: 42 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import contextlib
2-
import json
32
import unittest
43

54
from pysolr import SolrCoreAdmin, SolrError
@@ -46,31 +45,27 @@ def test_status(self):
4645
"""Test the status endpoint returns details for all cores and specific cores."""
4746

4847
# Status of all cores
49-
raw_all = self.solr_admin.status()
50-
all_data = json.loads(raw_all)
48+
result = self.solr_admin.status()
5149

52-
self.assertIn("core0", all_data["status"])
50+
self.assertIn("core0", result["status"])
5351

5452
# Status of a specific core
55-
raw_single = self.solr_admin.status(core="core0")
56-
single_data = json.loads(raw_single)
53+
result = self.solr_admin.status(core="core0")
5754

58-
self.assertEqual(single_data["status"]["core0"]["name"], "core0")
55+
self.assertEqual(result["status"]["core0"]["name"], "core0")
5956

6057
def test_create(self):
6158
"""Test creating a core returns a successful response."""
62-
raw_response = self.solr_admin.create("demo_core1")
63-
data = json.loads(raw_response)
59+
result = self.solr_admin.create("demo_core1")
6460

65-
self.assertEqual(data["responseHeader"]["status"], 0)
66-
self.assertEqual(data["core"], "demo_core1")
61+
self.assertEqual(result["responseHeader"]["status"], 0)
62+
self.assertEqual(result["core"], "demo_core1")
6763

6864
def test_reload(self):
6965
"""Test reloading a core returns a successful response."""
70-
raw_response = self.solr_admin.reload("core0")
71-
data = json.loads(raw_response)
66+
result = self.solr_admin.reload("core0")
7267

73-
self.assertEqual(data["responseHeader"]["status"], 0)
68+
self.assertEqual(result["responseHeader"]["status"], 0)
7469

7570
def test_rename(self):
7671
"""Test renaming a core succeeds and the new name appears in the status."""
@@ -79,16 +74,14 @@ def test_rename(self):
7974
self.solr_admin.create("demo_core1")
8075

8176
# Rename the core to a new name
82-
raw_response = self.solr_admin.rename("demo_core1", "demo_core2")
83-
data = json.loads(raw_response)
77+
result = self.solr_admin.rename("demo_core1", "demo_core2")
8478

85-
self.assertEqual(data["responseHeader"]["status"], 0)
79+
self.assertEqual(result["responseHeader"]["status"], 0)
8680

8781
# Verify that the renamed core appears in the status response
88-
raw_response2 = self.solr_admin.status(core="demo_core2")
89-
data2 = json.loads(raw_response2)
82+
result_2 = self.solr_admin.status(core="demo_core2")
9083

91-
self.assertEqual(data2["status"]["demo_core2"]["name"], "demo_core2")
84+
self.assertEqual(result_2["status"]["demo_core2"]["name"], "demo_core2")
9285

9386
def test_swap(self):
9487
"""
@@ -107,10 +100,9 @@ def test_swap(self):
107100
self.solr_admin.create("demo_core2")
108101

109102
# Perform swap
110-
raw_swap = self.solr_admin.swap("demo_core1", "demo_core2")
111-
swap_data = json.loads(raw_swap)
103+
result = self.solr_admin.swap("demo_core1", "demo_core2")
112104

113-
self.assertEqual(swap_data["responseHeader"]["status"], 0)
105+
self.assertEqual(result["responseHeader"]["status"], 0)
114106

115107
def test_unload(self):
116108
"""
@@ -121,21 +113,19 @@ def test_unload(self):
121113
"""
122114
self.solr_admin.create("demo_core1")
123115

124-
raw_response = self.solr_admin.unload("demo_core1")
125-
data = json.loads(raw_response)
116+
result = self.solr_admin.unload("demo_core1")
126117

127-
self.assertEqual(data["responseHeader"]["status"], 0)
118+
self.assertEqual(result["responseHeader"]["status"], 0)
128119

129120
def test_load(self):
130121
self.assertRaises(NotImplementedError, self.solr_admin.load, "wheatley")
131122

132123
def test_status__nonexistent_core_returns_empty_response(self):
133124
"""Test that requesting status for a missing core returns an empty response."""
134-
raw_response = self.solr_admin.status(core="not_exists")
135-
data = json.loads(raw_response)
125+
result = self.solr_admin.status(core="not_exists")
136126

137-
self.assertNotIn("name", data["status"]["not_exists"])
138-
self.assertNotIn("instanceDir", data["status"]["not_exists"])
127+
self.assertNotIn("name", result["status"]["not_exists"])
128+
self.assertNotIn("instanceDir", result["status"]["not_exists"])
139129

140130
def test_create__existing_core_raises_error(self):
141131
"""Test creating a core that already exists returns a 500 error."""
@@ -144,23 +134,21 @@ def test_create__existing_core_raises_error(self):
144134
self.solr_admin.create("demo_core1")
145135

146136
# Creating the same core again should return a 500 error response
147-
raw_response = self.solr_admin.create("demo_core1")
148-
data = json.loads(raw_response)
137+
result = self.solr_admin.create("demo_core1")
149138

150-
self.assertEqual(data["responseHeader"]["status"], 500)
139+
self.assertEqual(result["responseHeader"]["status"], 500)
151140
self.assertEqual(
152-
data["error"]["msg"], "Core with name 'demo_core1' already exists."
141+
result["error"]["msg"], "Core with name 'demo_core1' already exists."
153142
)
154143

155144
def test_reload__nonexistent_core_raises_error(self):
156145
"""Test that reloading a non-existent core returns a 400 error."""
157-
raw_response = self.solr_admin.reload("not_exists")
158-
data = json.loads(raw_response)
146+
result = self.solr_admin.reload("not_exists")
159147

160148
# Solr returns a 400 error for missing cores
161-
self.assertEqual(data["responseHeader"]["status"], 400)
162-
self.assertIn("No such core", data["error"]["msg"])
163-
self.assertIn("not_exists", data["error"]["msg"])
149+
self.assertEqual(result["responseHeader"]["status"], 400)
150+
self.assertIn("No such core", result["error"]["msg"])
151+
self.assertIn("not_exists", result["error"]["msg"])
164152

165153
def test_rename__nonexistent_core_no_effect(self):
166154
"""
@@ -175,12 +163,11 @@ def test_rename__nonexistent_core_no_effect(self):
175163
self.solr_admin.rename("not_exists", "demo_core99")
176164

177165
# Check the status of the target core to verify the rename had no effect
178-
raw_response = self.solr_admin.status(core="demo_core99")
179-
data = json.loads(raw_response)
166+
result = self.solr_admin.status(core="demo_core99")
180167

181168
# The target core should not exist because the rename operation was ignored
182-
self.assertNotIn("name", data["status"]["demo_core99"])
183-
self.assertNotIn("instanceDir", data["status"]["demo_core99"])
169+
self.assertNotIn("name", result["status"]["demo_core99"])
170+
self.assertNotIn("instanceDir", result["status"]["demo_core99"])
184171

185172
def test_swap__missing_source_core_returns_error(self):
186173
"""Test swapping when the source core is missing returns a 400 error."""
@@ -189,13 +176,12 @@ def test_swap__missing_source_core_returns_error(self):
189176
self.solr_admin.create("demo_core2")
190177

191178
# Attempt to swap a missing source core with an existing target core
192-
raw_response = self.solr_admin.swap("not_exists", "demo_core2")
193-
data = json.loads(raw_response)
179+
result = self.solr_admin.swap("not_exists", "demo_core2")
194180

195181
# Solr returns a 400 error when the source core does not exist
196-
self.assertEqual(data["responseHeader"]["status"], 400)
197-
self.assertIn("No such core", data["error"]["msg"])
198-
self.assertIn("not_exists", data["error"]["msg"])
182+
self.assertEqual(result["responseHeader"]["status"], 400)
183+
self.assertIn("No such core", result["error"]["msg"])
184+
self.assertIn("not_exists", result["error"]["msg"])
199185

200186
def test_swap__missing_target_core_returns_error(self):
201187
"""Test swapping when the target core is missing returns a 400 error."""
@@ -204,22 +190,20 @@ def test_swap__missing_target_core_returns_error(self):
204190
self.solr_admin.create("demo_core1")
205191

206192
# Attempt to swap with a missing target core
207-
raw_response = self.solr_admin.swap("demo_core1", "not_exists")
208-
data = json.loads(raw_response)
193+
result = self.solr_admin.swap("demo_core1", "not_exists")
209194

210195
# Solr returns a 400 error when the target core does not exist
211-
self.assertEqual(data["responseHeader"]["status"], 400)
212-
self.assertIn("No such core", data["error"]["msg"])
213-
self.assertIn("not_exists", data["error"]["msg"])
196+
self.assertEqual(result["responseHeader"]["status"], 400)
197+
self.assertIn("No such core", result["error"]["msg"])
198+
self.assertIn("not_exists", result["error"]["msg"])
214199

215200
def test_unload__nonexistent_core_returns_error(self):
216201
"""Test unloading a non-existent core returns a 400 error response."""
217202

218203
# Attempt to unload a core that does not exist
219-
raw_response = self.solr_admin.unload("not_exists")
220-
data = json.loads(raw_response)
204+
result = self.solr_admin.unload("not_exists")
221205

222206
# Solr returns a 400 error for unloading a missing core
223-
self.assertEqual(data["responseHeader"]["status"], 400)
224-
self.assertIn("Cannot unload non-existent core", data["error"]["msg"])
225-
self.assertIn("not_exists", data["error"]["msg"])
207+
self.assertEqual(result["responseHeader"]["status"], 400)
208+
self.assertIn("Cannot unload non-existent core", result["error"]["msg"])
209+
self.assertIn("not_exists", result["error"]["msg"])

0 commit comments

Comments
 (0)