Skip to content

Commit

Permalink
So many fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Bre77 committed Jun 1, 2024
1 parent df2d9a6 commit 0d67bae
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 121 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ async def main():
access_token="<access_token>",
session=session,
region="na",
raise_for_status=True,
)
try:
Expand Down Expand Up @@ -54,7 +53,6 @@ async def main():
refresh_token=auth["refresh_token"],
expires=auth["expires"],
region="na",
raise_for_status=True,
)
try:
data = await api.vehicle.list()
Expand Down Expand Up @@ -91,7 +89,6 @@ async def main():
api = Teslemetry(
access_token="<access_token>",
session=session,
raise_for_status=True,
)
try:
Expand Down Expand Up @@ -119,7 +116,6 @@ async def main():
api = Tessie(
access_token="<access_token>",
session=session,
raise_for_status=True,
)
try:
Expand Down
8 changes: 7 additions & 1 deletion tesla_fleet_api/energy.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from typing import Any
from __future__ import annotations
from typing import Any, TYPE_CHECKING
from .const import Method, EnergyOperationMode, EnergyExportMode
from .energyspecific import EnergySpecific

if TYPE_CHECKING:
from .teslafleetapi import TeslaFleetApi


class Energy:
"""Class describing the Tesla Fleet API partner endpoints"""

parent: TeslaFleetApi

def __init__(self, parent):
self._request = parent._request

Expand Down
11 changes: 9 additions & 2 deletions tesla_fleet_api/energyspecific.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
from typing import Any
from __future__ import annotations
from typing import Any, TYPE_CHECKING
from .const import EnergyExportMode, EnergyOperationMode

if TYPE_CHECKING:
from .energy import Energy


class EnergySpecific:
"""Class describing the Tesla Fleet API partner endpoints"""

_parent: Energy
energy_site_id: int

def __init__(
self,
parent,
parent: Energy,
energy_site_id: int,
):
self._parent = parent
Expand Down
8 changes: 5 additions & 3 deletions tesla_fleet_api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class TeslaFleetError(BaseException):

message: str = "An unknown error has occurred."
status: int | None = None
data: dict | None = None
data: dict | str | None = None
key: str | None = None

def __init__(self, data: dict | None = None, status: int | None = None):
def __init__(self, data: dict | str | None = None, status: int | None = None):
LOGGER.debug(self.message)
self.data = data
self.status = status or self.status
Expand All @@ -20,6 +21,7 @@ class ResponseError(TeslaFleetError):
"""The response from the server was not JSON."""

message = "The response from the server was not JSON."
data: str | None = None


class InvalidCommand(TeslaFleetError):
Expand Down Expand Up @@ -375,5 +377,5 @@ async def raise_for_status(resp: aiohttp.ClientResponse) -> None:
elif resp.status == 540:
raise DeviceUnexpectedResponse(data)
elif data is None:
raise ResponseError(status=resp.status)
raise ResponseError(status=resp.status, data=await resp.text())
resp.raise_for_status()
35 changes: 14 additions & 21 deletions tesla_fleet_api/teslafleetapi.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import aiohttp
"""Tesla Fleet API for Python."""

from json import dumps
from .exceptions import raise_for_status, InvalidRegion, LibraryError, InvalidToken
from typing import Any
import aiohttp

from .exceptions import raise_for_status, InvalidRegion, LibraryError, ResponseError
from .const import SERVERS, Method, LOGGER, VERSION
from .charging import Charging
from .energy import Energy
Expand All @@ -15,18 +17,18 @@
class TeslaFleetApi:
"""Class describing the Tesla Fleet API."""

access_token: str | None = None
region: str | None = None
server: str | None = None
session: aiohttp.ClientSession
headers: dict[str, str]
raise_for_status: bool

