tesla-fleet-api 0.6.2__tar.gz → 0.7.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. {tesla_fleet_api-0.6.2/tesla_fleet_api.egg-info → tesla_fleet_api-0.7.1}/PKG-INFO +1 -1
  2. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/setup.py +1 -1
  3. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/__init__.py +1 -0
  4. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/const.py +1 -1
  5. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/exceptions.py +6 -1
  6. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/teslafleetapi.py +16 -3
  7. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/teslafleetoauth.py +25 -9
  8. tesla_fleet_api-0.7.1/tesla_fleet_api/teslafleetopensource.py +61 -0
  9. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1/tesla_fleet_api.egg-info}/PKG-INFO +1 -1
  10. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api.egg-info/SOURCES.txt +1 -0
  11. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/LICENSE +0 -0
  12. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/README.md +0 -0
  13. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/setup.cfg +0 -0
  14. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/charging.py +0 -0
  15. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/energy.py +0 -0
  16. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/energyspecific.py +0 -0
  17. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/partner.py +0 -0
  18. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/teslemetry.py +0 -0
  19. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/tessie.py +0 -0
  20. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/user.py +0 -0
  21. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/vehicle.py +0 -0
  22. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api/vehiclespecific.py +0 -0
  23. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api.egg-info/dependency_links.txt +0 -0
  24. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api.egg-info/requires.txt +0 -0
  25. {tesla_fleet_api-0.6.2 → tesla_fleet_api-0.7.1}/tesla_fleet_api.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tesla_fleet_api
3
- Version: 0.6.2
3
+ Version: 0.7.1
4
4
  Summary: Tesla Fleet API library for Python
5
5
  Home-page: https://github.com/Teslemetry/tesla_fleet_api
6
6
  Author: Brett Adams
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="tesla_fleet_api",
8
- version="0.6.2",
8
+ version="0.7.1",
9
9
  author="Brett Adams",
10
10
  author_email="hello@teslemetry.com",
11
11
  description="Tesla Fleet API library for Python",
@@ -1,5 +1,6 @@
1
1
  from .teslafleetapi import TeslaFleetApi
2
2
  from .teslafleetoauth import TeslaFleetOAuth
3
+ from .teslafleetopensource import TeslaFleetOpenSource
3
4
  from .teslemetry import Teslemetry
4
5
  from .tessie import Tessie
5
6
  from .charging import Charging
@@ -3,7 +3,7 @@
3
3
  from enum import Enum
4
4
  import logging
5
5
 
6
- VERSION = "0.6.2"
6
+ VERSION = "0.7.1"
7
7
  LOGGER = logging.getLogger(__package__)
