Skip to content

Commit 23996b6

Browse files
committed
Make calls to numtracker async
1 parent 65ca166 commit 23996b6

6 files changed

Lines changed: 94 additions & 174 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies = [
3434
"observability-utils>=0.1.4",
3535
"pyjwt[crypto]",
3636
"tomlkit",
37+
"httpx>=0.28.1",
3738
]
3839
dynamic = ["version"]
3940
license.file = "LICENSE"
@@ -51,6 +52,7 @@ dev = [
5152
"pyright",
5253
"pytest-cov",
5354
"pytest-asyncio",
55+
"pytest-httpx>=0.35.0",
5456
"responses",
5557
"ruff",
5658
"semver",

src/blueapi/client/numtracker.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44
from textwrap import dedent
55

6-
import requests
6+
import httpx
77
from pydantic import Field
88

99
from blueapi.utils import BlueapiBaseModel
@@ -60,7 +60,7 @@ def set_headers(self, headers: Mapping[str, str]) -> None:
6060

6161
self._headers = headers
6262

63-
def create_scan(
63+
async def create_scan(
6464
self, instrument_session: str, instrument: str
6565
) -> NumtrackerScanMutationResponse:
6666
"""
@@ -92,11 +92,12 @@ def create_scan(
9292
""")
9393
}
9494

95-
response = requests.post(
96-
self._url,
97-
headers=self._headers,
98-
json=query,
99-
)
95+
async with httpx.AsyncClient() as client:
96+
response = await client.post(
97+
self._url,
98+
headers=self._headers,
99+
json=query,
100+
)
100101

101102
response.raise_for_status()
102103
json = response.json()

src/blueapi/service/interface.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,10 @@ def numtracker_client() -> NumtrackerClient | None:
110110
return None
111111

112112

113-
def _update_scan_num(md: dict[str, Any]) -> int:
113+
async def _update_scan_num(md: dict[str, Any]) -> int:
114114
numtracker = numtracker_client()
115115
if numtracker is not None:
116-
scan = numtracker.create_scan(md["instrument_session"], md["instrument"])
116+
scan = await numtracker.create_scan(md["instrument_session"], md["instrument"])
117117
md["data_session_directory"] = str(scan.scan.directory.path)
118118
return scan.scan.scan_number
119119
else:

tests/conftest.py

Lines changed: 6 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import asyncio
22
import base64
33
import time
4-
from collections.abc import Iterable
54
from pathlib import Path
65
from textwrap import dedent
76
from typing import Any, cast
@@ -20,7 +19,6 @@
2019
from opentelemetry.sdk.trace import TracerProvider
2120
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
2221
from opentelemetry.trace import get_tracer_provider
23-
from responses.matchers import json_params_matcher
2422

2523
from blueapi.config import ApplicationConfig, OIDCConfig
2624
from blueapi.service.model import Cache
@@ -335,12 +333,9 @@ def mock_jwks_fetch(json_web_keyset: JWK):
335333
return patch("jwt.PyJWKClient.fetch_data", mock)
336334

337335

