pypetkitapi 1.10.2__py3-none-any.whl → 1.10.3__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/__init__.py CHANGED
@@ -51,7 +51,7 @@ from .media import DownloadDecryptMedia, MediaCloud, MediaFile, MediaManager
51
51
  from .purifier_container import Purifier
52
52
  from .water_fountain_container import WaterFountain
53
53
 
54
- __version__ = "1.10.2"
54
+ __version__ = "1.10.3"
55
55
 
56
56
  __all__ = [
57
57
  "CTW3",
pypetkitapi/client.py CHANGED
@@ -108,8 +108,9 @@ class PetKitClient:
108
108
  _LOGGER.debug("Getting API server list")
109
109
  self.req.base_url = PetkitDomain.PASSPORT_PETKIT
110
110
 
111
- if self.region.lower() == "china":
111
+ if self.region.lower() == "china" or self.region.lower() == "cn":
112
112
  self.req.base_url = PetkitDomain.CHINA_SRV
113
+ _LOGGER.debug("Using specific China server: %s", PetkitDomain.CHINA_SRV)
113
114
  return
114
115
 
115
116
  response = await self.req.request(
pypetkitapi/media.py CHANGED
@@ -31,8 +31,8 @@ _LOGGER = logging.getLogger(__name__)
31
31
 
32
32
  @dataclass
33
33
  class MediaCloud:
34
- """Dataclass MediaFile.
35
- Represents a media file from a PetKit device.
34
+ """Dataclass MediaCloud.
35
+ Represents a media file from Petkit API.
36
36
  """
37
37
 
38
38
  event_id: str
@@ -68,7 +68,10 @@ class MediaManager:
68
68
  async def get_all_media_files(
69
69
  self, devices: list[Feeder | Litter]
70
70
  ) -> list[MediaCloud]:
71
- """Get all media files from all devices and return a list of MediaFile."""
71
+ """Get all media files from all devices and return a list of MediaCloud.
72
+ :param devices: List of devices
73
+ :return: List of MediaCloud objects
74
+ """
72
75
  media_files: list[MediaCloud] = []
73
76
  _LOGGER.debug("Processing media files for %s devices", len(devices))
74
77
 
@@ -101,7 +104,10 @@ class MediaManager:
101
104
  async def get_all_media_files_disk(
102
105
  self, storage_path: Path, device_id: int
103
106
  ) -> None:
104
- """Construct the media file table for disk storage."""
107
+ """Construct the media file table for disk storage.
108
+ :param storage_path: Path to the storage directory
109
+ :param device_id: Device ID
110
+ """
105
111
  self.media_table.clear()
106
112
 
107
113
  today_str = datetime.now().strftime("%Y%m%d")
@@ -129,7 +135,7 @@ class MediaManager:
129
135
  if entry.is_file() and valid_pattern.match(entry.name):
130
136
  _LOGGER.debug("Entries found: %s", entry.name)
131
137
  event_id = Path(entry.name).stem
132
- timestamp = self._extraire_timestamp(str(entry.name))
138
+ timestamp = self._extract_timestamp(str(entry.name))
133
139
  media_type_str = Path(entry.name).suffix.lstrip(".")
134
140
  try:
135
141
  media_type = MediaType(media_type_str)
@@ -148,8 +154,12 @@ class MediaManager:
148
154
  )
149
155
 
150
156
  @staticmethod
151
- def _extraire_timestamp(nom_fichier: str) -> int:
152
- match = re.search(r"_(\d+)\.[a-zA-Z0-9]+$", nom_fichier)
157
+ def _extract_timestamp(file_name: str) -> int:
158
+ """Extract timestamp from a filename.
159
+ :param file_name: Filename
160
+ :return: Timestamp
161
+ """
162
+ match = re.search(r"_(\d+)\.[a-zA-Z0-9]+$", file_name)
153
163
  if match:
154
164
  return int(match.group(1))
155
165
  return 0
@@ -160,7 +170,12 @@ class MediaManager:
160
170
  dl_type: list[MediaType] | None = None,
161
171
  event_type: list[RecordType] | None = None,
162
172
  ) -> list[MediaCloud]:
163
- """Compare MediaCloud objects with MediaFile objects and return a list of missing MediaCloud objects."""
173
+ """Compare MediaCloud objects with MediaFile objects and return a list of missing MediaCloud objects.
174
+ :param media_cloud_list: List of MediaCloud objects
175
+ :param dl_type: List of media types to download
176
+ :param event_type: List of event types to filter
177
+ :return: List of missing MediaCloud objects
178
+ """
164
179
  missing_media = []
165
180
  existing_event_ids = {media_file.event_id for media_file in self.media_table}
166
181
 
@@ -205,7 +220,10 @@ class MediaManager:
205
220
  return missing_media
206
221
 
207
222
  def _process_feeder(self, feeder: Feeder) -> list[MediaCloud]:
208
- """Process media files for a Feeder device."""
223
+ """Process media files for a Feeder device.
224
+ :param feeder: Feeder device object
225
+ :return: List of MediaCloud objects for the device
226
+ """
209
227
  media_files: list[MediaCloud] = []
210
228
  records = feeder.device_records
211
229
 
@@ -225,7 +243,12 @@ class MediaManager:
225
243
  def _process_feeder_record(
226
244
  self, record, record_type: RecordType, device_obj: Feeder
227
245
  ) -> list[MediaCloud]:
228
- """Process individual feeder records."""
246
+ """Process individual feeder records.
247
+ :param record: Record object
248
+ :param record_type: Record type
249
+ :param device_obj: Feeder device object
250
+ :return: List of MediaCloud objects for the record
251
+ """
229
252
  media_files: list[MediaCloud] = []
230
253
  user_id = device_obj.user.id if device_obj.user else None
231
254
  feeder_id = device_obj.device_nfo.device_id if device_obj.device_nfo else None
@@ -283,7 +306,10 @@ class MediaManager:
283
306
  return media_files
284
307
 
285
308
  def _process_litter(self, litter: Litter) -> list[MediaCloud]:
286
- """Process media files for a Litter device."""
309
+ """Process media files for a Litter device.
310
+ :param litter: Litter device object
311
+ :return: List of MediaCloud objects for the device
312
+ """
287
313
  media_files: list[MediaCloud] = []
288
314
  records = litter.device_records
289
315
  litter_id = litter.device_nfo.device_id if litter.device_nfo else None
@@ -345,7 +371,13 @@ class MediaManager:
345
371
  def construct_video_url(
346
372
  device_type: str | None, media_url: str | None, user_id: int, cp_sub: int | None
347
373
  ) -> str | None:
348
- """Construct the video URL."""
374
+ """Construct the video URL.
375
+ :param device_type: Device type
376
+ :param media_url: Media URL
377
+ :param user_id: User ID
378
+ :param cp_sub: Cpsub value
379
+ :return: Constructed video URL
380
+ """
349
381
  if not media_url or not user_id or cp_sub != 1:
350
382
  return None
351
383
  params = parse_qs(urlparse(media_url).query)
@@ -354,7 +386,10 @@ class MediaManager:
354
386
 
355
387
  @staticmethod
356
388
  def _get_timestamp(item) -> int:
357
- """Extract timestamp from a record item and raise an exception if it is None."""
389
+ """Extract timestamp from a record item and raise an exception if it is None.
390
+ :param item: Record item
391
+ :return: Timestamp
392
+ """
358
393
  timestamp = (
359
394
  item.timestamp
360
395
  or item.completed_at
@@ -381,7 +416,10 @@ class DownloadDecryptMedia:
381
416
  self.client = client
382
417
 
383
418
  async def get_fpath(self, file_name: str) -> Path:
384
- """Return the full path of the file."""
419
+ """Return the full path of the file.
420
+ :param file_name: Name of the file.
421
+ :return: Full path of the file.
422
+ """
385
423
  subdir = ""
386
424
  if file_name.endswith(".jpg"):
387
425
  subdir = "snapshot"
@@ -392,7 +430,10 @@ class DownloadDecryptMedia:
392
430
  async def download_file(
393
431
  self, file_data: MediaCloud, file_type: MediaType | None
394
432
  ) -> None:
395
- """Get image and video file"""
433
+ """Get image and video file
434
+ :param file_data: MediaCloud object
435
+ :param file_type: MediaType object
436
+ """
396
437
  _LOGGER.debug("Downloading media file %s", file_data.event_id)
397
438
  self.file_data = file_data
398
439
 
@@ -459,7 +500,12 @@ class DownloadDecryptMedia:
459
500
  return await self.client.extract_segments_m3u8(str(media_api))
460
501
 
461
502
  async def _get_file(self, url: str, aes_key: str, full_filename: str) -> bool:
462
- """Download a file from a URL and decrypt it."""
503
+ """Download a file from a URL and decrypt it.
504
+ :param url: URL of the file to download.
505
+ :param aes_key: AES key used for decryption.
506
+ :param full_filename: Name of the file to save.
507
+ :return: True if the file was downloaded successfully, False otherwise.
508
+ """
463
509
 
464
510
  full_file_path = await self.get_fpath(full_filename)
465
511
  if full_file_path.exists():
@@ -487,7 +533,11 @@ class DownloadDecryptMedia:
487
533
  return False
488
534
 
489
535
  async def _save_file(self, content: bytes, filename: str) -> Path:
490
- """Save content to a file asynchronously and return the file path."""
536
+ """Save content to a file asynchronously and return the file path.
537
+ :param content: Bytes data to save.
538
+ :param filename: Name of the file to save.
539
+ :return: Path of the saved file.
540
+ """
491
541
  file_path = await self.get_fpath(filename)
492
542
  try:
493
543
  # Ensure the directory exists
@@ -534,11 +584,11 @@ class DownloadDecryptMedia:
534
584
  Path(file_path).unlink()
535
585
  return decrypted_data
536
586
 
537
- async def _concat_segments(self, ts_files: list[Path], output_file):
538
- """Concatenate a list of .ts segments into a single output file without using a temporary file.
587
+ async def _concat_segments(self, ts_files: list[Path], output_file) -> None:
588
+ """Concatenate a list of .avi segments into a single output file without using a temporary file.
539
589
 
540
- :param ts_files: List of absolute paths of .ts files
541
- :param output_file: Path of the output file (e.g., "output.mp4")
590
+ :param ts_files: List of absolute paths of .avi files
591
+ :param output_file: Path of the output file (e.g., "output.avi")
542
592
  """
543
593
  full_output_file = await self.get_fpath(output_file)
544
594
  if full_output_file.exists():
@@ -586,7 +636,9 @@ class DownloadDecryptMedia:
586
636
  _LOGGER.error("OS error during concatenation: %s", e)
587
637
 
588
638
  async def _delete_segments(self, ts_files: list[Path]) -> None:
589
- """Delete all segment files after concatenation."""
639
+ """Delete all segment files after concatenation.
640
+ :param ts_files: List of absolute paths of .avi files
641
+ """
590
642
  for file in ts_files:
591
643
  if file.exists():
592
644
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pypetkitapi
3
- Version: 1.10.2
3
+ Version: 1.10.3
4
4
  Summary: Python client for PetKit API
5
5
  License: MIT
6
6
  Author: Jezza34000
@@ -1,19 +1,19 @@
1
- pypetkitapi/__init__.py,sha256=mdImqvI5uFp8gupd6pf9IACfLFVEXEfEO_8PitRyzUo,2107
1
+ pypetkitapi/__init__.py,sha256=qP06yQAfRHrJ7yiYlQgNDJD5cGUggj29WsKLhsR_P8M,2107
2
2
  pypetkitapi/bluetooth.py,sha256=u_xGp701WnrroTOt_KuIVUCZ3kRQ7BJeoMR8b9RpJ54,7176
3
- pypetkitapi/client.py,sha256=6HdTx4Bj8zwHzSzvQz1acdRzGLCX8nETsTeIv-BVc9M,26921
3
+ pypetkitapi/client.py,sha256=mbND1lzu1DQ2hHxcQnyk9-8ig1JJ5lAo8S-3lGKmYXs,27037
4
4
  pypetkitapi/command.py,sha256=cMCUutZCQo9Ddvjl_FYR5UjU_CqFz1iyetMznYwjpzM,7500
5
5
  pypetkitapi/const.py,sha256=US5QihmBYvlm8hIHX0PORPUnMmDW3nmLzwLWTepkkGg,4609
6
6
  pypetkitapi/containers.py,sha256=F_uyDBD0a5QD4s_ArjYiKTAAg1XHYBvmV_lEnO9RQ-U,4786
7
7
  pypetkitapi/exceptions.py,sha256=4BXUyYXLfZjNxdnOGJPjyE9ASIl7JmQphjws87jvHtE,1631
8
8
  pypetkitapi/feeder_container.py,sha256=PhidWd5WpsZqtdKZy60PzE67YXgQfApjm8CqvMCHK3U,14743
9
9
  pypetkitapi/litter_container.py,sha256=KWvHNAOJ6hDSeJ_55tqtzY9GxHtd9gAntPkbnVbdb-I,19275
10
- pypetkitapi/media.py,sha256=AWkFMNxDOk2CUE0XJLTZZIRDMvBBZTcR1lLKCtKB7jM,22339
10
+ pypetkitapi/media.py,sha256=OrtEN9LKe3Xy4AapvTUCj4LZ09ZAltGpRYEmD_xQwcA,24220
11
11
  pypetkitapi/purifier_container.py,sha256=ssyIxhNben5XJ4KlQTXTrtULg2ji6DqHqjzOq08d1-I,2491
12
12
  pypetkitapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  pypetkitapi/schedule_container.py,sha256=OjLAY6FY-g14JNJJnYMNFV5ZtdkjUzNBit1VUiiZKnQ,2053
14
14
  pypetkitapi/utils.py,sha256=z7325kcJQUburnF28HSXrJMvY_gY9007K73Zwxp-4DQ,743
15
15
  pypetkitapi/water_fountain_container.py,sha256=5J0b-fDZYcFLNX2El7fifv8H6JMhBCt-ttxSow1ozRQ,6787
16
- pypetkitapi-1.10.2.dist-info/LICENSE,sha256=u5jNkZEn6YMrtN4Kr5rU3TcBJ5-eAt0qMx4JDsbsnzM,1074
17
- pypetkitapi-1.10.2.dist-info/METADATA,sha256=OdpyJTrBznYfoFqdoLEYFVJ_Z4QVjaKoZtTiQNdTTmo,6256
18
- pypetkitapi-1.10.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
19
- pypetkitapi-1.10.2.dist-info/RECORD,,
16
+ pypetkitapi-1.10.3.dist-info/LICENSE,sha256=u5jNkZEn6YMrtN4Kr5rU3TcBJ5-eAt0qMx4JDsbsnzM,1074
17
+ pypetkitapi-1.10.3.dist-info/METADATA,sha256=OyJ5ainwvLr8ZSkZVM6Dt3cfLlqR1SRE2kXgiq5rBUs,6256
18
+ pypetkitapi-1.10.3.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
19
+ pypetkitapi-1.10.3.dist-info/RECORD,,