8
8
  SERVERS = {
9
9
  "na": "https://fleet-api.prd.na.vn.cloud.tesla.com",
@@ -363,7 +363,12 @@ async def raise_for_status(resp: aiohttp.ClientResponse) -> None:
363
363
  elif resp.status == 424:
364
364
  raise InvalidResponse(data)
365
365
  elif resp.status == 429:
366
- raise RateLimited(data)
366
+ raise RateLimited(
367
+ {
368
+ "reset": resp.headers.get("RateLimit-Reset"),
369
+ "after": resp.headers.get("Retry-After"),
370
+ }
371
+ )
367
372
  elif resp.status == 451:
368
373
  raise ResourceUnavailableForLegalReasons(data)
369
374
  elif resp.status == 499:
@@ -1,7 +1,7 @@
1
1
  """Tesla Fleet API for Python."""
2
2
 
3
3
  from json import dumps
4
- from typing import Any
4
+ from typing import Any, Awaitable
5
5
  import aiohttp
6
6
 
7
7
  from .exceptions import raise_for_status, InvalidRegion, LibraryError, ResponseError
@@ -22,6 +22,7 @@ class TeslaFleetApi:
22
22
  server: str | None = None
23
23
  session: aiohttp.ClientSession
24
24
  headers: dict[str, str]
25
+ refresh_hook: Awaitable | None
25
26
 
26
27
  def __init__(
27
28
  self,
@@ -34,11 +35,13 @@ class TeslaFleetApi:
34
35
  partner_scope: bool = True,
35
36
  user_scope: bool = True,
36
37
  vehicle_scope: bool = True,
38
+ refresh_hook: Awaitable | None = None,
37
39
  ):
38
40
  """Initialize the Tesla Fleet API."""
39
41
 
40
42
  self.session = session
41
43
  self.access_token = access_token
44
+ self.refresh_hook = refresh_hook
42
45
 
43
46
  if server is not None:
44
47
  self.server = server
@@ -46,8 +49,6 @@ class TeslaFleetApi:
46
49
  if region not in SERVERS:
47
50
  raise ValueError(f"Region must be one of {', '.join(SERVERS.keys())}")
48
51
  self.server = SERVERS.get(region)
49
- else:
50
- raise ValueError("Either server or region must be provided.")
51
52
 
52
53
  LOGGER.debug("Using server %s", self.server)
53
54
 
@@ -92,6 +93,10 @@ class TeslaFleetApi:
92
93
  if method == Method.GET and json is not None:
93
94
  raise ValueError("GET requests cannot have a body.")
94
95
 
96
+ # Call a pre-request hook if provided
97
+ if self.refresh_hook is not None:
98
+ await self.refresh_hook()
99
+
95
100
  LOGGER.debug("Sending request to %s", path)
96
101
 
97
102
  # Remove None values from params and json
@@ -116,8 +121,16 @@ class TeslaFleetApi:
116
121
  LOGGER.debug("Response Status: %s", resp.status)
117
122
  if "x-txid" in resp.headers:
118
123
  LOGGER.debug("Response TXID: %s", resp.headers["x-txid"])
124
+ if "RateLimit-Reset" in resp.headers:
125
+ LOGGER.debug(
126
+ "Rate limit reset: %s", resp.headers.get("RateLimit-Reset")
127
+ )
128
+ if "Retry-After" in resp.headers:
129
+ LOGGER.debug("Retry after: %s", resp.headers.get("Retry-After"))
130
+
119
131
  if not resp.ok:
120
132
  await raise_for_status(resp)
133
+
121
134
  if not resp.content_type.lower().startswith("application/json"):
122
135
  LOGGER.debug("Response type is: %s", resp.content_type)
123
136
  raise ResponseError(status=resp.status, data=await resp.text())
@@ -11,11 +11,16 @@ class TeslaFleetOAuth(TeslaFleetApi):
11
11
  """Tesla Fleet OAuth API."""
12
12
 
13
13
  expires: int
14
+ refresh_token: str
15
+ redirect_uri: str | None
16
+ _client_secret: str | None
14
17
 
15
18
  def __init__(
16
19
  self,
17
20
  session: aiohttp.ClientSession,
18
21
  client_id: str,
22
+ client_secret: str | None = None,
23
+ redirect_uri: str | None = None,
19
24
  access_token: str | None = None,
20
25
  refresh_token: str | None = None,
21
26
  expires: int = 0,
@@ -23,6 +28,8 @@ class TeslaFleetOAuth(TeslaFleetApi):
23
28
  server: str | None = None,
24
29
  ):
25
30
  self.client_id = client_id
31
+ self._client_secret = client_secret
32
+ self.redirect_uri = redirect_uri
26
33
  self.access_token = access_token
27
34
  self.refresh_token = refresh_token
28
35
  self.expires = expires
@@ -34,25 +41,34 @@ class TeslaFleetOAuth(TeslaFleetApi):
34
41
  server=server,
35
42
  )
36
43
 
37
- def get_login_url(
38
- self, redirect_uri: str, scopes: list[Scope], state: str = "login"
39
- ) -> str:
44
+ def get_login_url(self, scopes: list[Scope], state: str = "login") -> str:
40
45
  """Get the login URL."""
41
- return f"https://auth.tesla.com/oauth2/v3/authorize?response_type=code&client_id={self.client_id}&redirect_uri={redirect_uri}&scope={' '.join(scopes)}&state={state}"
46
+ if self.redirect_uri is None:
47
+ raise ValueError("Redirect URI is missing")
48
+ return f"https://auth.tesla.com/oauth2/v3/authorize?response_type=code&prompt=login&client_id={self.client_id}&redirect_uri={self.redirect_uri}&scope={' '.join(scopes)}&state={state}"
42
49
 
