Skip to content

Commit

Permalink
Cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
tdorssers committed Sep 11, 2021
1 parent f9a28cc commit f6514a6
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 47 deletions.
31 changes: 14 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ This module depends on Python [requests](https://pypi.org/project/requests/), [r
* It implements Tesla's new [OAuth 2](https://oauth.net/2/) Single Sign-On service.
* Acquired tokens are cached to disk (*cache.json*) for persistence.
* The cache stores tokens of each authorized identity (email).
* Authentication is only needed when a new token is requested.
* Authentication is only needed when a new token is requested (usually once).
* The token is automatically refreshed when expired without the need to reauthenticate.
* An email registered in another region (e.g. auth.tesla.cn) is also supported.
* Streaming API support using a [WebSocket](https://datatracker.ietf.org/doc/html/rfc6455).
* Pluggable cache and authenticator methods.

The constructor (not backwards compatible) takes these arguments:
TeslaPy 2.0.0+ no longer implements headless authentication. The constructor differs and takes these arguments:

| Argument | Description |
| --- | --- |
Expand All @@ -29,7 +29,7 @@ The constructor (not backwards compatible) takes these arguments:
| `cache_loader` | (optional) function that returns the cache dict |
| `cache_dumper` | (optional) function with one argument, the cache dict |

The SSO page opens in the system's default web browser. Upon successful authentication, a *Page not found* will be displayed and the redirected URL should start with `https://auth.tesla.com/void/callback`. The class will use `stdio` to get the full redirected URL from the web browser by default. Copy and paste the full URL to continue aquirering API tokens. You can use a pluggable authenticator method to automate this using [selenium](https://pypi.org/project/selenium/).
To authenticate, the SSO page opens in the system's default web browser. After successful authentication, a *Page not found* will be displayed and the URL should start with `https://auth.tesla.com/void/callback`, which is the redirected URL. The class will use `stdio` to get the full redirected URL from the web browser by default. You need to copy and paste the full URL from the web browser to the console to continue aquirering API tokens. You can use a pluggable authenticator method to automate this for example using [selenium](https://pypi.org/project/selenium/).

The convenience method `api()` uses named endpoints listed in *endpoints.json* to perform calls, so the module does not require changes if the API is updated. Any error message returned by the API is raised as an `HTTPError` exception. Additionally, the class implements the following methods:

Expand Down Expand Up @@ -103,21 +103,18 @@ with teslapy.Tesla('elon@tesla.com') as tesla:
print(vehicles[0].get_vehicle_data()['vehicle_state']['car_version'])
```

The `Tesla` class implements a pluggable authentication method. If you want to implement your own method to handle the SSO page and retrieve the redirected URL after authentication, you can pass a function that takes the authentication URL as an argument and returns the redirected URL, as an argument to the constructor. The `authenticator` argument is accessible as an attribute as well.
The `Tesla` class implements a pluggable authentication method. If you want to implement your own method to handle the SSO page and retrieve the redirected URL after authentication, you can pass a function as an argument to the constructor, that takes the authentication URL as an argument and returns the redirected URL. The `authenticator` argument is accessible as an attribute as well.

```python
from selenium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

def custom_auth(url):
browser = webdriver.Chrome()
browser.get(url)
wait = WebDriverWait(browser, 120)
wait.until(expected_conditions.url_contains('void/callback'))
url = browser.current_url
browser.close()
return url
with webdriver.Chrome() as browser:
browser.get(url)
WebDriverWait(browser, 300).until(EC.url_contains('void/callback'))
return browser.current_url

with teslapy.Tesla('elon@tesla.com', authenticator=custom_auth) as tesla:
tesla.fetch_token()
Expand Down Expand Up @@ -263,7 +260,7 @@ Example usage of [cli.py](https://github.com/tdorssers/TeslaPy/blob/master/cli.p

![](https://raw.githubusercontent.com/tdorssers/TeslaPy/master/media/menu.png)

[gui.py](https://github.com/tdorssers/TeslaPy/blob/master/gui.py) is a graphical interface using `tkinter`. API calls are performed asynchronously using threading. The GUI supports auto refreshing of the vehicle data and the GUI displays a composed vehicle image. Note that the vehicle will not go to sleep, if auto refresh is enabled. The application depends on [geopy](https://pypi.org/project/geopy/) to convert GPS coordinates to a human readable address and [pillow](https://pypi.org/project/Pillow/) to display the vehicle image.
[gui.py](https://github.com/tdorssers/TeslaPy/blob/master/gui.py) is a graphical interface using `tkinter`. API calls are performed asynchronously using threading. The GUI supports auto refreshing of the vehicle data and the GUI displays a composed vehicle image. Note that the vehicle will not go to sleep, if auto refresh is enabled. The application depends on [geopy](https://pypi.org/project/geopy/) to convert GPS coordinates to a human readable address. If Tcl/Tk GUI toolkit version of your Python installation is lower than 8.6 then [pillow](https://pypi.org/project/Pillow/) is required to display the vehicle image.

![](https://raw.githubusercontent.com/tdorssers/TeslaPy/master/media/gui.png)

Expand Down Expand Up @@ -551,10 +548,10 @@ TeslaPy is available on PyPI:

`python -m pip install teslapy`

Make sure you have [Python](https://www.python.org/) 2.7+ or 3.5+ installed on your system. Alternatively, clone the repository to your machine and run demo application [cli.py](https://github.com/tdorssers/TeslaPy/blob/master/cli.py), [menu.py](https://github.com/tdorssers/TeslaPy/blob/master/menu.py) or [gui.py](https://github.com/tdorssers/TeslaPy/blob/master/gui.py) to get started, after installing [requests_oauthlib](https://pypi.org/project/requests-oauthlib/), [geopy](https://pypi.org/project/geopy/) and [websocket-client](https://pypi.org/project/websocket-client/) using [PIP](https://pypi.org/project/pip/) as follows:
Make sure you have [Python](https://www.python.org/) 2.7+ or 3.5+ installed on your system. Alternatively, clone the repository to your machine and run demo application [cli.py](https://github.com/tdorssers/TeslaPy/blob/master/cli.py), [menu.py](https://github.com/tdorssers/TeslaPy/blob/master/menu.py) or [gui.py](https://github.com/tdorssers/TeslaPy/blob/master/gui.py) to get started, after installing [requests_oauthlib](https://pypi.org/project/requests-oauthlib/), [geopy](https://pypi.org/project/geopy/), [selenium](https://pypi.org/project/selenium/) and [websocket-client](https://pypi.org/project/websocket-client/) using [PIP](https://pypi.org/project/pip/) as follows:

`python -m pip install requests_oauthlib geopy websocket-client`
`python -m pip install requests_oauthlib geopy selenium websocket-client`

or on Ubuntu as follows:
and install [ChromeDriver](https://sites.google.com/chromium.org/driver/) to use Selenium or on Ubuntu as follows:

`sudo apt-get install python3-requests-oauthlib python3-geopy`
`sudo apt-get install python3-requests-oauthlib python3-geopy python3-selenium python3-websocket`
13 changes: 5 additions & 8 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import argparse
try:
from selenium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
except ImportError:
webdriver = None
Expand All @@ -17,13 +17,10 @@
raw_input = vars(__builtins__).get('raw_input', input) # Py2/3 compatibility

def custom_auth(url):
browser = webdriver.Chrome()
browser.get(url)
wait = WebDriverWait(browser, 120)
wait.until(expected_conditions.url_contains('void/callback'))
url = browser.current_url
browser.close()
return url
with webdriver.Chrome() as browser:
browser.get(url)
WebDriverWait(browser, 300).until(EC.url_contains('void/callback'))
return browser.current_url

def main():
parser = argparse.ArgumentParser(description='Tesla Owner API CLI')
Expand Down
22 changes: 10 additions & 12 deletions gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

# Author: Tim Dorssers

import io
import ssl
import time
import logging
Expand All @@ -13,7 +12,7 @@
from geopy.exc import *
try:
from selenium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
except ImportError:
webdriver = None
Expand Down Expand Up @@ -482,29 +481,28 @@ def add_cmd_args(self, endpoint):
def custom_auth(self, url):
""" Automated or manual authentication """
if webdriver:
browser = webdriver.Chrome()
browser.get(url)
wait = WebDriverWait(browser, 120)
wait.until(expected_conditions.url_contains('void/callback'))
url = browser.current_url
browser.close()
return url
with webdriver.Chrome() as browser:
browser.get(url)
wait = WebDriverWait(browser, 300)
wait.until(EC.url_contains('void/callback'))
return browser.current_url
# Manual authentication
webbrowser.open(url)
# Ask user for callback URL in new dialog
result = ['not_set']
def show_dialog():
""" Inner function to show dialog """
result[0] = askstring('Login', 'Callback URL:')
result[0] = askstring('Login', 'URL after authentication:')
self.after_idle(show_dialog) # Start from main thread
while result[0] == 'not_set':
time.sleep(0.1) # Block login thread until passcode is entered
return result[0]

def login(self):
""" Display login dialog and start new thread to get vehicle list """
result = askstring('Login', 'Email:',
initialvalue=getattr(self, 'email'))
result = askstring('Login', 'Use browser to login.\nPage Not Found '
'will be shown at success.\n\nEmail:',
initialvalue=self.email)
if result:
self.email = result
self.status.text('Logging in...')
Expand Down
13 changes: 5 additions & 8 deletions menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from geopy.exc import GeocoderTimedOut
try:
from selenium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
except ImportError:
webdriver = None
Expand Down Expand Up @@ -237,13 +237,10 @@ def menu(vehicle):
print('Not available')

def custom_auth(url):
browser = webdriver.Chrome()
browser.get(url)
wait = WebDriverWait(browser, 120)
wait.until(expected_conditions.url_contains('void/callback'))
url = browser.current_url
browser.close()
return url
with webdriver.Chrome() as browser:
browser.get(url)
WebDriverWait(browser, 300).until(EC.url_contains('void/callback'))
return browser.current_url

def main():
parser = argparse.ArgumentParser(description='Tesla Owner API Menu')
Expand Down
5 changes: 3 additions & 2 deletions teslapy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
__version__ = '2.0.0'

import os
import re
import ast
import json
import time
Expand Down Expand Up @@ -172,8 +171,10 @@ def _fetch_jwt(self, oauth):
@staticmethod
def _authenticate(url):
""" Default authenticator method """
logger.debug('Opening %s with default browser', url)
print('Use browser to login. Page Not Found will be shown at success.')
webbrowser.open(url)
return input('Enter redirect URL: ')
return input('Enter URL after authentication: ')

def _cache_load(self):
""" Default cache loader method """
Expand Down

0 comments on commit f6514a6

Please sign in to comment.