pypetkitapi 1.7.5__py3-none-any.whl → 1.7.8__py3-none-any.whl

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.
pypetkitapi/client.py CHANGED
@@ -30,9 +30,11 @@ from pypetkitapi.const import (
30
30
  from pypetkitapi.containers import AccountData, Device, Pet, RegionInfo, SessionInfo
31
31
  from pypetkitapi.exceptions import (
32
32
  PetkitAuthenticationError,
33
+ PetkitAuthenticationUnregisteredEmailError,
33
34
  PetkitInvalidHTTPResponseCodeError,
34
35
  PetkitInvalidResponseFormat,
35
36
  PetkitRegionalServerNotFoundError,
37
+ PetkitSessionExpiredError,
36
38
  PetkitTimeoutError,
37
39
  PypetkitError,
38
40
  )
@@ -87,6 +89,7 @@ class PetKitClient:
87
89
  for region in response.get("list", []):
88
90
  server = RegionInfo(**region)
89
91
  if server.name.lower() == self.region or server.id.lower() == self.region:
92
+ self.region = server.id.lower()
90
93
  self.req.base_url = server.gateway
91
94
  _LOGGER.debug("Found matching server: %s", server)
92
95
  return
@@ -141,6 +144,8 @@ class PetKitClient:
141
144
  response = await self.req.request(
142
145
  method=HTTPMethod.POST,
143
146
  url=PetkitEndpoint.REFRESH_SESSION,
147
+ data=LOGIN_DATA,
148
+ headers=await self.get_session_id(),
144
149
  )
145
150
  session_data = response["session"]
146
151
  self._session = SessionInfo(**session_data)
@@ -148,6 +153,7 @@ class PetKitClient:
148
153
  async def validate_session(self) -> None:
149
154
  """Check if the session is still valid and refresh or re-login if necessary."""
150
155
  if self._session is None:
156
+ _LOGGER.debug("No token, logging in")
151
157
  await self.login()
152
158
  return
153
159
 
@@ -192,6 +198,8 @@ class PetKitClient:
192
198
 
193
199
  async def get_devices_data(self) -> None:
194
200
  """Get the devices data from the PetKit servers."""
201
+ await self.validate_session()
202
+
195
203
  start_time = datetime.now()
196
204
  if not self.account_data:
197
205
  await self._get_account_data()
@@ -199,7 +207,6 @@ class PetKitClient:
199
207
  main_tasks = []
200
208
  record_tasks = []
201
209
  device_list: list[Device] = []
202
- stats_tasks = []
203
210
 
204
211
  for account in self.account_data:
205
212
  _LOGGER.debug("List devices data for account: %s", account)
@@ -265,8 +272,6 @@ class PetKitClient:
265
272
  ],
266
273
  ) -> None:
267
274
  """Fetch the device data from the PetKit servers."""
268
- await self.validate_session()
269
-
270
275
  device_type = device.device_type.lower()
271
276
 
272
277
  _LOGGER.debug("Reading device type : %s (id=%s)", device_type, device.device_id)
@@ -396,6 +401,8 @@ class PetKitClient:
396
401
  setting: dict | None = None,
397
402
  ) -> bool:
398
403
  """Control the device using the PetKit API."""
404
+ await self.validate_session()
405
+
399
406
  device = self.petkit_entities.get(device_id)
400
407
  if not device:
401
408
  raise PypetkitError(f"Device with ID {device_id} not found.")
@@ -529,17 +536,24 @@ class PrepReq:
529
536
 
530
537
  # Check for errors in the response
531
538
  if ERR_KEY in response_json:
539
+ error_code = int(response_json[ERR_KEY].get("code", 0))
532
540
  error_msg = response_json[ERR_KEY].get("msg", "Unknown error")
