Skip to content
Draft
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ testing_scripts/api_test_editor.py
testing_scripts/yamltest.yaml
testing_scripts/yamltest copy.yaml
creds.json
docstest/*
docstest/*
/.claude
.mcp.json
.vesync_auth
2 changes: 1 addition & 1 deletion ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ ignore = [
"EXE002", # Use of exec - IGNORE
"DTZ005", # Use of datetime.now() without tz - IGNORE
# "Q000", # Quotes
# "ERA001", # Commented out code
"ERA001", # Commented out code


]
Expand Down
53 changes: 35 additions & 18 deletions src/pyvesync/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ class VeSyncAuth:

Handles login, token management, and persistent storage of authentication
credentials for VeSync API access.

Args:
manager: VeSync manager instance for API calls
username: VeSync account username (email)
password: VeSync account password
country_code: Country code in ISO 3166 Alpha-2 format

Note:
Either username/password or token/account_id must be provided.
If token_file_path is provided, credentials will be saved/loaded
automatically. When loading credentials, the current working directory
and home directory are checked for the token file if no path is provided.
When saving credentials, if no path is provided, it will save to the current
working directory.
"""

__slots__ = (
Expand All @@ -57,19 +71,7 @@ def __init__(
password: str,
country_code: str = DEFAULT_REGION,
) -> None:
"""Initialize VeSync Authentication Manager.

Args:
manager: VeSync manager instance for API calls
username: VeSync account username (email)
password: VeSync account password
country_code: Country code in ISO 3166 Alpha-2 format

Note:
Either username/password or token/account_id must be provided.
If token_file_path is provided, credentials will be saved/loaded
automatically.
"""
"""Initialize VeSync Authentication Manager."""
self.manager = manager
self._username = username
self._password = password
Expand All @@ -85,6 +87,11 @@ def _country_code_to_region(self) -> str:
return 'US'
return 'EU'

@property
def credentials_saved(self) -> bool:
"""Return whether credentials have been saved to file."""
return self._token_file_path is not None and self._token_file_path.exists()

@property
def token(self) -> str:
"""Return VeSync API token."""
Expand Down Expand Up @@ -150,17 +157,25 @@ async def reauthenticate(self) -> bool:
True if re-authentication successful, False otherwise
"""
self.clear_credentials()
return await self.login()
success = await self.login()
if success:
logger.debug('Re-authentication successful for user: %s', self._username)
if self.credentials_saved:
await self.save_credentials_to_file(self._token_file_path)
else:
logger.debug('Re-authentication failed for user: %s', self._username)
return False
return success

async def load_credentials_from_file(
self, file_path: str | Path | None = None
) -> bool:
"""Load credentials from token file if path is set.

If no path is provided, it will try to load from the users home directory and
then the current working directory.
If no path is provided, it will try to load from the current working directory and
then the user's home directory.
"""
locations = [Path.home() / '.vesync_auth', Path.cwd() / '.vesync_auth']
locations = [Path.cwd() / '.vesync_auth', Path.home() / '.vesync_auth']
file_path_object: Path | None = None
if file_path is None:
for location in locations:
Expand All @@ -174,6 +189,7 @@ async def load_credentials_from_file(
if not file_path_object or not file_path_object.exists():
logger.debug('Credentials file not found: %s', file_path_object)
return False
self._token_file_path = file_path_object
try:
data = await asyncio.to_thread(
Path(file_path_object).read_text, encoding='utf-8'
Expand Down Expand Up @@ -228,14 +244,15 @@ async def save_credentials_to_file(self, file_path: str | Path | None = None) ->
file_path_object = self._token_file_path
else:
logger.debug('No token file path set, saving to default location')
file_path_object = Path.home() / '.vesync_auth'
file_path_object = Path.cwd() / '.vesync_auth'
if not self.is_authenticated:
logger.debug('No credentials to save, not authenticated')
return
credentials = {
'token': self._token,
'account_id': self._account_id,
'country_code': self._country_code,
'current_region': self.current_region,
}
try:
data = orjson.dumps(credentials).decode('utf-8')
Expand Down
Loading
Loading