Skip to content

Commit 7fcf28e

Browse files
committed
fix linting
1 parent c94a26e commit 7fcf28e

File tree

18 files changed

+177
-125
lines changed

18 files changed

+177
-125
lines changed

eventsourcingdb/abstract_base_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ class AbstractBaseClient(ABC):
66
@property
77
@abstractmethod
88
def http_client(self) -> HttpClient:
9-
raise NotImplementedError()
9+
raise NotImplementedError()

eventsourcingdb/client.py

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections.abc import AsyncGenerator
2+
import contextlib
23
from types import TracebackType
3-
from typing import Optional, Type, TypeVar
4+
from typing import TypeVar
45

56
from eventsourcingdb.abstract_base_client import AbstractBaseClient
67

@@ -21,6 +22,7 @@
2122

2223
T = TypeVar('T')
2324

25+
2426
class Client(AbstractBaseClient):
2527
def __init__(
2628
self,
@@ -34,89 +36,71 @@ async def __aenter__(self):
3436
return self
3537

3638
async def __aexit__(
37-
self,
38-
exc_type: Optional[Type[BaseException]],
39-
exc_val: Optional[BaseException],
40-
exc_tb: Optional[TracebackType]
39+
self,
40+
exc_type: BaseException | None = None,
41+
exc_val: BaseException | None = None,
42+
exc_tb: TracebackType | None = None,
4143
) -> None:
4244
await self.__http_client.__aexit__(exc_tb=exc_tb, exc_val=exc_val, exc_type=exc_type)
4345

4446
async def initialize(self) -> None:
4547
await self.__http_client.initialize()
4648

4749
async def close(self) -> None:
48-
await self.__http_client.close()
49-
50+
await self.__http_client.close()
51+
5052
@property
5153
def http_client(self) -> HttpClient:
5254
return self.__http_client
5355

5456
async def ping(self) -> None:
5557
return await ping(self)
56-
58+
5759
async def verify_api_token(self) -> None:
5860
raise NotImplementedError("verify_api_token is not implemented yet.")
59-
61+
6062
async def write_events(
6163
self,
6264
event_candidates: list[EventCandidate],
63-
preconditions: list[Precondition] = None # type: ignore
64-
) -> list[EventContext]: # TODO: list[Event] of Events (complete)
65+
preconditions: list[Precondition] = None # type: ignore
66+
) -> list[EventContext]:
6567
if preconditions is None:
6668
preconditions = []
6769
return await write_events(self, event_candidates, preconditions)
68-
70+
6971
async def read_events(
7072
self,
7173
subject: str,
7274
options: ReadEventsOptions
73-
) -> AsyncGenerator[Event, None]:
74-
generator = read_events(self, subject, options)
75-
try:
75+
) -> AsyncGenerator[Event]:
76+
async with contextlib.aclosing(read_events(self, subject, options)) as generator:
7677
async for item in generator:
7778
yield item
78-
finally:
79-
await generator.aclose()
80-
# TODO: run eventql query
81-
async def run_eventql_query(self, query: str) -> AsyncGenerator[Event, None]:
82-
"""
83-
the issue like read_events. can be abort or canceled.
84-
"""
79+
80+
async def run_eventql_query(self, query: str) -> AsyncGenerator[Event]:
8581
raise NotImplementedError("run_eventql_query is not implemented yet.")
8682

8783
async def observe_events(
8884
self,
8985
subject: str,
9086
options: ObserveEventsOptions
91-
) -> AsyncGenerator[Event, None]:
92-
generator = observe_events(self, subject, options)
93-
try:
87+
) -> AsyncGenerator[Event]:
88+
async with contextlib.aclosing(observe_events(self, subject, options)) as generator:
9489
async for item in generator:
9590
yield item
96-
finally:
97-
await generator.aclose()
9891

9992
async def register_event_schema(self, event_type: str, json_schema: dict) -> None:
100-
# Updated type hint to reflect it should be a dict
10193
await register_event_schema(self, event_type, json_schema)
10294