533
- if any(
534
- endpoint in url
535
- for endpoint in [
536
- PetkitEndpoint.LOGIN,
537
- PetkitEndpoint.GET_LOGIN_CODE,
538
- PetkitEndpoint.REFRESH_SESSION,
539
- ]
540
- ):
541
- raise PetkitAuthenticationError(f"Login failed: {error_msg}")
542
- raise PypetkitError(f"Request failed: {error_msg}")
541
+
542
+ match error_code:
543
+ case 5:
544
+ raise PetkitSessionExpiredError(f"Session expired: {error_msg}")
545
+ case 122:
546
+ raise PetkitAuthenticationError(
547
+ f"Authentication failed: {error_msg}"
548
+ )
549
+ case 125:
550
+ raise PetkitAuthenticationUnregisteredEmailError(
551
+ f"Authentication failed: {error_msg}"
552
+ )
553
+ case _:
554
+ raise PypetkitError(
555
+ f"Request failed code : {error_code} details : {error_msg}"
556
+ )
543
557
 
544
558
  # Check for success in the response
545
559
  if RES_KEY in response_json:
pypetkitapi/command.py CHANGED
@@ -39,7 +39,9 @@ class FeederCommand(StrEnum):
39
39
  MANUAL_FEED_DUAL = "manual_feed_dual"
40
40
  CANCEL_MANUAL_FEED = "cancelRealtimeFeed"
41
41
  FOOD_REPLENISHED = "food_replenished"
42
- RESET_DESICCANT = "desiccantReset"
42
+ RESET_DESICCANT = "desiccant_reset"
43
+ REMOVE_DAILY_FEED = "remove_daily_feed"
44
+ RESTORE_DAILY_FEED = "restore_daily_feed"
43
45
 
44
46
 
45
47
  class LitterCommand(StrEnum):
@@ -160,6 +162,24 @@ ACTIONS_MAP = {
160
162
  },
161
163
  supported_device=ALL_DEVICES,
162
164
  ),