43
- async def get_refresh_token(
44
- self, client_secret: str, code: str, redirect_uri: str
45
- ) -> None:
50
+ async def get_refresh_token(self, code: str) -> None:
46
51
  """Get the refresh token."""
52
+
53
+ if self._client_secret is None:
54
+ raise ValueError("Client secret is missing")
55
+
56
+ if self.redirect_uri is None:
57
+ raise ValueError("Redirect URI is missing")
58
+
59
+ if self.server is None:
60
+ self.region = code.split("_")[0].lower()
61
+ self.server = SERVERS.get(self.region)
62
+
47
63
  async with self.session.post(
48
64
  "https://auth.tesla.com/oauth2/v3/token",
49
65
  data={
50
66
  "grant_type": "authorization_code",
51
67
  "client_id": self.client_id,
52
- "client_secret": client_secret,
68
+ "client_secret": self._client_secret,
53
69
  "code": code,
54
70
  "audience": self.server,
55
- "redirect_uri": redirect_uri,
71
+ "redirect_uri": self.redirect_uri,
56
72
  },
57
73
  ) as resp:
58
74
  if resp.ok:
@@ -0,0 +1,61 @@
1
+ import aiohttp
2
+ import time
3
+ import secrets
4
+ import hashlib
5
+ import base64
6
+
7
+ from .teslafleetoauth import TeslaFleetOAuth
8
+ from .const import Scope, SERVERS
9
+
10
+
11
+ class TeslaFleetOpenSource(TeslaFleetOAuth):
12
+ """Tesla Fleet Open Source OAuth API."""
13
+
14
+ code_verifier: str
15
+ code_challenge: str
16
+
17
+ def __init__(
18
+ self,
19
+ session: aiohttp.ClientSession,
20
+ client_id: str,
21
+ redirect_uri: str,
22
+ ):
23
+ self.code_verifier = secrets.token_urlsafe(32)
24
+
25
+ # Hash the code_verifier using SHA-256
26
+ hashed_verifier = hashlib.sha256(self.code_verifier.encode()).digest()
27
+ # Encode the hash using URL-safe Base64 encoding, without padding
28
+ self.code_challenge = (
29
+ base64.urlsafe_b64encode(hashed_verifier).decode().replace("=", "")
30
+ )
31
+
32
+ super().__init__(session, client_id, redirect_uri=redirect_uri)
33
+
34
+ def get_login_url(self, scopes: list[Scope], state: str = "login") -> str:
35
+ """Get the login URL without a client secret."""
36
+
37
+ return (
38
+ super().get_login_url(scopes, state)
39
+ + f"&code_challenge={self.code_challenge}"
40
+ )
41
+
42
+ async def get_refresh_token(self, code: str) -> None:
43
+ """Get the refresh token."""
44
+ async with self.session.post(
45
+ "https://auth.tesla.com/oauth2/v3/token",
46
+ data={
47
+ "grant_type": "authorization_code",
48
+ "client_id": self.client_id,
49
+ "code": code,
50
+ "audience": self.server,
51
+ "redirect_uri": self.redirect_uri,
52
+ "code_verifier": self.code_verifier,
53
+ },
54
+ ) as resp:
55
+ if resp.ok:
56
+ data = await resp.json()
57
+ self.refresh_token = data["refresh_token"]
58
+ self.access_token = data["access_token"]
59
+ self.expires = int(time.time()) + data["expires_in"]
60
+ region = code.split("_")[0].lower()
61
+ self.server = SERVERS.get(region)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tesla_fleet_api
3
- Version: 0.6.2
3
+ Version: 0.7.1
4
4
  Summary: Tesla Fleet API library for Python
5
5
  Home-page: https://github.com/Teslemetry/tesla_fleet_api
6
6
  Author: Brett Adams
@@ -10,6 +10,7 @@ tesla_fleet_api/exceptions.py
10
10
  tesla_fleet_api/partner.py
11
11
  tesla_fleet_api/teslafleetapi.py
12
12
  tesla_fleet_api/teslafleetoauth.py
13
+ tesla_fleet_api/teslafleetopensource.py
13
14
  tesla_fleet_api/teslemetry.py
14
15
  tesla_fleet_api/tessie.py
15
16
  tesla_fleet_api/user.py
File without changes