pynintendoparental 0.7.0__tar.gz → 0.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/PKG-INFO +1 -1
  2. pynintendoparental-0.7.2/pynintendoparental/_version.py +1 -0
  3. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/api.py +6 -23
  4. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/authenticator/__init__.py +42 -25
  5. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/device.py +8 -1
  6. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental.egg-info/PKG-INFO +1 -1
  7. pynintendoparental-0.7.0/pynintendoparental/_version.py +0 -1
  8. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/LICENSE +0 -0
  9. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/README.md +0 -0
  10. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/__init__.py +0 -0
  11. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/application.py +0 -0
  12. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/authenticator/const.py +0 -0
  13. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/const.py +0 -0
  14. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/enum.py +0 -0
  15. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/exceptions.py +0 -0
  16. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/player.py +0 -0
  17. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/py.typed +0 -0
  18. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental/utils.py +0 -0
  19. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental.egg-info/SOURCES.txt +0 -0
  20. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental.egg-info/dependency_links.txt +0 -0
  21. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental.egg-info/requires.txt +0 -0
  22. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pynintendoparental.egg-info/top_level.txt +0 -0
  23. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/pyproject.toml +0 -0
  24. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/setup.cfg +0 -0
  25. {pynintendoparental-0.7.0 → pynintendoparental-0.7.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pynintendoparental
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: A Python module to interact with Nintendo Parental Controls
5
5
  Home-page: http://github.com/pantherale0/pynintendoparental
6
6
  Author: pantherale0
@@ -0,0 +1 @@
1
+ __version__ = "0.7.2"
@@ -1,7 +1,5 @@
1
1
  """API handler."""
2
2
 
3
- from datetime import datetime, timedelta
4
-
5
3
  import aiohttp
6
4
 
7
5
  from .authenticator import Authenticator
@@ -25,20 +23,11 @@ def _check_http_success(status: int) -> bool:
25
23
  class Api:
26
24
  """Nintendo Parental Controls API."""
27
25
 
28
- def __init__(self, auth, tz, lang, session: aiohttp.ClientSession=None):
26
+ def __init__(self, auth, tz, lang):
29
27
  """INIT"""
30
28
  self._auth: Authenticator = auth
31
29
  self._tz = tz
32
30
  self._language = lang
33
- if session is None:
34
- session = aiohttp.ClientSession()
35
- self._session_created_internally = True
36
- self._session = session
37
-
38
- @property
39
- def _auth_token(self) -> str:
40
- """Returns the auth token."""
41
- return f"Bearer {self._auth.access_token}"
42
31
 
43
32
  @property
44
33
  def account_id(self):
@@ -59,15 +48,9 @@ class Api:
59
48
  "X-Moon-TimeZone": self._tz,
60
49
  "X-Moon-Os-Language": self._language,
61
50
  "X-Moon-App-Language": self._language,
62
- "Authorization": self._auth_token
51
+ "Authorization": self._auth.access_token
63
52
  }
64
53
 
65
- async def async_close(self):
66
- """Closes the underlying aiohttp.ClientSession if it was created by this instance."""
67
- if hasattr(self, '_session_created_internally') and self._session_created_internally and self._session and not self._session.closed:
68
- await self._session.close()
69
- self._session = None # Optional: clear the session attribute
70
-
71
54
  async def send_request(self, endpoint: str, body: object=None, **kwargs):
72
55
  """Sends a request to a given endpoint."""
73
56
  _LOGGER.debug("Sending request to %s", endpoint)
@@ -76,7 +59,7 @@ class Api:
76
59
  if e_point is None:
77
60
  raise ValueError("Endpoint does not exist")
78
61
  # refresh the token if it has expired.
79
- if self._auth.expires < (datetime.now()+timedelta(seconds=30)):
62
+ if self._auth.access_token_expired:
80
63
  _LOGGER.debug("Access token expired, requesting refresh.")
81
64
  await self._auth.perform_refresh()
82
65
  # format the URL using the kwargs
@@ -89,11 +72,11 @@ class Api:
89
72
  "json": "",
90
73
  "headers": ""
91
74
  }
