Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/ipsdk/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ async def fetch_devices():
import time
import urllib.parse

from datetime import datetime
from datetime import timezone
from typing import Any

import httpx
Expand Down Expand Up @@ -517,7 +519,9 @@ def _send_request(

try:
logging.info(f"{method.value} {path}")
started_at = datetime.now(timezone.utc)
res = self.client.send(request)
finished_at = datetime.now(timezone.utc)
res.raise_for_status()

except httpx.RequestError as exc:
Expand All @@ -532,7 +536,11 @@ def _send_request(
logging.exception(exc)
raise

return Response(res)
return Response(
res,
started_at=started_at.isoformat(),
finished_at=finished_at.isoformat(),
)

@logging.trace
def get(self, path: str, params: dict[str, Any | None] | None = None) -> Response:
Expand Down Expand Up @@ -741,7 +749,9 @@ async def _send_request(

try:
logging.info(f"{method.value} {path}")
started_at = datetime.now(timezone.utc)
res = await self.client.send(request)
finished_at = datetime.now(timezone.utc)
res.raise_for_status()

except httpx.RequestError as exc:
Expand All @@ -756,7 +766,11 @@ async def _send_request(
logging.exception(exc)
raise

return Response(res)
return Response(
res,
started_at=started_at.isoformat(),
finished_at=finished_at.isoformat(),
)

@logging.trace
async def get(
Expand Down
52 changes: 50 additions & 2 deletions src/ipsdk/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
to ensure consistent behavior across all supported Python versions.
"""

from datetime import datetime
from http import HTTPStatus
from typing import TYPE_CHECKING
from typing import Any
Expand Down Expand Up @@ -132,15 +133,23 @@ class Response:
ValueError: If the httpx_response is None or invalid
"""

__slots__ = ("_response",)
__slots__ = ("_finished_at", "_response", "_started_at")

@logging.trace
def __init__(self, httpx_response: httpx.Response) -> None:
def __init__(
self,
httpx_response: httpx.Response,
*,
started_at: str,
finished_at: str,
) -> None:
if httpx_response is None:
msg = "httpx_response cannot be None"
raise ValueError(msg)

self._response = httpx_response
self._started_at = started_at
self._finished_at = finished_at

@property
def status_code(self) -> int:
Expand Down Expand Up @@ -202,6 +211,45 @@ def request(self) -> httpx.Request:
"""
return self._response.request

@property
def started_at(self) -> str:
"""
Get the UTC ISO 8601 timestamp when the request was sent.

Returns:
str: ISO 8601 UTC timestamp of when the request was sent.
"""
return self._started_at

@property
def finished_at(self) -> str:
"""
Get the UTC ISO 8601 timestamp when the response was received.

Returns:
str: ISO 8601 UTC timestamp of when the response was received.
"""
return self._finished_at

@property
def elapsed_ms(self) -> int:
"""
Get the request duration in milliseconds.

Computed from the difference between finished_at and started_at.
Truncated to whole milliseconds (not rounded).

Returns:
int: Request duration in milliseconds.
"""
return int(
(
datetime.fromisoformat(self._finished_at)
- datetime.fromisoformat(self._started_at)
).total_seconds()
* 1000
)

@logging.trace
def json(self) -> dict[str, Any]:
"""
Expand Down
Loading