Skip to content

Commit

Permalink
Address upcoming deprecation of datetime.datetime.utcnow() (#719)
Browse files Browse the repository at this point in the history
* Address upcoming deprecation of `datetime.datetime.utcnow()`

* Support Python 3.10
  • Loading branch information
bachya authored Jan 13, 2024
1 parent 33b5722 commit 9aeb778
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 17 deletions.
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "poetry.core.masonry.api"
target-version = ["py39"]

[tool.coverage.report]
exclude_lines = ["raise NotImplementedError", "TYPE_CHECKING"]
exclude_lines = ["raise NotImplementedError", "TYPE_CHECKING", "ImportError"]
fail_under = 100
show_missing = true

Expand All @@ -30,7 +30,7 @@ follow_imports = "silent"
ignore_missing_imports = true
no_implicit_optional = true
platform = "linux"
python_version = "3.10"
python_version = "3.12"
show_error_codes = true
strict_equality = true
warn_incomplete_stub = true
Expand Down
5 changes: 3 additions & 2 deletions simplipy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
DEFAULT_CLIENT_ID,
DEFAULT_REDIRECT_URI,
)
from simplipy.util.dt import utcnow
from simplipy.websocket import WebsocketClient

API_URL_HOSTNAME = "api.simplisafe.com"
Expand Down Expand Up @@ -167,7 +168,7 @@ async def _async_handle_on_backoff(self, _: dict[str, Any]) -> None:
if err.status == 401 and self._token_last_refreshed:
# Calculate the window between now and the last time the token was
# refreshed:
window = (datetime.utcnow() - self._token_last_refreshed).total_seconds()
window = (utcnow() - self._token_last_refreshed).total_seconds()

