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
2 changes: 1 addition & 1 deletion shellhub/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Increment versions here according to SemVer
__version__ = "0.2.4"
__version__ = "0.3.0"

from .models.device import ShellHubDevice, ShellHubDeviceInfo
from .models.base import ShellHub
Expand Down
35 changes: 29 additions & 6 deletions shellhub/models/device.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from typing import Dict
from typing import List
from typing import Optional
Expand Down Expand Up @@ -40,12 +41,12 @@ class ShellHubDevice:
info: ShellHubDeviceInfo
public_key: str
tenant_id: str
last_seen: str
last_seen: datetime
online: bool
namespace: str
status: str
status_updated_at: str
created_at: str
status_updated_at: datetime
created_at: datetime
remote_addr: str
tags: List[str]
acceptable: bool
Expand All @@ -59,16 +60,38 @@ def __init__(self, api_object: shellhub.models.base.ShellHub, device_json): # t
self.info = ShellHubDeviceInfo(device_json["info"])
self.public_key = device_json["public_key"]
self.tenant_id = device_json["tenant_id"]
self.last_seen = device_json["last_seen"]
self.last_seen = self._safe_isoformat_to_datetime(device_json["last_seen"])
self.online = device_json["online"]
self.namespace = device_json["namespace"]
self.status = device_json["status"]
self.status_updated_at = device_json["status_updated_at"]
self.created_at = device_json["created_at"]
self.status_updated_at = self._safe_isoformat_to_datetime(device_json["status_updated_at"])
self.created_at = self._safe_isoformat_to_datetime(device_json["created_at"])
self.remote_addr = device_json["remote_addr"]
self.tags = device_json["tags"]
self.acceptable = device_json["acceptable"]

@staticmethod
def _safe_isoformat_to_datetime(date_string: str) -> datetime:
# Replace "Z" with "+00:00" to indicate UTC in a format compatible with Python 3.7-3.10.
if date_string.endswith("Z"):
date_string = date_string[:-1] + "+00:00"
try:
# Direct conversion using fromisoformat
return datetime.fromisoformat(date_string)
except ValueError:
try:
# For Python versions that do not handle offset-aware datetimes well in fromisoformat
# This part is more of a catch-all to ensure even non-standard or unexpected formats
# might be parsed, but primarily, the first attempt should work for ISO 8601 formats.
# Note: strptime might not be necessary if fromisoformat works after the 'Z' to '+00:00' replacement,
# but it's here as an example if further customization is needed.
return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S%z")
except ValueError as e:
# If the first attempt fails due to the format not being exactly ISO 8601 after 'Z' replacement,
# this additional attempt can catch other variations. This might not be strictly necessary,
# depending on your input formats.
raise ShellHubApiError(f"Invalid date string: {date_string} (Couldn't convert to datetime)") from e

def delete(self) -> bool:
"""
Delete the device from the API
Expand Down
42 changes: 39 additions & 3 deletions tests/test_devices.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from datetime import datetime
from datetime import timezone

import pytest

from shellhub.exceptions import ShellHubApiError
Expand Down Expand Up @@ -104,6 +107,7 @@ def test_get_device(self, shellhub, requests_mock):
}
requests_mock.get(f"{MOCKED_DOMAIN_URL}/api/devices/1", json=mock_response)
device = shellhub.get_device("1")
origin_time = datetime(1970, 1, 1, 0, 0, tzinfo=timezone.utc)

assert device.uid == "1"
assert device.name == "default"
Expand All @@ -118,16 +122,48 @@ def test_get_device(self, shellhub, requests_mock):
== "-----BEGIN RSA PUBLIC KEY-----\nxxx\nxxx\nxxx\nxxx\nxxx\nxxx\n-----END RSA PUBLIC KEY-----\n"
)
assert device.tenant_id == "1"
assert device.last_seen == "1970-01-01T00:00:00Z"
assert device.last_seen == origin_time
assert device.online
assert device.namespace == "dev"
assert device.status == "accepted"
assert device.status_updated_at == "1970-01-01T00:00:00Z"
assert device.created_at == "1970-01-01T00:00:00Z"
assert device.status_updated_at == origin_time
assert device.created_at == origin_time
assert device.remote_addr == "0.0.0.0"
assert device.tags == []
assert not device.acceptable

def test_get_incorrect_datetime_format(self, shellhub, requests_mock):
mock_response = {
"uid": "1",
"name": "default",
"identity": {"mac": "06:04:ju:le:s7:08"},
"info": {
"id": "ubuntu",
"pretty_name": "Ubuntu 20.04.2 LTS",
"version": "v0.14.1",
"arch": "amd64",
"platform": "docker",
},
"public_key": "-----BEGIN RSA PUBLIC KEY-----\nxxx\nxxx\nxxx\n"
"xxx\nxxx\nxxx\n-----END RSA PUBLIC KEY-----\n",
"tenant_id": "1",
"last_seen": "-1",
"online": True,
"namespace": "dev",
"status": "accepted",
"status_updated_at": "-1",
"created_at": "-1",
"remote_addr": "0.0.0.0",
"position": {"latitude": 0, "longitude": 0},
"tags": [],
"public_url": False,
"public_url_address": "",
"acceptable": False,
}
requests_mock.get(f"{MOCKED_DOMAIN_URL}/api/devices/1", json=mock_response)
with pytest.raises(ShellHubApiError):
shellhub.get_device("1")


class TestDeleteDevice:
def test_delete_device(self, shellhub_device, requests_mock):
Expand Down