mlowijs / tesla_api

Lightweight Python API client for the Tesla API.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using a token to create a client..?

tyddynonn opened this issue · comments

commented

Great module! Just what I'm looking for.

Works fine when I specify username & password, but when I tried to use a pre-existing token to create a client..


user=None
pw=None
tk='token string here'
client=TeslaApiClient(email=user, password=pw, token=tk)

Throws 'TypeError: init() got an unexpected keyword argument 'token'

The source here suggests that this should work, and I've installed the latest version (pip install tesla-api). Python 3.5

Any ideas?

Hi! I didn't release a new version yet to PyPI, let me do that right now.

I just released version 2.0.0 to PyPI, which should be available soon. Let me know if you still have problems after updating!

commented

Thanks for the speedy response - all downloaded OK.

Just need to upgrade to Python 3.8 now to get asyncio.run working ;-)

commented

So Python 3.8 installed & working, now I get a different error when passing in a token string as a parameter to TeslaApiClient - I'm passing in the OAuth token string.

File "/usr/local/lib/python3.8/site-packages/tesla_api/init.py", line 67, in authenticate
expiry_time = timedelta(seconds=self.token['expires_in'])
TypeError: string indices must be integers

It looks like from line 65 of init.py that the object saves self.token as the complete response from authentication rather than just the token value - which would account for this error.

Is this the intention? It would mean re-authenticating for every session, or finding a way to create/save a complete token dict to pass in on the next invocation.

Ideally I'd like to save the OAuth token outside my code (env variable?) and just pass that in when I instantiate TeslaApiClient. Does this make sense?

Hi there! I think we made a little oversight here. The token parameter should be set to a Token dict, looking like:

{
  "access_token": "AN_ACCESS_TOKEN",
  "refresh_token": "A_REFRESH_TOKEN",
  "expires_in": 123567,
  "created_at": "2020-01-01T00:00:00"
}

You could probably make it work for once by setting the token parameter to:

{
  "access_token": "",
  "refresh_token": "A_VALID_REFRESH_TOKEN_HERE",
  "expires_in": 0,
  "created_at": "2020-01-01T00:00:00"
}

and then making an API call to refresh the token and after that grabbing the token property from the TeslaApiClient.

Or use your username and password one time and then grab the token property from the TeslaApiClient!

commented

Thanks again, I can work with this. Just need the token to be publicly accessible from the client - at the moment it always returns null. As you can probably tell, I'm new to Python - more a C# & Javascript person..

Hey, I'm more of a C# person myself, so I understand you 😉. The token property should be publically accessible from the client:

async def main():
  client = TeslaApiClient("your@email.com", "your@password.com")
  await client.list_vehicles()
  print(client.token)

Works on my machine with the 2.0.1 package.

commented

I plan to add some features around this, and some documentation to show how to use this properly.

But, as demonstrated above, you need to use the client first (e.g. client.list_vehicles()) before the token is retrieved and saved to that attribute.
After that you should be able to access client.token and get a dict object.

To save/restore with the token, I've been using json.dump() to store the token, and then loading it back with json.load() to be passed into a file.

A fuller example would therefore be something like:

async def main():
  email = password = token = None
  try:
    token = json.load(open("token_file"))
  except OSError:
    email = input()
    password = input()
  client = TeslaApiClient(email, password, token)
  await client.list_vehicles()

  # If successful, save token.
  json.dump(client.token, open("token_file", "w"))
commented

@Dreamsorcerer We should probably make it so that you can pass in a refresh token and let the client request a fresh token for us, what do you think??

commented

@tyddynonn The client already does that; it checks whether your access token has expired and will request a new one using the refresh token. I'm proposing to let you pass just the refresh token into the TeslaApiClient, instead of the full dictionary. Or perhaps we should support both a refresh token or the full dict.

I'm not sure I see the use case..
If you want to restore from a token, it's more efficient to use the full token which appears to be valid for 45 days. In what case would you only want to save the refresh token?

I'm currently waiting for my token to expire so I can test out the refresh stuff. I find it rather odd that you would be able to fetch a new API token, after it had expired (at which point you are no longer authenticated to use the service).

commented

I'm easy with just using the full dict. The main reason is to avoid embedding username/pw details anywhere...

@tyddynonn The next release will have some changes to this. It handles the JSON, so you should now just pass in the string format. There is also a callback you should pass into the constructor to save the token. So, that previous example will now look more like this:

def save_token(token):
    open("token_file", "w").write(token)

async def main():
  email = password = token = None
  try:
    token = open("token_file").read()
  except OSError:
    email = input()
    password = input()
  client = TeslaApiClient(email, password, token, on_new_token=save_token)

The callback means you don't need to worry about when the token is available. But, more importantly, it will allow you to automatically save any new tokens (from a refresh), so you don't end up with an invalid/expired token.

README has been updated with the current best practices on master (will need a new release for pip).