338-
NOT_CONFIGURED_INSTRUMENT = "p100"
339-
340-
341-
@pytest.fixture(scope="module")
342-
def mock_numtracker_server() -> Iterable[responses.RequestsMock]:
343-
query_working = {
336+
@pytest.fixture
337+
def nt_query() -> dict[str, str]:
338+
return {
344339
"query": dedent("""
345340
mutation{
346341
scan(
@@ -358,94 +353,11 @@ def mock_numtracker_server() -> Iterable[responses.RequestsMock]:
358353
}
359354
""")
360355
}
361-
query_400 = {
362-
"query": dedent("""
363-
mutation{
364-
scan(
365-
instrument: "p47",
366-
instrumentSession: "ab123"
367-
) {
368-
directory{
369-
instrumentSession
370-
instrument
371-
path
372-
}
373-
scanFile
374-
scanNumber
375-
}
376-
}
377-
""")
378-
}
379-
query_500 = {
380-
"query": dedent("""
381-
mutation{
382-
scan(
383-
instrument: "p48",
384-
instrumentSession: "ab123"
385-
) {
386-
directory{
387-
instrumentSession
388-
instrument
389-
path
390-
}
391-
scanFile
392-
scanNumber
393-
}
394-
}
395-
""")
396-
}
397-
query_key_error = {
398-
"query": dedent("""
399-
mutation{
400-
scan(
401-
instrument: "p49",
402-
instrumentSession: "ab123"
403-
) {
404-
directory{
405-
instrumentSession
406-
instrument
407-
path
408-
}
409-
scanFile
410-
scanNumber
411-
}
412-
}
413-
""")
414-
}
415-
query_200_with_errors = {
416-
"query": dedent(f"""
417-
mutation{{
418-
scan(
419-
instrument: "{NOT_CONFIGURED_INSTRUMENT}",
420-
instrumentSession: "ab123"
421-
) {{
422-
directory{{
423-
instrumentSession
424-
instrument
425-
path
426-
}}
427-
scanFile
428-
scanNumber
429-
}}
430-
}}
431-
""")
432-
}
433356

434-
response_with_errors = {
435-
"data": None,
436-
"errors": [
437-
{
438-
"message": (
439-
"No configuration available for instrument "
440-
f'"{NOT_CONFIGURED_INSTRUMENT}"'
441-
),
442-
"locations": [{"line": 3, "column": 5}],
443-
"path": ["scan"],
444-
}
445-
],
446-
}
447357

448-
working_response = {
358+
@pytest.fixture
359+
def nt_response() -> dict[str, Any]:
360+
return {
449361
"data": {
450362
"scan": {
451363
"scanFile": "p46-11",
@@ -458,42 +370,3 @@ def mock_numtracker_server() -> Iterable[responses.RequestsMock]:
458370
}
459371
}
460372
}
461-
empty_response = {}
462-
463-
with responses.RequestsMock(assert_all_requests_are_fired=False) as requests_mock:
464-
requests_mock.add(
465-
responses.POST,
466-
url="https://numtracker-example.com/graphql",
467-
match=[json_params_matcher(query_working)],
468-
status=200,
469-
json=working_response,
470-
)
471-
requests_mock.add(
472-
responses.POST,
473-
url="https://numtracker-example.com/graphql",
474-
match=[json_params_matcher(query_400)],
475-
status=400,
476-
json=empty_response,
477-
)
478-
requests_mock.add(
479-
responses.POST,
480-
url="https://numtracker-example.com/graphql",
481-
match=[json_params_matcher(query_500)],
482-
status=500,
483-
json=empty_response,
484-
)
485-
requests_mock.add(
486-
responses.POST,
487-
url="https://numtracker-example.com/graphql",
488-
match=[json_params_matcher(query_key_error)],
489-
status=200,
490-
json=empty_response,
491-
)
492-
requests_mock.add(
493-
responses.POST,
494-
"https://numtracker-example.com/graphql",
495-
match=[json_params_matcher(query_200_with_errors)],
496-
status=200,
497-
json=response_with_errors,
498-
)
499-
yield requests_mock
Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
from pathlib import Path
22

3+
import httpx
34
import pytest
4-
import responses
5-
from requests import HTTPError
6-
from tests.conftest import NOT_CONFIGURED_INSTRUMENT
5+
from pytest_httpx import HTTPXMock
76

87
from blueapi.client.numtracker import (
98
DirectoryPath,
@@ -18,11 +17,33 @@ def numtracker() -> NumtrackerClient:
1817
return NumtrackerClient("https://numtracker-example.com/graphql")
1918

2019

21-
def test_create_scan(
22-
numtracker: NumtrackerClient,
23-
mock_numtracker_server: responses.RequestsMock,
20+
URL = "https://numtracker-example.com/graphql"
21+
22+
EMPTY = {}
23+
24+
ERRORS = {
25+
"data": None,
26+
"errors": [
27+
{
28+
"message": "No configuration available for instrument p46",
29+
"locations": [{"line": 3, "column": 5}],
30+
"path": ["scan"],
31+
}
32+
],
33+
}
34+
35+
36+
async def test_create_scan(
37+
numtracker: NumtrackerClient, httpx_mock: HTTPXMock, nt_query, nt_response
2438
):
25-
scan = numtracker.create_scan("ab123", "p46")
39+
httpx_mock.add_response(
40+
method="POST",
41+
url=URL,
42+
match_json=nt_query,
43+
status_code=200,
44+
json=nt_response,
45+
)
46+
scan = await numtracker.create_scan("ab123", "p46")
2647
assert scan == NumtrackerScanMutationResponse(
2748
scan=ScanPaths(
2849
scanFile="p46-11",
@@ -36,42 +57,54 @@ def test_create_scan(
3657
)
3758

3859

39-
def test_create_scan_raises_400_error(
40-
numtracker: NumtrackerClient,
41-
mock_numtracker_server: responses.RequestsMock,
60+
async def test_create_scan_raises_400_error(
61+
numtracker: NumtrackerClient, httpx_mock: HTTPXMock, nt_query
4262
):
63+
httpx_mock.add_response(
64+
method="POST", url=URL, match_json=nt_query, status_code=400, json=EMPTY
65+
)
4366
with pytest.raises(
44-
HTTPError,
45-
match="400 Client Error: Bad Request for url: https://numtracker-example.com/graphql",
67+
httpx.HTTPStatusError,
68+
match="Client error '400 Bad Request' for url 'https://numtracker-example.com/graphql'",
4669
):
47-
numtracker.create_scan("ab123", "p47")
70+
await numtracker.create_scan("ab123", "p46")
4871

4972

50-
def test_create_scan_raises_500_error(
51-
numtracker: NumtrackerClient,
52-
mock_numtracker_server: responses.RequestsMock,
73+
async def test_create_scan_raises_500_error(
74+
numtracker: NumtrackerClient, httpx_mock: HTTPXMock, nt_query
5375
):
76+
httpx_mock.add_response(
77+
method="POST", url=URL, match_json=nt_query, status_code=500, json=EMPTY
78+
)
5479
with pytest.raises(
55-
HTTPError,
56-
match="500 Server Error: Internal Server Error for url: https://numtracker-example.com/graphql",
80+
httpx.HTTPStatusError,
81+
match="Server error '500 Internal Server Error' for url 'https://numtracker-example.com/graphql'",
5782
):
58-
numtracker.create_scan("ab123", "p48")
83+
await numtracker.create_scan("ab123", "p46")
5984

6085

61-
def test_create_scan_raises_key_error_on_incorrectly_formatted_responses(
62-
numtracker: NumtrackerClient,
63-
mock_numtracker_server: responses.RequestsMock,
86+
async def test_create_scan_raises_key_error_on_incorrectly_formatted_responses(
87+
numtracker: NumtrackerClient, httpx_mock: HTTPXMock, nt_query
6488
):
89+
httpx_mock.add_response(
90+
method="POST", url=URL, match_json=nt_query, status_code=200, json=EMPTY
91+
)
6592
with pytest.raises(
6693
KeyError,
6794
match="data",
6895
):
69-
numtracker.create_scan("ab123", "p49")
96+
await numtracker.create_scan("ab123", "p46")
7097

7198

72-
def test_create_scan_raises_runtime_error_on_graphql_error(
73-
numtracker: NumtrackerClient,
74-
mock_numtracker_server: responses.RequestsMock,
99+
async def test_create_scan_raises_runtime_error_on_graphql_error(
100+
numtracker: NumtrackerClient, httpx_mock: HTTPXMock, nt_query
75101
):
102+
httpx_mock.add_response(
103+
method="POST",
104+
url=URL,
105+
match_json=nt_query,
106+
status_code=200,
107+
json=ERRORS,
108+
)
76109
with pytest.raises(RuntimeError, match="Numtracker error:"):
77-
numtracker.create_scan("ab123", NOT_CONFIGURED_INSTRUMENT)
110+
await numtracker.create_scan("ab123", "p46")

0 commit comments

Comments
 (0)