165
+ FeederCommand.REMOVE_DAILY_FEED: CmdData(
166
+ endpoint=PetkitEndpoint.REMOVE_DAILY_FEED,
167
+ params=lambda device, setting: {
168
+ "deviceId": device.id,
169
+ "day": datetime.datetime.now().strftime("%Y%m%d"),
170
+ **setting, # Need the id of the feed to remove
171
+ },
172
+ supported_device=DEVICES_FEEDER,
173
+ ),
174
+ FeederCommand.RESTORE_DAILY_FEED: CmdData(
175
+ endpoint=PetkitEndpoint.RESTORE_DAILY_FEED,
176
+ params=lambda device, setting: {
177
+ "deviceId": device.id,
178
+ "day": datetime.datetime.now().strftime("%Y%m%d"),
179
+ **setting, # Need the id of the feed to restore
180
+ },
181
+ supported_device=DEVICES_FEEDER,
182
+ ),
163
183
  FeederCommand.MANUAL_FEED: CmdData(
164
184
  endpoint=lambda device: get_endpoint_manual_feed(device),
165
185
  params=lambda device, setting: {
pypetkitapi/const.py CHANGED
@@ -131,4 +131,9 @@ class PetkitEndpoint(StrEnum):
131
131
  MANUAL_FEED_MINI = "feedermini/save_dailyfeed"
132
132
  MANUAL_FEED_FRESH_ELEMENT = "feeder/save_dailyfeed"
133
133
  MANUAL_FEED_DUAL = "saveDailyFeed"
134
- DAILY_FEED_AND_EAT = "dailyFeedAndEat"
134
+ DAILY_FEED_AND_EAT = "dailyFeedAndEat" # D3
135
+ FEED_STATISTIC = "feedStatistic" # D4
136
+ DAILY_FEED = "dailyFeeds" # D4S
137
+ REMOVE_DAILY_FEED = "removeDailyFeed"
138
+ RESTORE_DAILY_FEED = "restoreDailyFeed"
139
+ SAVE_FEED = "saveFeed" # For Feeding plan
pypetkitapi/exceptions.py CHANGED
@@ -11,10 +11,14 @@ class PetkitTimeoutError(PypetkitError):
11
11
  """Class for PyPetkit timeout exceptions."""
12
12
 
13
13
 
14
- class PetkitConnectionError(PypetkitError):
14
+ class PetkitSessionExpiredError(PypetkitError):
15
15
  """Class for PyPetkit connection exceptions."""
16
16
 
17
17
 
18
+ class PetkitAuthenticationUnregisteredEmailError(PypetkitError):
19
+ """Exception raised when the email is not registered with Petkit."""
20
+
21
+
18
22
  class PetkitRegionalServerNotFoundError(PypetkitError):
19
23
  """Exception raised when the specified region server is not found."""
20
24
 
@@ -5,7 +5,7 @@ from typing import Any, ClassVar
5
5
 
6
6
  from pydantic import BaseModel, Field
7
7
 
8
- from pypetkitapi.const import D3, DEVICE_DATA, DEVICE_RECORDS, PetkitEndpoint
8
+ from pypetkitapi.const import D3, D4, D4S, DEVICE_DATA, DEVICE_RECORDS, PetkitEndpoint
9
9
  from pypetkitapi.containers import CloudProduct, Device, FirmwareDetail, Wifi
10
10
 
11
11
 
@@ -271,6 +271,10 @@ class FeederRecord(BaseModel):
271
271
  """Get the endpoint URL for the given device type."""
272
272
  if device_type == D3:
273
273
  return PetkitEndpoint.DAILY_FEED_AND_EAT
274
+ if device_type == D4:
275
+ return PetkitEndpoint.FEED_STATISTIC
276
+ if device_type == D4S:
277
+ return PetkitEndpoint.DAILY_FEED
274
278
  return PetkitEndpoint.GET_DEVICE_RECORD
275
279
 
276
280
  @classmethod
@@ -283,7 +287,10 @@ class FeederRecord(BaseModel):
283
287
  """Generate query parameters including request_date."""
284
288
  if request_date is None:
285
289
  request_date = datetime.now().strftime("%Y%m%d")
286
- return {"days": int(request_date), "deviceId": device.device_id}
290
+
291
+ if device.device_type.lower() == D4:
292
+ return {"date": request_date, "type": 0, "deviceId": device.device_id}
293
+ return {"days": request_date, "deviceId": device.device_id}
287
294
 
288
295
 
289
296
  class Feeder(BaseModel):
@@ -296,7 +303,7 @@ class Feeder(BaseModel):
296
303
  cloud_product: CloudProduct | None = Field(None, alias="cloudProduct")
297
304
  created_at: str | None = Field(None, alias="createdAt")
298
305
  firmware: float
299
- firmware_details: list[FirmwareDetail] = Field(alias="firmwareDetails")
306
+ firmware_details: list[FirmwareDetail] | None = Field(None, alias="firmwareDetails")
300
307
  hardware: int
301
308
  id: int
302
309
  locale: str | None = None
pypetkitapi/medias.py CHANGED
@@ -194,5 +194,6 @@ class MediaDownloadDecode:
194
194
  except Exception as e: # noqa: BLE001
195
195
  logging.error("Error decrypting image from file %s: %s", file_path, e)
196
196
  return None
197
- Path(file_path).unlink()
197
+ if Path(file_path).exists():
198
+ Path(file_path).unlink()
198
199
  return decrypted_data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pypetkitapi
3
- Version: 1.7.5
3
+ Version: 1.7.8
4
4
  Summary: Python client for PetKit API
5
5
  Home-page: https://github.com/Jezza34000/pypetkit
6
6
  License: MIT
@@ -0,0 +1,15 @@
1
+ pypetkitapi/__init__.py,sha256=eVpyGMD3tkYtiHUkdKEeNSZhQlZ4woI2Y5oVoV7CwXM,61
2
+ pypetkitapi/client.py,sha256=VsISt7-njR0eFlloJenBjMXqeHjnrXXJr-7wamNA3fk,20473
3
+ pypetkitapi/command.py,sha256=q_9B-_z74BicMKNTR46PpELhmqW1cUV8q-95D6riJy4,7959
4
+ pypetkitapi/const.py,sha256=4FiPflYNMtQpycPDhtk9V2MZG9AjPC_FNNO36C_8NVI,3698
5
+ pypetkitapi/containers.py,sha256=nhp50QwyoQRveTnEgWL7JFEY3Tl5m5wp9EqZvklW87Y,4338
6
+ pypetkitapi/exceptions.py,sha256=fuTLT6Iw2_kA7eOyNJPf59vQkgfByhAnTThY4lC0Rt0,1283
7
+ pypetkitapi/feeder_container.py,sha256=RvhOHG-CKAHunDx28iQRpam3r3rvvBcdjw1dw8fTlS0,14370
8
+ pypetkitapi/litter_container.py,sha256=wsM-v7ibx17lsD-o17RGz2wvcHCUu1iZ2RqAh3CN1Qc,18254
9
+ pypetkitapi/medias.py,sha256=IuWkC7usw0Hbx173X8TGv24jOp4nqv6bIUosZBpXMGg,6945
10
+ pypetkitapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ pypetkitapi/water_fountain_container.py,sha256=3F4GP5pXJqq6kxLMSK__GgFMuZ-rz1VDIIhaVd19Kl8,6781
12
+ pypetkitapi-1.7.8.dist-info/LICENSE,sha256=4FWnKolNLc1e3w6cVlT61YxfPh0DQNeQLN1CepKKSBg,1067
13
+ pypetkitapi-1.7.8.dist-info/METADATA,sha256=ECwzMWXEWub8VuUZLucFRzM-mXHoBlkiu62xeRfeT00,4852
14
+ pypetkitapi-1.7.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
15
+ pypetkitapi-1.7.8.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- pypetkitapi/__init__.py,sha256=eVpyGMD3tkYtiHUkdKEeNSZhQlZ4woI2Y5oVoV7CwXM,61
2
- pypetkitapi/client.py,sha256=aBfwh6O-egpaX7vhW1I2l3flnXL_XrwGraH1uOzmH-w,19888
3
- pypetkitapi/command.py,sha256=gw3_J_oZHuuGLk66P8uRSqSrySjYa8ArpKaPHi2ybCw,7155
4
- pypetkitapi/const.py,sha256=ZpFWBgzb3nvy0Z4oyM550cCunlIceWgXqqtwtJc3mFo,3479
5
- pypetkitapi/containers.py,sha256=nhp50QwyoQRveTnEgWL7JFEY3Tl5m5wp9EqZvklW87Y,4338
6
- pypetkitapi/exceptions.py,sha256=NWmpsI2ewC4HaIeu_uFwCeuPIHIJxZBzjoCP7aNwvhs,1139
7
- pypetkitapi/feeder_container.py,sha256=y1A5WhObXCdbcWwtKgSPzTWokYchoUDlholAD-AMgGQ,14069
8
- pypetkitapi/litter_container.py,sha256=wsM-v7ibx17lsD-o17RGz2wvcHCUu1iZ2RqAh3CN1Qc,18254
9
- pypetkitapi/medias.py,sha256=8hrMdzFR9d0L0PQOVYdy-MReSF9GMp8Ft0IGGHtL1Ag,6904
10
- pypetkitapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- pypetkitapi/water_fountain_container.py,sha256=3F4GP5pXJqq6kxLMSK__GgFMuZ-rz1VDIIhaVd19Kl8,6781
12
- pypetkitapi-1.7.5.dist-info/LICENSE,sha256=4FWnKolNLc1e3w6cVlT61YxfPh0DQNeQLN1CepKKSBg,1067
13
- pypetkitapi-1.7.5.dist-info/METADATA,sha256=bvYlZOdBLw9_nu53rnNrPSXvgYdSvQMjqAVeSFMm7VE,4852
14
- pypetkitapi-1.7.5.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
15
- pypetkitapi-1.7.5.dist-info/RECORD,,