pypetkitapi 1.0.0__tar.gz → 1.2.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pypetkitapi
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: Python client for PetKit API
5
5
  Home-page: https://github.com/Jezza34000/pypetkit
6
6
  License: MIT
@@ -49,9 +49,6 @@ class PetKitClient:
49
49
  _servers_list: list[RegionInfo] = []
50
50
  account_data: list[AccountData] = []
51
51
  petkit_entities: dict[int, Feeder | Litter | WaterFountain | Pet] = {}
52
- petkit_entities_records: dict[
53
- int, FeederRecord | LitterRecord | WaterFountainRecord
54
- ] = {}
55
52
 
56
53
  def __init__(
57
54
  self,
@@ -67,6 +64,7 @@ class PetKitClient:
67
64
  self.region = region.lower()
68
65
  self.timezone = timezone
69
66
  self._session = None
67
+ self.petkit_entities = {}
70
68
  self.aiohttp_session = session or aiohttp.ClientSession()
71
69
  self.req = PrepReq(
72
70
  base_url=PetkitDomain.PASSPORT_PETKIT, session=self.aiohttp_session
@@ -84,7 +82,6 @@ class PetKitClient:
84
82
  method=HTTPMethod.GET,
85
83
  url=PetkitEndpoint.REGION_SERVERS,
86
84
  )
87
- _LOGGER.debug("API server list: %s", response)
88
85
 
89
86
  # Filter the servers by region
90
87
  for region in response.get("list", []):
@@ -169,7 +166,6 @@ class PetKitClient:
169
166
  elif half_max_age < token_age <= max_age:
170
167
  _LOGGER.debug("Token still OK, but refreshing session")
171
168
  await self.refresh_session()
172
- return
173
169
 
174
170
  async def get_session_id(self) -> dict:
175
171
  """Return the session ID."""
@@ -200,47 +196,54 @@ class PetKitClient:
200
196
  if not self.account_data:
201
197
  await self._get_account_data()
202
198
 
203
- tasks = []
199
+ main_tasks = []
200
+ record_tasks = []
204
201
  device_list: list[Device] = []
205
202
 
206
203
  for account in self.account_data:
207
- _LOGGER.debug("Fetching devices data for account: %s", account)
204
+ _LOGGER.debug("List devices data for account: %s", account)
208
205
  if account.device_list:
209
206
  device_list.extend(account.device_list)
210
207
 
211
208
  _LOGGER.debug("Fetch %s devices for this account", len(device_list))
212
209
 
213
210
  for device in device_list:
214
- _LOGGER.debug("Fetching devices data: %s", device)
215
211
  device_type = device.device_type.lower()
216
- device_id = device.device_id
217
212
  if device_type in DEVICES_FEEDER:
218
- # Add tasks for feeders
219
- tasks.append(self._fetch_device_data(account, device_id, Feeder))
220
- tasks.append(
221
- self._fetch_device_data(account, device_id, FeederRecord)
213
+ main_tasks.append(
214
+ self._fetch_device_data(account, device.device_id, Feeder)
215
+ )
216
+ record_tasks.append(
217
+ self._fetch_device_data(account, device.device_id, FeederRecord)
222
218
  )
223
219
  elif device_type in DEVICES_LITTER_BOX:
224
- # Add tasks for litter boxes
225
- tasks.append(self._fetch_device_data(account, device_id, Litter))
226
- tasks.append(
227
- self._fetch_device_data(account, device_id, LitterRecord)
220
+ main_tasks.append(
221
+ self._fetch_device_data(account, device.device_id, Litter)
222
+ )
223
+ record_tasks.append(
224
+ self._fetch_device_data(account, device.device_id, LitterRecord)
228
225
  )
229
226
  elif device_type in DEVICES_WATER_FOUNTAIN:
230
- # Add tasks for water fountains
231
- tasks.append(
232
- self._fetch_device_data(account, device_id, WaterFountain)
227
+ main_tasks.append(
228
+ self._fetch_device_data(
229
+ account, device.device_id, WaterFountain
230
+ )
233
231
  )
234
- tasks.append(
235
- self._fetch_device_data(account, device_id, WaterFountainRecord)
232
+ record_tasks.append(
233
+ self._fetch_device_data(
234
+ account, device.device_id, WaterFountainRecord
235
+ )
236
236
  )
237
- else:
238
- _LOGGER.warning("Unknown device type: %s", device_type)
239
- await asyncio.gather(*tasks)
237
+
238
+ # Execute main device tasks first
239
+ await asyncio.gather(*main_tasks)
240
+
241
+ # Then execute record tasks
242
+ await asyncio.gather(*record_tasks)
240
243
 
241
244
  end_time = datetime.now()
242
245
  total_time = end_time - start_time
243
- _LOGGER.info("OK Petkit data fetched in : %s", total_time)
246
+ _LOGGER.info("Petkit data fetched successfully in: %s", total_time)
244
247
 
245
248
  async def _fetch_device_data(
246
249
  self,
@@ -273,6 +276,8 @@ class PetKitClient:
273
276
  return
274
277
  device_type = device.device_type.lower()
275
278
 
279
+ _LOGGER.debug("Reading device type : %s (id=%s)", device_type, device_id)
280
+
276
281
  endpoint = data_class.get_endpoint(device_type)
277
282
  query_param = data_class.query_param(account, device.device_id)
278
283
 
@@ -292,18 +297,12 @@ class PetKitClient:
292
297
  _LOGGER.error("Unexpected response type: %s", type(response))
293
298
  return
294
299
 
295
- if isinstance(device_data, list):
296
- for item in device_data:
297
- item.device_type = device_type
298
- else:
299
- device_data.device_type = device_type
300
-
301
- _LOGGER.debug("Reading device type : %s (id=%s)", device_type, device_id)
302
-
303
300
  if data_class.data_type == DEVICE_DATA:
304
301
  self.petkit_entities[device_id] = device_data
302
+ _LOGGER.debug("Device data fetched OK for %s", device_type)
305
303
  elif data_class.data_type == DEVICE_RECORDS:
306
- self.petkit_entities_records[device_id] = device_data
304
+ self.petkit_entities[device_id].device_records = device_data
305
+ _LOGGER.debug("Device records fetched OK for %s", device_type)
307
306
  else:
308
307
  _LOGGER.error("Unknown data type: %s", data_class.data_type)
309
308
 
@@ -367,7 +366,7 @@ class PetKitClient:
367
366
  )
368
367
  if res in (SUCCESS_KEY, RES_KEY):
369
368
  # TODO : Manage to get the response from manual feeding
370
- _LOGGER.info("Command executed successfully")
369
+ _LOGGER.debug("Command executed successfully")
371
370
  else:
372
371
  _LOGGER.error("Command execution failed")
373
372
 
@@ -416,14 +415,7 @@ class PrepReq:
416
415
  """Make a request to the PetKit API."""
417
416
  _url = "/".join(s.strip("/") for s in [self.base_url, url])
418
417
  _headers = {**self.base_headers, **(headers or {})}
419
- _LOGGER.debug(
420
- "Request: %s %s Params: %s Data: %s Headers: %s",
421
- method,
422
- _url,
423
- params,
424
- data,
425
- _headers,
426
- )
418
+ _LOGGER.debug("Request: %s %s", method, _url)
427
419
  try:
428
420
  async with self.session.request(
429
421
  method,
@@ -11,6 +11,7 @@ SUCCESS_KEY = "success"
11
11
 
12
12
  DEVICE_RECORDS = "deviceRecords"
13
13
  DEVICE_DATA = "deviceData"
14
+ PET_DATA = "petData"
14
15
 
15
16
  # PetKit Models
16
17
  FEEDER = "feeder"
@@ -167,48 +167,6 @@ class ManualFeed(BaseModel):
167
167
  time: int | None = None
168
168
 
169
169
 
170
- class Feeder(BaseModel):
171
- """Dataclass for feeder data."""
172
-
173
- data_type: ClassVar[str] = DEVICE_DATA
174
-
175
- auto_upgrade: int | None = Field(None, alias="autoUpgrade")
176
- bt_mac: str | None = Field(None, alias="btMac")
177
- cloud_product: CloudProduct | None = Field(None, alias="cloudProduct")
178
- created_at: str | None = Field(None, alias="createdAt")
179
- firmware: float
180
- firmware_details: list[FirmwareDetail] = Field(alias="firmwareDetails")
181
- hardware: int
182
- id: int
183
- locale: str | None = None
184
- mac: str | None = None
185
- model_code: int | None = Field(None, alias="modelCode")
186
- multi_feed_item: MultiFeedItem | None = Field(None, alias="multiFeedItem")
187
- name: str | None = None
188
- secret: str | None = None
189
- service_status: int | None = Field(None, alias="serviceStatus")
190
- settings: SettingsFeeder | None = None
191
- share_open: int | None = Field(None, alias="shareOpen")
192
- signup_at: str | None = Field(None, alias="signupAt")
193
- sn: str
194
- state: StateFeeder | None = None
195
- timezone: float | None = None
196
- p2p_type: int | None = Field(None, alias="p2pType")
197
- multi_config: bool | None = Field(None, alias="multiConfig")
198
- device_type: str | None = Field(None, alias="deviceType")
199
- manual_feed: ManualFeed | None = None
200
-
201
- @classmethod
202
- def get_endpoint(cls, device_type: str) -> str:
203
- """Get the endpoint URL for the given device type."""
204
- return PetkitEndpoint.DEVICE_DETAIL
205
-
206
- @classmethod
207
- def query_param(cls, account: AccountData, device_id: int) -> dict:
208
- """Generate query parameters including request_date."""
209
- return {"id": device_id}
210
-
211
-
212
170
  class EventState(BaseModel):
213
171
  """Dataclass for event state data."""
214
172
 
@@ -300,3 +258,46 @@ class FeederRecord(BaseModel):
300
258
  if request_date is None:
301
259
  request_date = datetime.now().strftime("%Y%m%d")
302
260
  return {"days": int(request_date), "deviceId": device_id}
261
+
262
+
263
+ class Feeder(BaseModel):
264
+ """Dataclass for feeder data."""
265
+
266
+ data_type: ClassVar[str] = DEVICE_DATA
267
+
268
+ auto_upgrade: int | None = Field(None, alias="autoUpgrade")
269
+ bt_mac: str | None = Field(None, alias="btMac")
270
+ cloud_product: CloudProduct | None = Field(None, alias="cloudProduct")
271
+ created_at: str | None = Field(None, alias="createdAt")
272
+ firmware: float
273
+ firmware_details: list[FirmwareDetail] = Field(alias="firmwareDetails")
274
+ hardware: int
275
+ id: int
276
+ locale: str | None = None
277
+ mac: str | None = None
278
+ model_code: int | None = Field(None, alias="modelCode")
279
+ multi_feed_item: MultiFeedItem | None = Field(None, alias="multiFeedItem")
280
+ name: str | None = None
281
+ secret: str | None = None
282
+ service_status: int | None = Field(None, alias="serviceStatus")
283
+ settings: SettingsFeeder | None = None
284
+ share_open: int | None = Field(None, alias="shareOpen")
285
+ signup_at: str | None = Field(None, alias="signupAt")
286
+ sn: str
287
+ state: StateFeeder | None = None
288
+ timezone: float | None = None
289
+ p2p_type: int | None = Field(None, alias="p2pType")
290
+ multi_config: bool | None = Field(None, alias="multiConfig")
291
+ device_type: str | None = Field(None, alias="deviceType")
292
+ manual_feed: ManualFeed | None = None
293
+ device_records: FeederRecord | None = None
294
+
295
+ @classmethod
296
+ def get_endpoint(cls, device_type: str) -> str:
297
+ """Get the endpoint URL for the given device type."""
298
+ return PetkitEndpoint.DEVICE_DETAIL
299
+
300
+ @classmethod
301
+ def query_param(cls, account: AccountData, device_id: int) -> dict:
302
+ """Generate query parameters including request_date."""
303
+ return {"id": device_id}
@@ -142,58 +142,6 @@ class StateLitter(BaseModel):
142
142
  work_state: WorkState | None = Field(None, alias="workState")
143
143
 
144
144
 
145
- class Litter(BaseModel):
146
- """Dataclass for Litter Data.
147
- Supported devices = T4, T6
148
- """
149
-
150
- data_type: ClassVar[str] = DEVICE_DATA
151
-
152
- auto_upgrade: int | None = Field(None, alias="autoUpgrade")
153
- bt_mac: str | None = Field(None, alias="btMac")
154
- created_at: str | None = Field(None, alias="createdAt")
155
- firmware: float
156
- firmware_details: list[FirmwareDetail] = Field(alias="firmwareDetails")
157
- hardware: int
158
- id: int
159
- is_pet_out_tips: int | None = Field(None, alias="isPetOutTips")
160
- locale: str | None = None
161
- mac: str | None = None
162
- maintenance_time: int | None = Field(None, alias="maintenanceTime")
163
- multi_config: bool | None = Field(None, alias="multiConfig")
164
- name: str | None = None
165
- pet_in_tip_limit: int | None = Field(None, alias="petInTipLimit")
166
- pet_out_tips: list[Any] | None = Field(None, alias="petOutTips")
167
- secret: str | None = None
168
- settings: SettingsLitter | None = None
169
- share_open: int | None = Field(None, alias="shareOpen")
170
- signup_at: str | None = Field(None, alias="signupAt")
171
- sn: str
172
- state: StateLitter | None = None
173
- timezone: float | None = None
174
- cloud_product: CloudProduct | None = Field(None, alias="cloudProduct") # For T5/T6
175
- in_times: int | None = Field(None, alias="inTimes")
176
- last_out_time: int | None = Field(None, alias="lastOutTime")
177
- p2p_type: int | None = Field(None, alias="p2pType")
178
- package_ignore_state: int | None = Field(None, alias="packageIgnoreState")
179
- package_total_count: int | None = Field(None, alias="packageTotalCount")
180
- package_used_count: int | None = Field(None, alias="packageUsedCount")
181
- pet_out_records: list[list[int]] | None = Field(None, alias="petOutRecords")
182
- service_status: int | None = Field(None, alias="serviceStatus")
183
- total_time: int | None = Field(None, alias="totalTime")
184
- device_type: str | None = Field(None, alias="deviceType")
185
-
186
- @classmethod
187
- def get_endpoint(cls, device_type: str) -> str:
188
- """Get the endpoint URL for the given device type."""
189
- return PetkitEndpoint.DEVICE_DETAIL
190
-
191
- @classmethod
192
- def query_param(cls, account: AccountData, device_id: int) -> dict:
193
- """Generate query parameters including request_date."""
194
- return {"id": device_id}
195
-
196
-
197
145
  class Content(BaseModel):
198
146
  """Dataclass for content data."""
199
147
 
@@ -247,3 +195,56 @@ class LitterRecord(BaseModel):
247
195
  if request_date is None:
248
196
  request_date = datetime.now().strftime("%Y%m%d")
249
197
  return {"date": int(request_date), "deviceId": device_id}
198
+
199
+
200
+ class Litter(BaseModel):
201
+ """Dataclass for Litter Data.
202
+ Supported devices = T4, T6
203
+ """
204
+
205
+ data_type: ClassVar[str] = DEVICE_DATA
206
+
207
+ auto_upgrade: int | None = Field(None, alias="autoUpgrade")
208
+ bt_mac: str | None = Field(None, alias="btMac")
209
+ created_at: str | None = Field(None, alias="createdAt")
210
+ firmware: float
211
+ firmware_details: list[FirmwareDetail] = Field(alias="firmwareDetails")
212
+ hardware: int
213
+ id: int
214
+ is_pet_out_tips: int | None = Field(None, alias="isPetOutTips")
215
+ locale: str | None = None
216
+ mac: str | None = None
217
+ maintenance_time: int | None = Field(None, alias="maintenanceTime")
218
+ multi_config: bool | None = Field(None, alias="multiConfig")
219
+ name: str | None = None
220
+ pet_in_tip_limit: int | None = Field(None, alias="petInTipLimit")
221
+ pet_out_tips: list[Any] | None = Field(None, alias="petOutTips")
222
+ secret: str | None = None
223
+ settings: SettingsLitter | None = None
224
+ share_open: int | None = Field(None, alias="shareOpen")
225
+ signup_at: str | None = Field(None, alias="signupAt")
226
+ sn: str
227
+ state: StateLitter | None = None
228
+ timezone: float | None = None
229
+ cloud_product: CloudProduct | None = Field(None, alias="cloudProduct") # For T5/T6
230
+ in_times: int | None = Field(None, alias="inTimes")
231
+ last_out_time: int | None = Field(None, alias="lastOutTime")
232
+ p2p_type: int | None = Field(None, alias="p2pType")
233
+ package_ignore_state: int | None = Field(None, alias="packageIgnoreState")
234
+ package_total_count: int | None = Field(None, alias="packageTotalCount")
235
+ package_used_count: int | None = Field(None, alias="packageUsedCount")
236
+ pet_out_records: list[list[int]] | None = Field(None, alias="petOutRecords")
237
+ service_status: int | None = Field(None, alias="serviceStatus")
238
+ total_time: int | None = Field(None, alias="totalTime")
239
+ device_type: str | None = Field(None, alias="deviceType")
240
+ device_records: LitterRecord | None = None
241
+
242
+ @classmethod
243
+ def get_endpoint(cls, device_type: str) -> str:
244
+ """Get the endpoint URL for the given device type."""
245
+ return PetkitEndpoint.DEVICE_DETAIL
246
+
247
+ @classmethod
248
+ def query_param(cls, account: AccountData, device_id: int) -> dict:
249
+ """Generate query parameters including request_date."""
250
+ return {"id": device_id}
@@ -85,6 +85,38 @@ class Status(BaseModel):
85
85
  suspend_status: int | None = Field(None, alias="suspendStatus")
86
86
 
87
87
 
88
+ class WaterFountainRecord(BaseModel):
89
+ """Dataclass for feeder record data."""
90
+
91
+ data_type: ClassVar[str] = DEVICE_RECORDS
92
+
93
+ day_time: int | None = Field(None, alias="dayTime")
94
+ stay_time: int | None = Field(None, alias="stayTime")
95
+ work_time: int | None = Field(None, alias="workTime")
96
+ device_type: str | None = Field(None, alias="deviceType")
97
+
98
+ @classmethod
99
+ def get_endpoint(cls, device_type: str) -> str:
100
+ """Get the endpoint URL for the given device type."""
101
+ return PetkitEndpoint.GET_WORK_RECORD
102
+
103
+ @classmethod
104
+ def query_param(
105
+ cls, account: AccountData, device_id: int, request_date: str | None = None
106
+ ) -> dict:
107
+ """Generate query parameters including request_date."""
108
+ if not account.user_list or not account.user_list[0]:
109
+ raise ValueError("The account does not have a valid user_list.")
110
+
111
+ if request_date is None:
112
+ request_date = datetime.now().strftime("%Y%m%d")
113
+ return {
114
+ "day": int(request_date),
115
+ "deviceId": device_id,
116
+ "userId": account.user_list[0].user_id,
117
+ }
118
+
119
+
88
120
  class WaterFountain(BaseModel):
89
121
  """Dataclass for Water Fountain Data.
90
122
  Supported devices = CTW3
@@ -128,6 +160,7 @@ class WaterFountain(BaseModel):
128
160
  user_id: str | None = Field(None, alias="userId")
129
161
  water_pump_run_time: int | None = Field(None, alias="waterPumpRunTime")
130
162
  device_type: str | None = Field(None, alias="deviceType")
163
+ device_records: list[WaterFountainRecord] | None = None
131
164
 
132
165
  @classmethod
133
166
  def get_endpoint(cls, device_type: str) -> str:
@@ -138,35 +171,3 @@ class WaterFountain(BaseModel):
138
171
  def query_param(cls, account: AccountData, device_id: int) -> dict:
139
172
  """Generate query parameters including request_date."""
140
173
  return {"id": device_id}
141
-
142
-
143
- class WaterFountainRecord(BaseModel):
144
- """Dataclass for feeder record data."""
145
-
146
- data_type: ClassVar[str] = DEVICE_RECORDS
147
-
148
- day_time: int | None = Field(None, alias="dayTime")
149
- stay_time: int | None = Field(None, alias="stayTime")
150
- work_time: int | None = Field(None, alias="workTime")
151
- device_type: str | None = Field(None, alias="deviceType")
152
-
153
- @classmethod
154
- def get_endpoint(cls, device_type: str) -> str:
155
- """Get the endpoint URL for the given device type."""
156
- return PetkitEndpoint.GET_WORK_RECORD
157
-
158
- @classmethod
159
- def query_param(
160
- cls, account: AccountData, device_id: int, request_date: str | None = None
161
- ) -> dict:
162
- """Generate query parameters including request_date."""
163
- if not account.user_list or not account.user_list[0]:
164
- raise ValueError("The account does not have a valid user_list.")
165
-
166
- if request_date is None:
167
- request_date = datetime.now().strftime("%Y%m%d")
168
- return {
169
- "day": int(request_date),
170
- "deviceId": device_id,
171
- "userId": account.user_list[0].user_id,
172
- }
@@ -187,7 +187,7 @@ build-backend = "poetry.core.masonry.api"
187
187
 
188
188
  [tool.poetry]
189
189
  name = "pypetkitapi"
190
- version = "1.0.0"
190
+ version = "1.2.0"
191
191
  description = "Python client for PetKit API"
192
192
  authors = ["Jezza34000 <info@mail.com>"]
193
193
  readme = "README.md"
@@ -204,7 +204,7 @@ black = "^24.10.0"
204
204
  ruff = "^0.8.1"
205
205
 
206
206
  [tool.bumpver]
207
- current_version = "1.0.0"
207
+ current_version = "1.2.0"
208
208
  version_pattern = "MAJOR.MINOR.PATCH"
209
209
  commit_message = "bump version {old_version} -> {new_version}"
210
210
  tag_message = "{new_version}"
File without changes
File without changes