10395
async def read_subjects(
10496
self,
10597
base_subject: str
106-
) -> AsyncGenerator[str, None]:
107-
"""Read subjects with proper cancellation support."""
108-
generator = read_subjects(self, base_subject)
109-
try:
98+
) -> AsyncGenerator[str]:
99+
async with contextlib.aclosing(read_subjects(self, base_subject)) as generator:
110100
async for item in generator:
111101
yield item
112-
finally:
113-
await generator.aclose()
114-
115-
async def read_event_types(self) -> AsyncGenerator[EventType, None]:
116-
"""Read event types with proper cancellation support."""
117-
generator = read_event_types(self)
118-
try:
102+
103+
async def read_event_types(self) -> AsyncGenerator[EventType]:
104+
async with contextlib.aclosing(read_event_types(self)) as generator:
119105
async for item in generator:
120106
yield item
121-
finally:
122-
await generator.aclose()

eventsourcingdb/container.py

Lines changed: 98 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1+
import logging
12
import time
3+
from http import HTTPStatus
4+
25
import docker
36
import requests
4-
from typing import Optional
57
from docker import errors
68

79
from eventsourcingdb.client import Client
810

11+
HOST_PORT_KEY = 'HostPort'
12+
HTTP_OK = HTTPStatus.OK
13+
914

1015
class Container:
1116
def __init__(
1217
self,
1318
image_name: str = "thenativeweb/eventsourcingdb",
14-
image_tag: str = "0.113.2",
19+
image_tag: str = "0.120.4",
1520
api_token: str = "secret",
1621
internal_port: int = 3000
1722
):
@@ -21,7 +26,7 @@ def __init__(
2126
self._api_token = api_token
2227
self._container = None
2328
self._docker_client = docker.from_env()
24-
self._mapped_port: Optional[int] = None
29+
self._mapped_port: int | None = None
2530
self._host = "localhost"
2631

2732
def with_image_tag(self, tag: str) -> "Container":
@@ -39,7 +44,7 @@ def with_port(self, port: int) -> "Container":
3944
def start(self) -> "Container":
4045
if self._container is not None:
4146
return self
42-
47+
4348
try:
4449
self._docker_client.images.pull(self._image_name, self._image_tag)
4550
except errors.APIError as e:
@@ -49,17 +54,27 @@ def start(self) -> "Container":
4954
self._create_container()
5055
self._fetch_mapped_port()
5156
self._wait_for_http("/api/v1/ping", timeout=20)
52-
57+
5358
return self
5459

5560
def _cleanup_existing_containers(self) -> None:
5661
try:
57-
for container in self._docker_client.containers.list(
58-
filters={"ancestor": f"{self._image_name}:{self._image_tag}"}):
62+
containers = self._docker_client.containers.list(
63+
filters={"ancestor": f"{self._image_name}:{self._image_tag}"})
64+
except errors.APIError as e:
65+
logging.warning("Warning: Error listing existing containers: %s", e)
66+
return
67+
68+
for container in containers:
69+
try:
5970
container.stop()
71+
except errors.APIError as e:
72+
logging.warning("Warning: Error stopping container: %s", e)
73+
74+
try:
6075
container.remove()
61-
except Exception as e:
62-
print(f"Warning: Error stopping existing containers: {e}")
76+
except errors.APIError as e:
77+
logging.warning("Warning: Error removing container: %s", e)
6378

6479
def _create_container(self) -> None:
6580
port_bindings = {f"{self._internal_port}/tcp": None}
@@ -73,64 +88,109 @@ def _create_container(self) -> None:
7388
"--http-enabled",
7489
"--https-enabled=false",
7590
],
76-
ports=port_bindings, # type: ignore
91+
ports=port_bindings, # type: ignore
7792
detach=True,
78-
) # type: ignore
93+
) # type: ignore
7994

8095
def _fetch_mapped_port(self) -> None:
8196
if self._container is None:
8297
raise RuntimeError("Container failed to start")
83-
98+
8499
max_retries, retry_delay = 5, 1
85-
100+
86101
for attempt in range(max_retries):
87-
try:
88-
container_info = self._docker_client.api.inspect_container(
89-
self._container.id)
90-
port_mappings = container_info["NetworkSettings"]["Ports"].get(f"{self._internal_port}/tcp")
91-
92-
if port_mappings and "HostPort" in port_mappings[0]:
93-
self._mapped_port = int(port_mappings[0]["HostPort"])
94-
return
95-
except (KeyError, IndexError, AttributeError):
96-
pass
97-
102+
if (port := self._try_get_port_from_container()) is not None:
103+
self._mapped_port = port
104+
return
105+
98106
if attempt < max_retries - 1:
99107
time.sleep(retry_delay)
100-
101-
# Failed to get port, clean up
108+
102109
self._stop_and_remove_container()
103110
raise RuntimeError("Failed to determine mapped port")
104111