def __init__(
self,
session: aiohttp.ClientSession,
access_token: str | None = None,
region: str | None = None,
server: str | None = None,
raise_for_status: bool = True,
charging_scope: bool = True,
energy_scope: bool = True,
partner_scope: bool = True,
Expand All @@ -37,7 +39,6 @@ def __init__(

self.session = session
self.access_token = access_token
self.raise_for_status = raise_for_status

if server is not None:
self.server = server
Expand Down Expand Up @@ -82,7 +83,7 @@ async def _request(
path: str,
params: dict[str, Any] | None = None,
json: dict[str, Any] | None = None,
) -> dict[str, Any] | str:
) -> dict[str, Any]:
"""Send a request to the Tesla Fleet API."""

if not self.server:
Expand Down Expand Up @@ -113,22 +114,14 @@ async def _request(
params=params,
) as resp:
LOGGER.debug("Response Status: %s", resp.status)
if self.raise_for_status and not resp.ok:
if not resp.ok:
await raise_for_status(resp)
elif resp.status == 401 and resp.content_type != "application/json":
# Manufacture a response since Tesla doesn't provide a body for token expiration.
return {
"response": None,
"error": InvalidToken.key,
"error_message": "The OAuth token has expired.",
}
if resp.content_type == "application/json":
data = await resp.json()
LOGGER.debug("Response JSON: %s", data)
return data

data = await resp.text()
LOGGER.debug("Response Text: %s", data)
if not resp.content_type.lower().startswith("application/json"):
LOGGER.debug("Response type is: %s", resp.content_type)
raise ResponseError(status=resp.status, data=await resp.text())

data = await resp.json()
LOGGER.debug("Response JSON: %s", data)
return data

async def status(self) -> str:
Expand Down
8 changes: 3 additions & 5 deletions tesla_fleet_api/teslafleetoauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ def __init__(
expires: int = 0,
region: str | None = None,
server: str | None = None,
raise_for_status: bool = True,
):
self.client_id = client_id
self.access_token = access_token
Expand All @@ -33,7 +32,6 @@ def __init__(
access_token="",
region=region,
server=server,
raise_for_status=raise_for_status,
)

def get_login_url(
Expand Down Expand Up @@ -96,8 +94,8 @@ async def _request(
method: Method,
path: str,
params: dict[str, Any] | None = None,
data: dict[str, Any] | None = None,
) -> str | dict[str, Any]:
json: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Send a request to the Tesla Fleet API."""
await self.check_access_token()
return await super()._request(method, path, params, data)
return await super()._request(method, path, params, json)
9 changes: 4 additions & 5 deletions tesla_fleet_api/teslemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ def __init__(
self,
session: aiohttp.ClientSession,
access_token: str,
raise_for_status: bool = True,
):
"""Initialize the Teslemetry API."""
super().__init__(
session,
access_token,
server="https://api.teslemetry.com",
raise_for_status=raise_for_status,
partner_scope=False,
user_scope=False,
)
Expand Down Expand Up @@ -52,18 +50,19 @@ async def metadata(self, update_region=True) -> dict[str, Any]:
LOGGER.debug("Using server %s", self.server)
return resp

# TODO: type this properly, it probably should return something
async def find_server(self) -> None:
async def find_server(self) -> str:
"""Find the server URL for the Tesla Fleet API."""
await self.metadata(True)
assert self.region
return self.region

async def _request(
self,
method: Method,
path: str,
params: dict[str, Any] | None = None,
json: dict[str, Any] | None = None,
) -> str | dict[str, Any]:
) -> dict[str, Any]:
"""Send a request to the Teslemetry API."""
async with rate_limit:
return await super()._request(method, path, params, json)
2 changes: 0 additions & 2 deletions tesla_fleet_api/tessie.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ def __init__(
self,
session: aiohttp.ClientSession,
access_token: str,
raise_for_status: bool = True,
):
"""Initialize the Tessie API."""
super().__init__(
session,
access_token,
server="https://api.tessie.com",
raise_for_status=raise_for_status,
partner_scope=False,
user_scope=False,
)
Expand Down
64 changes: 17 additions & 47 deletions tesla_fleet_api/vehicle.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Any, List
from __future__ import annotations
from typing import Any, List, TYPE_CHECKING
from .const import (
Method,
Trunk,
Expand All @@ -13,17 +14,22 @@
)
from .vehiclespecific import VehicleSpecific

if TYPE_CHECKING:
from .teslafleetapi import TeslaFleetApi


class Vehicle:
"""Class describing the Tesla Fleet API vehicle endpoints and commands."""

def __init__(self, parent):
_parent: TeslaFleetApi

def __init__(self, parent: TeslaFleetApi):
self._parent = parent
self._request = parent._request

def specific(self, vehicle_tag: str | int) -> VehicleSpecific:
def specific(self, vin: str) -> VehicleSpecific:
"""Creates a class for each vehicle."""
return VehicleSpecific(self, vehicle_tag)
return VehicleSpecific(self, vin)

def pre2021(self, vin: str) -> bool:
"""Checks if a vehicle is a pre-2021 model S or X."""
Expand Down Expand Up @@ -716,34 +722,14 @@ async def signed_command(
{"routable_message": routable_message},
)

async def subscriptions(
self, device_token: str, device_type: str
) -> dict[str, Any]:
"""Returns the list of vehicles for which this mobile device currently subscribes to push notifications."""
return await self._request(
Method.GET,
"api/1/subscriptions",
query={"device_token": device_token, "device_type": device_type},
)

async def subscriptions_set(
self, device_token: str, device_type: str
) -> dict[str, Any]:
"""Allows a mobile device to specify which vehicles to receive push notifications from."""
return await self._request(
Method.POST,
"api/1/subscriptions",
query={"device_token": device_token, "device_type": device_type},
)

async def vehicle(self, vehicle_tag: str | int) -> dict[str, Any]:
"""Returns information about a vehicle."""
return await self._request(Method.GET, f"api/1/vehicles/{vehicle_tag}")

async def vehicle_data(
self,
vehicle_tag: str | int,
endpoints: List[VehicleDataEndpoint] | List[str] | None = None,
endpoints: List[VehicleDataEndpoint | str] | None = None,
) -> dict[str, Any]:
"""Makes a live call to the vehicle. This may return cached data if the vehicle is offline. For vehicles running firmware versions 2023.38+, location_data is required to fetch vehicle location. This will result in a location sharing icon to show on the vehicle UI."""
endpoint_payload = ";".join(endpoints) if endpoints else None
Expand All @@ -753,37 +739,21 @@ async def vehicle_data(
{"endpoints": endpoint_payload},
)

async def vehicle_subscriptions(
self, device_token: str, device_type: DeviceType | str
) -> dict[str, Any]:
"""Returns the list of vehicles for which this mobile device currently subscribes to push notifications."""
return await self._request(
Method.GET,
"api/1/vehicle_subscriptions",
{"device_token": device_token, "device_type": device_type},
)

async def vehicle_subscriptions_set(
self, device_token: str, device_type: DeviceType | str
) -> dict[str, Any]:
"""Allows a mobile device to specify which vehicles to receive push notifications from."""
return await self._request(
Method.POST,
"api/1/vehicle_subscriptions",
params={"device_token": device_token, "device_type": device_type},
)

async def wake_up(self, vehicle_tag: str | int) -> dict[str, Any]:
"""Wakes the vehicle from sleep, which is a state to minimize idle energy consumption."""
return await self._request(Method.POST, f"api/1/vehicles/{vehicle_tag}/wake_up")

async def warranty_details(self, vin: str | None) -> dict[str, Any]:
"""Returns warranty details."""
return await self._request(Method.GET, "api/1/dx/warranty/details", {vin: vin})
return await self._request(
Method.GET, "api/1/dx/warranty/details", {"vin": vin}
)

async def fleet_status(self, vins: List[str]) -> dict[str, Any]:
"""Checks whether vehicles can accept Tesla commands protocol for the partner's public key"""
return await self._request(Method.GET, "api/1/vehicles/fleet_status", json=vins)
return await self._request(
Method.GET, "api/1/vehicles/fleet_status", json={"vins": vins}
)

async def fleet_telemetry_config_create(
self, config: dict[str, Any]
Expand Down
Loading

0 comments on commit 0d67bae

Please sign in to comment.