Skip to content

Commit 66651cf

Browse files
naufalandikaMuhammad Naufal Andika Natsir Putra
and
Muhammad Naufal Andika Natsir Putra
authored
feat(sdk): migrate unit test from urllib3-mock to MagicMock (caraml-dev#405)
* feat: run sdk unit test for python 3.9-3.12 * chore: adjust dependency to support python 3.11++ * feat: adjust test_list_jobs * feat: adjust test_submit_job and create fixture for active project magic mock * feat: adjust test_fetch_job * feat: adjust test_terminate_job * feat: adjust test_list_images * feat: adjust test_create_image * feat: adjust test_list_ensemblers * feat: adjust test_create_ensembler * chore: remove unnecessary context manager * feat: adjust test_update_ensembler * feat: refactor magic mock for mlflow and gcs to fixtures * feat: adjust test_update_ensembler_existing_router_version * feat: adjust test_update_ensembler_existing_job * feat: adjust test_delete_ensembler * feat: remove unused fixtures * feat: adjust test_list_projects * feat: adjust test_create_version * feat: adjust test_list_routers * feat: adjust test_create_router * feat: adjust test_delete_router * feat: adjust test_get_router * feat: adjust test_update_router * feat: adjust test_deploy_router * feat: adjust test_undeploy_router * feat: adjust test_list_versions * feat: adjust test_get_version * feat: adjust test_get_version_config * feat: adjust test_delete_version * feat: adjust test_deploy_version * feat: adjust test_get_events_list * feat: adjust test_wait_for_status * feat: adjust test_wait_for_version_status * chore: try to upgrade setuptools version * feat: bump numpy version to support python 3.9 - 3.12 * chore: install wheel * chore: adjust numpy version * feat: bump python version used by publish CI --------- Co-authored-by: Muhammad Naufal Andika Natsir Putra <[email protected]>
1 parent 9b0ccd2 commit 66651cf

File tree

13 files changed

+1128
-1076
lines changed

13 files changed

+1128
-1076
lines changed

.github/workflows/sdk.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
runs-on: ubuntu-latest
2626
strategy:
2727
matrix:
28-
python-version: ["3.8", "3.9", "3.10"]
28+
python-version: ["3.9", "3.10", "3.11", "3.12"]
2929

3030
defaults:
3131
run:
@@ -92,7 +92,7 @@ jobs:
9292
- name: Setup Python
9393
uses: actions/setup-python@v5
9494
with:
95-
python-version: 3.8
95+
python-version: 3.11
9696
cache: pip
9797
cache-dependency-path: |
9898
sdk/requirements.txt

sdk/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.PHONY: setup
22
setup:
3-
pip install -r requirements.txt -r requirements.dev.txt
3+
@pip install "setuptools>=64,<75" "wheel"
4+
@pip install -r requirements.txt -r requirements.dev.txt
45

56
.PHONY: gen-client
67
gen-client:

sdk/requirements.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ deprecation==2.1.0
33
fire
44
google-cloud-storage>=1.19.0
55
mlflow>=1.26.1,<2.0.0
6-
# Numpy >= v1.24.0 is incompatible with our pinned versions of mlflow due to the deprecation of several common numpy
7-
# aliases (see the last bullet point here: https://numpy.org/doc/stable/release/1.24.0-notes.html#expired-deprecations).
8-
numpy<1.24.0
96
pandas
107
protobuf>=3.12.0,<5.0.0 # Determined by the mlflow dependency
118
python_dateutil>=2.5.3
129
requests
1310
urllib3>=1.25.3
14-
caraml-auth-google==0.0.0.post7
11+
caraml-auth-google==0.0.0.post16.dev0
1512
PyPrind>=2.11.2
13+
numpy>=1.26.0

sdk/tests/batch/job_test.py

Lines changed: 101 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import json
12
import os
3+
from unittest.mock import MagicMock, patch
4+
25
import pytest
36
from tests import utc_date
7+
import tests
48
import turing
59
import turing.batch
610
import turing.batch.config
7-
from urllib3_mock import Responses
811

9-
responses = Responses("requests.packages.urllib3")
1012
data_dir = os.path.join(os.path.dirname(__file__), "../testdata/api_responses")
1113

1214
with open(os.path.join(data_dir, "list_jobs_0000.json")) as f:
@@ -18,13 +20,6 @@
1820
with open(os.path.join(data_dir, "get_job_0000.json")) as f:
1921
get_job_0000 = f.read()
2022

21-
22-
@pytest.fixture(scope="module", name="responses")
23-
def _responses():
24-
return responses
25-
26-
27-
@responses.activate
2823
@pytest.mark.parametrize(
2924
"api_response, expected",
3025
[
@@ -62,36 +57,37 @@ def _responses():
6257
],
6358
)
6459
def test_list_jobs(
65-
turing_api, active_project, api_response, expected, use_google_oauth
60+
turing_api, project, api_response, expected, use_google_oauth, active_project_magic_mock
6661
):
67-
turing.set_url(turing_api, use_google_oauth)
68-
turing.set_project(active_project.name)
69-
70-
responses.add(
71-
method="GET",
72-
url=f"/v1/projects/{active_project.id}/jobs?"
62+
with patch("urllib3.PoolManager.request") as mock_request:
63+
turing.set_url(turing_api, use_google_oauth)
64+
65+
mock_request.return_value = active_project_magic_mock
66+
turing.set_project(project.name)
67+
68+
mock_response = MagicMock()
69+
mock_response.method = "GET"
70+
mock_response.status = 200
71+
mock_response.path = f"/v1/projects/{project.id}/jobs?"
7372
f"status={turing.batch.EnsemblingJobStatus.PENDING.value}&"
74-
f"status={turing.batch.EnsemblingJobStatus.RUNNING.value}",
75-
body=api_response,
76-
match_querystring=True,
77-
status=200,
78-
content_type="application/json",
79-
)
80-
81-
actual = turing.batch.EnsemblingJob.list(
82-
status=[
83-
turing.batch.EnsemblingJobStatus.PENDING,
84-
turing.batch.EnsemblingJobStatus.RUNNING,
85-
]
86-
)
87-
88-
assert len(actual) == len(expected)
73+
f"status={turing.batch.EnsemblingJobStatus.RUNNING.value}"
74+
mock_response.data = api_response.encode('utf-8')
75+
mock_response.getheader.return_value = 'application/json'
76+
77+
mock_request.return_value = mock_response
8978

90-
for actual, expected in zip(actual, expected):
91-
assert actual == expected
79+
actual = turing.batch.EnsemblingJob.list(
80+
status=[
81+
turing.batch.EnsemblingJobStatus.PENDING,
82+
turing.batch.EnsemblingJobStatus.RUNNING,
83+
]
84+
)
9285

86+
assert len(actual) == len(expected)
9387

94-
@responses.activate
88+
for actual, expected in zip(actual, expected):
89+
assert actual == expected
90+
9591
@pytest.mark.parametrize(
9692
"api_response, expected",
9793
[
@@ -112,31 +108,34 @@ def test_list_jobs(
112108
)
113109
def test_submit_job(
114110
turing_api,
115-
active_project,
111+
project,
116112
ensembling_job_config,
117113
api_response,
118114
expected,
119115
use_google_oauth,
116+
active_project_magic_mock
120117
):
121-
turing.set_url(turing_api, use_google_oauth)
122-
turing.set_project(active_project.name)
118+
with patch("urllib3.PoolManager.request") as mock_request:
119+
turing.set_url(turing_api, use_google_oauth)
123120

124-
responses.add(
125-
method="POST",
126-
url=f"/v1/projects/{active_project.id}/jobs",
127-
body=api_response,
128-
status=201,
129-
content_type="application/json",
130-
)
121+
mock_request.return_value = active_project_magic_mock
122+
turing.set_project(project.name)
123+
124+
mock_response = MagicMock()
125+
mock_response.method = "POST"
126+
mock_response.status = 201
127+
mock_response.path = f"/v1/projects/{project.id}/jobs"
128+
mock_response.data = api_response.encode('utf-8')
129+
mock_response.getheader.return_value = 'application/json'
131130

132-
actual = turing.batch.job.EnsemblingJob.submit(
133-
ensembler_id=2,
134-
config=ensembling_job_config,
135-
)
136-
assert actual == expected
131+
mock_request.return_value = mock_response
137132

133+
actual = turing.batch.job.EnsemblingJob.submit(
134+
ensembler_id=2,
135+
config=ensembling_job_config,
136+
)
137+
assert actual == expected
138138

139-
@responses.activate
140139
@pytest.mark.parametrize(
141140
"api_response_get, expected, api_response_refresh, updated",
142141
[
@@ -168,43 +167,46 @@ def test_submit_job(
168167
)
169168
def test_fetch_job(
170169
turing_api,
171-
active_project,
170+
project,
172171
api_response_get,
173172
expected,
174173
api_response_refresh,
175174
updated,
176175
use_google_oauth,
176+
active_project_magic_mock
177177
):
178-
turing.set_url(turing_api, use_google_oauth)
179-
turing.set_project(active_project.name)
180-
181-
responses.add(
182-
method="GET",
183-
url=f"/v1/projects/{active_project.id}/jobs/{expected.id}",
184-
body=api_response_get,
185-
status=200,
186-
content_type="application/json",
187-
)
188-
189-
job = turing.batch.EnsemblingJob.get_by_id(expected.id)
178+
with patch("urllib3.PoolManager.request") as mock_request:
179+
turing.set_url(turing_api, use_google_oauth)
190180

191-
assert job == expected
181+
mock_request.return_value = active_project_magic_mock
182+
turing.set_project(project.name)
192183

193-
responses.reset()
194-
responses.add(
195-
method="GET",
196-
url=f"/v1/projects/{active_project.id}/jobs/{expected.id}",
197-
body=api_response_refresh,
198-
status=200,
199-
content_type="application/json",
200-
)
184+
mock_response = MagicMock()
185+
mock_response.method = "GET"
186+
mock_response.status = 200
187+
mock_response.path = f"/v1/projects/{project.id}/jobs/{expected.id}"
188+
mock_response.data = api_response_get.encode('utf-8')
189+
mock_response.getheader.return_value = 'application/json'
190+
191+
mock_request.return_value = mock_response
201192

202-
job.refresh()
193+
job = turing.batch.EnsemblingJob.get_by_id(expected.id)
203194

204-
assert job == updated
195+
assert job == expected
196+
197+
mock_response = MagicMock()
198+
mock_response.method = "GET"
199+
mock_response.status = 200
200+
mock_response.path = f"/v1/projects/{project.id}/jobs/{expected.id}"
201+
mock_response.data = api_response_refresh.encode('utf-8')
202+
mock_response.getheader.return_value = 'application/json'
203+
204+
mock_request.return_value = mock_response
205205

206+
job.refresh()
206207

207-
@responses.activate
208+
assert job == updated
209+
208210
@pytest.mark.parametrize(
209211
"job, api_response_delete, api_response_get, expected",
210212
[
@@ -236,34 +238,38 @@ def test_fetch_job(
236238
)
237239
def test_terminate_job(
238240
turing_api,
239-
active_project,
241+
project,
240242
job,
241243
api_response_delete,
242244
api_response_get,
243245
expected,
244246
use_google_oauth,
247+
active_project_magic_mock
245248
):
246-
turing.set_url(turing_api, use_google_oauth)
247-
turing.set_project(active_project.name)
249+
with patch("urllib3.PoolManager.request") as mock_request:
250+
turing.set_url(turing_api, use_google_oauth)
248251

249-
responses.add(
250-
method="DELETE",
251-
url=f"/v1/projects/{active_project.id}/jobs/{job.id}",
252-
body=api_response_delete,
253-
status=201,
254-
content_type="application/json",
255-
)
252+
mock_request.return_value = active_project_magic_mock
253+
turing.set_project(project.name)
256254

257-
responses.add(
258-
method="GET",
259-
url=f"/v1/projects/{active_project.id}/jobs/{job.id}",
260-
body=api_response_get,
261-
status=200,
262-
content_type="application/json",
263-
)
255+
mock_response_1 = MagicMock()
256+
mock_response_1.method = "DELETE"
257+
mock_response_1.status = 201
258+
mock_response_1.path = f"/v1/projects/{project.id}/jobs/{job.id}"
259+
mock_response_1.data = api_response_delete.encode('utf-8')
260+
mock_response_1.getheader.return_value = 'application/json'
261+
262+
mock_response_2 = MagicMock()
263+
mock_response_2.method = "get"
264+
mock_response_2.status = 200
265+
mock_response_2.path = f"/v1/projects/{project.id}/jobs/{job.id}"
266+
mock_response_2.data = api_response_get.encode('utf-8')
267+
mock_response_2.getheader.return_value = 'application/json'
268+
269+
mock_request.side_effect = [mock_response_1, mock_response_2]
264270

265-
assert job != expected
271+
assert job != expected
266272

267-
job.terminate()
273+
job.terminate()
268274

269-
assert job == expected
275+
assert job == expected

0 commit comments

Comments
 (0)