Skip to content

Commit 99c68a4

Browse files
authored
feat: Refactor http client to be async (#30)
* Refactor http client to be async * Start testing http_client * Make all client functions async * Fix tests for http client * Fix ping tests * Fix read_subjects tests * Fix read_subject tests * Fix write events tests * Fix read_events tests * Fix observe event tests * Refactor observe events tests * Refactor read events tests * Refactor tests
1 parent 83753bb commit 99c68a4

27 files changed

+1670
-890
lines changed

eventsourcingdb_client_python/abstract_base_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABC, abstractmethod
2-
from .http_client import HttpClient
2+
from .http_client.http_client import HttpClient
33

44

55
class AbstractBaseClient(ABC):
Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from collections.abc import Generator
1+
from collections.abc import AsyncGenerator
22

33
from .abstract_base_client import AbstractBaseClient
44
from .client_configuration import ClientConfiguration
@@ -7,7 +7,7 @@
77
from .event.event_context import EventContext
88
from .handlers.observe_events.observe_events import observe_events
99
from .handlers.observe_events.observe_events_options import ObserveEventsOptions
10-
from .http_client import HttpClient
10+
from .http_client.http_client import HttpClient
1111
from .handlers.ping import ping
1212
from .handlers.read_events import read_events, ReadEventsOptions
1313
from .handlers.read_subjects import read_subjects
@@ -16,54 +16,75 @@
1616

1717

1818
class Client(AbstractBaseClient):
19-
def __init__(
20-
self,
19+
__create_key = object()
20+
21+
@classmethod
22+
async def create(
23+
cls,
2124
base_url: str,
2225
access_token: str,
2326
options: ClientOptions = ClientOptions()
24-
):
25-
self.configuration: ClientConfiguration = ClientConfiguration(
27+
) -> 'Client':
28+
configuration = ClientConfiguration(
2629
base_url=base_url,
2730
timeout_seconds=options.timeout_seconds,
2831
access_token=access_token,
2932
protocol_version=options.protocol_version,
3033
max_tries=options.max_tries
3134
)
3235

33-
self.__http_client: HttpClient = HttpClient(self.configuration)
36+
http_client = await HttpClient.create(configuration)
37+
38+
return cls(Client.__create_key, http_client)
39+
40+
def __init__(
41+
self,
42+
create_key,
43+
http_client: HttpClient
44+
):
45+
assert create_key == Client.__create_key, \
46+
'Client objects must be created using Client.create.'
47+
48+
self.__http_client = http_client
49+
50+
async def close(self):
51+
await self.__http_client.close()
3452

3553
@property
3654
def http_client(self) -> HttpClient:
3755
return self.__http_client
3856

39-
def ping(self) -> None:
40-
return ping(self)
57+
async def ping(self) -> None:
58+
return await ping(self)
4159

42-
def read_subjects(
60+
async def read_subjects(
4361
self,
4462
base_subject: str
45-
) -> Generator[str, None, None]:
46-
return read_subjects(self, base_subject)
63+
) -> AsyncGenerator[str, None]:
64+
async for subject in read_subjects(self, base_subject):
65+
yield subject
4766

48-
def read_events(
67+
async def read_events(
4968
self,
5069
subject: str,
5170
options: ReadEventsOptions
52-
) -> Generator[StoreItem, None, None]:
53-
return read_events(self, subject, options)
71+
) -> AsyncGenerator[StoreItem, None]:
72+
async for event in read_events(self, subject, options):
73+
yield event
5474

55-
def observe_events(
75+
async def observe_events(
5676
self,
5777
subject: str,
5878
options: ObserveEventsOptions
59-
) -> Generator[StoreItem, None, None]:
60-
return observe_events(self, subject, options)
79+
) -> AsyncGenerator[StoreItem, None]:
80+
async for event in observe_events(self, subject, options):
81+
yield event
6182

62-
def write_events(
83+
async def write_events(
6384
self,
6485
event_candidates: list[EventCandidate],
6586
preconditions: list[Precondition] = None
6687
) -> list[EventContext]:
6788
if preconditions is None:
6889
preconditions = []
69-
return write_events(self, event_candidates, preconditions)
90+
return await write_events(self, event_candidates, preconditions)

eventsourcingdb_client_python/handlers/observe_events/observe_events.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import json
2-
from collections.abc import Generator
2+
from collections.abc import AsyncGenerator
33
from http import HTTPStatus
44

5-
import requests
6-
75
from ..is_heartbeat import is_heartbeat
86
from ..is_item import is_item
97
from ..is_stream_error import is_stream_error
@@ -18,13 +16,14 @@
1816
from ...event.validate_subject import validate_subject
1917
from ..store_item import StoreItem
2018
from .observe_events_options import ObserveEventsOptions
19+
from ...http_client.response import Response
2120

2221

23-
def observe_events(
22+
async def observe_events(
2423
client: AbstractBaseClient,
2524
subject: str,
2625
options: ObserveEventsOptions
27-
) -> Generator[StoreItem, None, None]:
26+
) -> AsyncGenerator[StoreItem, None]:
2827
try:
2928
validate_subject(subject)
3029
except ValidationError as validation_error:
@@ -44,12 +43,11 @@ def observe_events(
4443
'options': options.to_json()
4544
})
4645

47-
response: requests.Response
46+
response: Response
4847
try:
49-
response = client.http_client.post(
48+
response = await client.http_client.post(
5049
path='/api/observe-events',
5150
request_body=request_body,
52-
stream_response=True
5351
)
5452
except CustomError as custom_error:
5553
raise custom_error
@@ -62,7 +60,7 @@ def observe_events(
6260
f'Unexpected response status: '
6361
f'{response.status_code} {HTTPStatus(response.status_code).phrase}'
6462
)
65-
for raw_message in response.iter_lines():
63+
async for raw_message in response.body:
6664
message = parse_raw_message(raw_message)
6765

6866
if is_heartbeat(message):

eventsourcingdb_client_python/handlers/ping.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
from ..errors.server_error import ServerError
55

66

7-
def ping(client: AbstractBaseClient) -> None:
8-
response = client.http_client.get('/ping')
7+
async def ping(client: AbstractBaseClient) -> None:
8+
response = await client.http_client.get('/ping')
9+
response_body = bytes.decode(await response.body.read(), encoding='utf-8')
910

10-
if response.status_code != HTTPStatus.OK or response.text != HTTPStatus.OK.phrase:
11-
raise ServerError(f'Received unexpected response: {response.text}')
11+
if response.status_code != HTTPStatus.OK or response_body != HTTPStatus.OK.phrase:
12+
raise ServerError(f'Received unexpected response: {response_body}')

eventsourcingdb_client_python/handlers/read_events/read_events.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
import json
33
from http import HTTPStatus
44

5-
import requests
6-
75
from ...abstract_base_client import AbstractBaseClient
86
from ...errors.custom_error import CustomError
97
from ...errors.internal_error import InternalError
@@ -12,14 +10,15 @@
1210
from ...errors.validation_error import ValidationError
1311
from ...event.event import Event
1412
from ...event.validate_subject import validate_subject
13+
from ...http_client.response import Response
1514
from ..is_item import is_item
1615
from ..is_stream_error import is_stream_error
1716
from ..parse_raw_message import parse_raw_message
1817
from ..store_item import StoreItem
1918
from .read_events_options import ReadEventsOptions
2019

2120

22-
def read_events(
21+
async def read_events(
2322
client: AbstractBaseClient,
2423
subject: str,
2524
options: ReadEventsOptions
@@ -43,12 +42,11 @@ def read_events(
4342
'options': options.to_json()
4443
})
4544

46-
response: requests.Response
45+
response: Response
4746
try:
48-
response = client.http_client.post(
47+
response = await client.http_client.post(
4948
path='/api/read-events',
5049
request_body=request_body,
51-
stream_response=True
5250
)
5351
except CustomError as custom_error:
5452
raise custom_error
@@ -61,7 +59,7 @@ def read_events(
6159
f'Unexpected response status: '
6260
f'{response.status_code} {HTTPStatus(response.status_code).phrase}'
6361
)
64-
for raw_message in response.iter_lines():
62+
async for raw_message in response.body:
6563
message = parse_raw_message(raw_message)
6664

6765
if is_stream_error(message):

eventsourcingdb_client_python/handlers/read_subjects/read_subjects.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,20 @@
22
import json
33
from http import HTTPStatus
44

5-
import requests
6-
75
from ...abstract_base_client import AbstractBaseClient
86
from ...errors.custom_error import CustomError
97
from ...errors.internal_error import InternalError
108
from ...errors.invalid_parameter_error import InvalidParameterError
119
from ...errors.server_error import ServerError
1210
from ...errors.validation_error import ValidationError
11+
from ...http_client.response import Response
1312
from ..is_stream_error import is_stream_error
1413
from ..parse_raw_message import parse_raw_message
1514
from .is_subject import is_subject
1615
from ...event.validate_subject import validate_subject
1716

1817

19-
def read_subjects(
18+
async def read_subjects(
2019
client: AbstractBaseClient,
2120
base_subject: str
2221
) -> Generator[str, None, None]:
@@ -31,12 +30,11 @@ def read_subjects(
3130
'baseSubject': base_subject
3231
})
3332

34-
response: requests.Response
33+
response: Response
3534
try:
36-
response = client.http_client.post(
35+
response = await client.http_client.post(
3736
path='/api/read-subjects',
3837
request_body=request_body,
39-
stream_response=True
4038
)
4139
except CustomError as custom_error:
4240
raise custom_error
@@ -49,7 +47,7 @@ def read_subjects(
4947
f'Unexpected response status: '
5048
f'{response.status_code} {HTTPStatus(response.status_code).phrase}'
5149
)
52-
for raw_message in response.iter_lines():
50+
async for raw_message in response.body:
5351
message = parse_raw_message(raw_message)
5452

5553
if is_stream_error(message):

eventsourcingdb_client_python/handlers/write_events/write_events.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
from http import HTTPStatus
22
import json
3-
from typing import Any
4-
5-
import requests
63

74
from ...abstract_base_client import AbstractBaseClient
85
from ...errors.custom_error import CustomError
@@ -12,10 +9,11 @@
129
from ...errors.validation_error import ValidationError
1310
from ...event.event_candidate import EventCandidate
1411
from ...event.event_context import EventContext
12+
from ...http_client.response import Response
1513
from .preconditions import Precondition
1614

1715

18-
def write_events(
16+
async def write_events(
1917
client: AbstractBaseClient,
2018
event_candidates: list[EventCandidate],
2119
preconditions: list[Precondition]
@@ -41,9 +39,9 @@ def write_events(
4139
'preconditions': [precondition.to_json() for precondition in preconditions]
4240
})
4341

44-
response: requests.Response
42+
response: Response
4543
try:
46-
response = client.http_client.post(
44+
response = await client.http_client.post(
4745
path='/api/write-events',
4846
request_body=request_body,
4947
)
@@ -58,10 +56,11 @@ def write_events(
5856
f'{response.status_code} {HTTPStatus(response.status_code).phrase}.'
5957
)
6058

61-
response_data: Any
59+
response_data = await response.body.read()
60+
response_data = bytes.decode(response_data, encoding='utf-8')
6261
try:
63-
response_data = response.json()
64-
except requests.exceptions.JSONDecodeError as decode_error:
62+
response_data = json.loads(response_data)
63+
except json.JSONDecodeError as decode_error:
6564
raise ServerError(str(decode_error)) from decode_error
6665
except Exception as other_error:
6766
raise InternalError(str(other_error)) from other_error
@@ -70,13 +69,13 @@ def write_events(
7069
raise ServerError(
7170
f'Failed to parse response \'{response_data}\' to list.')
7271

73-
return_value = []
72+
result = []
7473
for unparsed_event_context in response_data:
7574
try:
76-
return_value.append(EventContext.parse(unparsed_event_context))
75+
result.append(EventContext.parse(unparsed_event_context))
7776
except ValidationError as validation_error:
7877
raise ServerError(str(validation_error)) from validation_error
7978
except Exception as other_error:
8079
raise InternalError(str(other_error)) from other_error
8180

82-
return return_value
81+
return result

0 commit comments

Comments
 (0)