# Since we might have multiple requests (each running their own retry
# sequence) land here, we only refresh the access token if it hasn't
Expand Down Expand Up @@ -253,7 +254,7 @@ def _save_token_data_from_response(self, token_data: dict[str, Any]) -> None:
Args:
token_data: An API response payload.
"""
self._token_last_refreshed = datetime.utcnow()
self._token_last_refreshed = utcnow()
self.access_token = token_data["access_token"]
if refresh_token := token_data.get("refresh_token"):
self.refresh_token = refresh_token
Expand Down
5 changes: 3 additions & 2 deletions simplipy/system/v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
SystemStates,
guard_from_missing_data,
)
from simplipy.util.dt import utcnow

if TYPE_CHECKING:
from simplipy.api import API
Expand Down Expand Up @@ -404,7 +405,7 @@ async def _async_set_state(self, value: SystemStates) -> None:
)

self._state = value
self._last_state_change_dt = datetime.utcnow()
self._last_state_change_dt = utcnow()

async def _async_set_updated_pins(self, pins: dict[str, Any]) -> None:
"""Post new PINs.
Expand Down Expand Up @@ -612,7 +613,7 @@ async def async_update(
if (
self.locks
and self._last_state_change_dt
and datetime.utcnow()
and utcnow()
<= self._last_state_change_dt + DEFAULT_LOCK_STATE_CHANGE_WINDOW
):
# The SimpliSafe cloud API currently has a bug wherein systems with locks
Expand Down
21 changes: 19 additions & 2 deletions simplipy/util/dt.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
"""Define datetime utilities."""
from datetime import datetime, timezone
from datetime import datetime

try:
from datetime import UTC
except ImportError:
# In place for support of Python 3.10
from datetime import timezone

UTC = timezone.utc


def utcnow() -> datetime:
"""Return the current UTC time.
Returns:
A ``datetime.datetime`` object.
"""
return datetime.now(tz=UTC)


def utc_from_timestamp(timestamp: float) -> datetime:
Expand All @@ -11,4 +28,4 @@ def utc_from_timestamp(timestamp: float) -> datetime:
Returns:
A parsed ``datetime.datetime`` object.
"""
return datetime.fromtimestamp(timestamp, tz=timezone.utc)
return datetime.fromtimestamp(timestamp, tz=UTC)
4 changes: 2 additions & 2 deletions simplipy/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
NotConnectedError,
)
from simplipy.util import CallbackType, execute_callback
from simplipy.util.dt import utc_from_timestamp
from simplipy.util.dt import utc_from_timestamp, utcnow

if TYPE_CHECKING:
from simplipy import API
Expand Down Expand Up @@ -405,7 +405,7 @@ async def async_disconnect(self) -> None:

async def async_listen(self) -> None:
"""Start listening to the websocket server."""
now = datetime.utcnow()
now = utcnow()
now_ts = round(now.timestamp() * 1000)
now_utc_iso = f"{now.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}Z"

Expand Down
3 changes: 2 additions & 1 deletion tests/system/test_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)
from simplipy.system import SystemStates
from simplipy.system.v3 import SystemV3, Volume
from simplipy.util.dt import utcnow
from tests.common import (
TEST_AUTHORIZATION_CODE,
TEST_CODE_VERIFIER,
Expand Down Expand Up @@ -1134,7 +1135,7 @@ async def test_no_state_change_on_failure(

# pylint: disable=protected-access
# Manually set the expiration datetime to force a refresh token flow:
simplisafe._token_last_refreshed = datetime.utcnow() - timedelta(seconds=30)
simplisafe._token_last_refreshed = utcnow() - timedelta(seconds=30)

systems = await simplisafe.async_get_systems()
system = systems[TEST_SYSTEM_ID]
Expand Down
9 changes: 5 additions & 4 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import asyncio
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Any
from unittest.mock import AsyncMock, Mock, patch

Expand All @@ -13,6 +13,7 @@

from simplipy import API
from simplipy.errors import InvalidCredentialsError, RequestError, SimplipyError
from simplipy.util.dt import utcnow

from .common import (
TEST_ACCESS_TOKEN,
Expand Down Expand Up @@ -87,7 +88,7 @@ async def test_401_refresh_token_failure(
)

# Manually set the expiration datetime to force a refresh token flow:
simplisafe._token_last_refreshed = datetime.utcnow() - timedelta(seconds=30)
simplisafe._token_last_refreshed = utcnow() - timedelta(seconds=30)

with pytest.raises(InvalidCredentialsError):
await simplisafe.async_get_systems()
Expand Down Expand Up @@ -152,7 +153,7 @@ async def test_401_refresh_token_success(
)

# Manually set the expiration datetime to force a refresh token flow:
simplisafe._token_last_refreshed = datetime.utcnow() - timedelta(seconds=30)
simplisafe._token_last_refreshed = utcnow() - timedelta(seconds=30)

# If this succeeds without throwing an exception, the retry is successful:
await simplisafe.async_get_systems()
Expand Down Expand Up @@ -397,7 +398,7 @@ async def test_refresh_token_callback(
)

# Manually set the expiration datetime to force a refresh token flow:
simplisafe._token_last_refreshed = datetime.utcnow() - timedelta(seconds=30)
simplisafe._token_last_refreshed = utcnow() - timedelta(seconds=30)

# We'll hang onto one callback:
simplisafe.add_refresh_token_callback(mock_callback_1)
Expand Down
5 changes: 3 additions & 2 deletions tests/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# pylint: disable=protected-access
from __future__ import annotations

from datetime import datetime, timedelta
from datetime import timedelta
from typing import Any, cast
from unittest.mock import Mock

Expand All @@ -14,6 +14,7 @@
from simplipy.device.lock import LockStates
from simplipy.errors import InvalidCredentialsError
from simplipy.system.v3 import SystemV3
from simplipy.util.dt import utcnow

from .common import (
TEST_AUTHORIZATION_CODE,
Expand Down Expand Up @@ -139,7 +140,7 @@ async def test_no_state_change_on_failure(
)

# Manually set the expiration datetime to force a refresh token flow:
simplisafe._token_last_refreshed = datetime.utcnow() - timedelta(seconds=30)
simplisafe._token_last_refreshed = utcnow() - timedelta(seconds=30)

systems = await simplisafe.async_get_systems()
system: SystemV3 = cast(SystemV3, systems[TEST_SYSTEM_ID])
Expand Down

0 comments on commit 9aeb778

Please sign in to comment.