92
- self._session.headers.update(self._headers)
93
- async with self._session.request(
75
+ async with self._auth.client_session.request(
94
76
  method=e_point.get("method"),
95
77
  url=url,
96
- json=body
78
+ json=body,
79
+ headers=self._headers
97
80
  ) as response:
98
81
  _LOGGER.debug("%s request to %s status code %s",
99
82
  e_point.get("method"),
@@ -2,7 +2,6 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import logging
5
- import re
6
5
  import base64
7
6
  import hashlib
8
7
  import random
@@ -60,11 +59,16 @@ def _rand():
60
59
  class Authenticator:
61
60
  """Authentication functions."""
62
61
 
63
- def __init__(self, session_token = None, auth_code_verifier = None):
62
+ def __init__(
63
+ self,
64
+ session_token = None,
65
+ auth_code_verifier = None,
66
+ client_session: aiohttp.ClientSession = None
67
+ ):
64
68
  """Basic init."""
65
69
  _LOGGER.debug(">> Init authenticator.")
66
- self.expires: datetime = None
67
- self.access_token: str = None
70
+ self._at_expiry: datetime = None
71
+ self._access_token: str = None
68
72
  self.available_scopes: dict = None
69
73
  self.account_id: str = None
70
74
  self.account: dict = None
@@ -73,12 +77,25 @@ class Authenticator:
73
77
  self._id_token: str = None
74
78
  self._session_token: str = session_token
75
79
  self.login_url: str = None
80
+ if client_session is None:
81
+ client_session = aiohttp.ClientSession()
82
+ self.client_session: aiohttp.ClientSession = client_session
76
83
 
77
84
  @property
78
85
  def get_session_token(self) -> str:
79
86
  """Return the session token."""
80
87
  return self._session_token
81
88
 
89
+ @property
90
+ def access_token(self) -> str:
91
+ """Return the formatted access token."""
92
+ return f"Bearer {self._access_token}"
93
+
94
+ @property
95
+ def access_token_expired(self) -> bool:
96
+ """Check if the access token has expired."""
97
+ return self._at_expiry < (datetime.now()+timedelta(minutes=1))
98
+
82
99
  async def _request_handler(self, method, url, json=None, data=None, headers: dict=None):
83
100
  """Send a HTTP request"""
84
101
  if headers is None:
@@ -89,28 +106,25 @@ class Authenticator:
89
106
  "json": "",
90
107
  "headers": ""
91
108
  }
92
- async with aiohttp.ClientSession() as session:
93
- session.headers.add("Accept", "application/json")
94
- for header in headers.keys():
95
- session.headers.add(header, headers[header])
96
- async with session.request(
97
- method=method,
98
- url=url,
99
- json=json,
100
- data=data
101
- ) as resp:
102
- response["status"] = resp.status
103
- response["text"] = await resp.text()
104
- response["json"] = await resp.json()
105
- response["headers"] = resp.headers
109
+ async with self.client_session.request(
110
+ method=method,
111
+ url=url,
112
+ json=json,
113
+ data=data,
114
+ headers=headers
115
+ ) as resp:
116
+ response["status"] = resp.status
117
+ response["text"] = await resp.text()
118
+ response["json"] = await resp.json()
119
+ response["headers"] = resp.headers
106
120
  return response
107
121
 
108
122
  def _read_tokens(self, tokens: dict):
109
123
  """Reads tokens into self."""
110
124
  self.available_scopes = tokens.get("scope")
111
- self.expires = datetime.now() + timedelta(seconds=tokens.get("expires_in"))
125
+ self._at_expiry = datetime.now() + timedelta(seconds=tokens.get("expires_in"))
112
126
  self._id_token = tokens.get("id_token")
113
- self.access_token = tokens.get("access_token")
127
+ self._access_token = tokens.get("access_token")
114
128
 
115
129
  async def perform_login(self, session_token_code):
116
130
  """Retrieves initial tokens."""
@@ -159,7 +173,7 @@ class Authenticator:
159
173
  method="GET",
160
174
  url=MY_ACCOUNT_ENDPOINT,
161
175
  headers={
162
- "Authorization": f"Bearer {self.access_token}"
176
+ "Authorization": self.access_token
163
177
  }
164
178
  )
165
179
  if account["status"] != 200:
@@ -168,11 +182,13 @@ class Authenticator:
168
182
  self.account = account["json"]
169
183
 
170
184
  @classmethod
171
- def generate_login(cls) -> 'Authenticator':
185
+ def generate_login(
186
+ cls,
187
+ client_session: aiohttp.ClientSession | None = None) -> 'Authenticator':
172
188
  """Starts configuration of the authenticator."""
173
189
  verifier = _rand()
174
190
 
175
- auth = cls(auth_code_verifier=verifier)
191
+ auth = cls(auth_code_verifier=verifier, client_session=client_session)
176
192
 
177
193
  query = {
178
194
  "client_id": CLIENT_ID,
@@ -193,10 +209,11 @@ class Authenticator:
193
209
  async def complete_login(cls,
194
210
  auth: Authenticator | None,
195
211
  response_token: str,
196
- is_session_token: bool=False) -> Authenticator:
212
+ is_session_token: bool=False,
213
+ client_session: aiohttp.ClientSession | None = None) -> Authenticator:
197
214
  """Creates and logs into Nintendo APIs"""
198
215
  if is_session_token:
199
- auth = cls(session_token=response_token)
216
+ auth = cls(session_token=response_token, client_session=client_session)
200
217
  await auth.perform_refresh()
201
218
  else:
202
219
  response_token = _parse_response_token(response_token)
@@ -76,6 +76,13 @@ class Device:
76
76
  if callback not in self._callbacks:
77
77
  self._callbacks.append(callback)
78
78
 
79
+ def remove_device_callback(self, callback):
80
+ """Remove a given device callback."""
81
+ if not callable(callback):
82
+ raise ValueError("Object must be callable.")
83
+ if callback in self._callbacks:
84
+ self._callbacks.remove(callback)
85
+
79
86
  async def set_new_pin(self, pin: str):
80
87
  """Updates the pin for the device."""
81
88
  _LOGGER.debug(">> Device.set_new_pin(pin=REDACTED)")
@@ -273,7 +280,7 @@ class Device:
273
280
  time_remaining_by_bedtime = 0.0
274
281
  if bedtime_dt > now: # Bedtime is in the future today
275
282
  time_remaining_by_bedtime = (bedtime_dt - now).total_seconds() / 60
276
- time_remaining_by_bedtime = max(0.0, time_remaining_by_bedtime)
283
+ time_remaining_by_bedtime = max(0.0, time_remaining_by_bedtime)
277
284
  # else: Bedtime has passed for today or is now, so time_remaining_by_bedtime remains 0.0
278
285
 
279
286
  effective_remaining_time = min(effective_remaining_time, time_remaining_by_bedtime)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pynintendoparental
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: A Python module to interact with Nintendo Parental Controls
5
5
  Home-page: http://github.com/pantherale0/pynintendoparental
6
6
  Author: pantherale0
@@ -1 +0,0 @@
1
- __version__ = "0.7.0"