112+
def _try_get_port_from_container(self) -> int | None:
113+
if not self._container:
114+
return None
115+
116+
if not (container_info := self._get_container_info()):
117+
return None
118+
119+
return self._extract_port_from_container_info(container_info)
120+
121+
def _get_container_info(self):
122+
if self._container is None:
123+
return None
124+
return self._docker_client.api.inspect_container(self._container.id)
125+
126+
def _extract_port_from_container_info(self, container_info):
127+
port = None
128+
valid_mapping = True
129+
port_mappings = None
130+
131+
try:
132+
port_mappings = container_info["NetworkSettings"]["Ports"].get(
133+
f"{self._internal_port}/tcp")
134+
except KeyError:
135+
valid_mapping = False
136+
137+
if valid_mapping and port_mappings and isinstance(port_mappings, list) and port_mappings:
138+
first_mapping = port_mappings[0]
139+
140+
if HOST_PORT_KEY in first_mapping:
141+
port_value = first_mapping[HOST_PORT_KEY]
142+
port = int(port_value)
143+
144+
return port
145+
105146
def _wait_for_http(self, path: str, timeout: int) -> None:
106147
base_url = self.get_base_url()
107148
url = f"{base_url}{path}"
108149
start_time = time.time()
109-
110-
while time.time() - start_time < timeout:
150+
151+
max_attempts = int(timeout * 2)
152+
for _ in range(max_attempts):
153+
if time.time() - start_time >= timeout:
154+
break
155+
156+
response = None
157+
status_code = None
111158
try:
112159
response = requests.get(url, timeout=2)
113-
if response.status_code == 200:
114-
return
115160
except requests.RequestException:
116161
pass
162+
163+
if response is not None:
164+
status_code = response.status_code
165+
166+
if response is not None and status_code == HTTP_OK:
167+
return
168+
117169
time.sleep(0.5)
118-
170+
119171
self._stop_and_remove_container()
120172
raise TimeoutError(f"Service failed to become ready within {timeout} seconds")
121173

122174
def _stop_and_remove_container(self) -> None:
123175
if self._container is None:
124176
return
125-
126-
try:
177+
178+
try:
127179
self._container.stop()
180+
except errors.NotFound as e:
181+
print(f"Warning: Container not found while stopping: {e}")
182+
except errors.APIError as e:
183+
print(f"Warning: API error while stopping container: {e}")
184+
185+
try:
128186
self._container.remove()
129-
except Exception as e:
130-
print(f"Warning: Error stopping container: {e}")
131-
finally:
132-
self._container = None
133-
self._mapped_port = None
187+
except errors.NotFound as e:
188+
print(f"Warning: Container not found while removing: {e}")
189+
except errors.APIError as e:
190+
print(f"Warning: API error while removing container: {e}")
191+
192+
self._container = None
193+
self._mapped_port = None
134194

135195
def get_host(self) -> str:
136196
if self._container is None:

eventsourcingdb/event/event.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def __init__(
2020
data_content_type: str,
2121
predecessor_hash: str,
2222
trace_parent: str | None = None,
23-
trace_state: str | None = None
23+
trace_state: str | None = None
2424
):
2525
super().__init__(
2626
source,
@@ -64,4 +64,4 @@ def to_json(self):
6464
json = super().to_json()
6565
json['data'] = self.data
6666

67-
return json
67+
return json

eventsourcingdb/event/event_context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ class EventContext:
2020
time: datetime
2121
data_content_type: str
2222
predecessor_hash: str
23-
trace_parent: str | None = None
24-
trace_state: str | None = None
23+
trace_parent: str | None = None
24+
trace_state: str | None = None
2525

2626
@staticmethod
2727
def parse(unknown_object: dict) -> "EventContext":

eventsourcingdb/handlers/bound.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ def to_json(self) -> dict[str, str]:
1616
return {
1717
'id': self.id,
1818
'type': self.type.value
19-
}
19+
}

eventsourcingdb/handlers/ping.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,4 @@ async def ping(client: AbstractBaseClient) -> None:
3939
):
4040
return
4141

42-
4342
raise ServerError(f"Received unexpected response: {response_body}")

0 commit comments

Comments
 (0)