uiprotect 1.2.2__py3-none-any.whl → 1.4.0__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.

Potentially problematic release.


This version of uiprotect might be problematic. Click here for more details.

uiprotect/api.py CHANGED
@@ -1146,7 +1146,7 @@ class ProtectApiClient(BaseApiClient):
1146
1146
 
1147
1147
  async def get_devices_raw(self, model_type: ModelType) -> list[dict[str, Any]]:
1148
1148
  """Gets a raw device list given a model_type"""
1149
- return await self.api_request_list(f"{model_type.value}s")
1149
+ return await self.api_request_list(model_type.devices_key)
1150
1150
 
1151
1151
  async def get_devices(
1152
1152
  self,
@@ -1703,7 +1703,7 @@ class ProtectApiClient(BaseApiClient):
1703
1703
 
1704
1704
  async def adopt_device(self, model_type: ModelType, device_id: str) -> None:
1705
1705
  """Adopts a device"""
1706
- key = f"{model_type.value}s"
1706
+ key = model_type.devices_key
1707
1707
  data = await self.api_request_obj(
1708
1708
  "devices/adopt",
1709
1709
  method="post",
@@ -80,12 +80,9 @@ CAMERA_EVENT_ATTR_MAP: dict[EventType, tuple[str, str]] = {
80
80
  }
81
81
 
82
82
 
83
- def _process_light_event(event: Event) -> None:
84
- if event.light is None:
85
- return
86
-
87
- if _event_is_in_range(event, event.light.last_motion):
88
- event.light.last_motion_event_id = event.id
83
+ def _process_light_event(event: Event, light: Light) -> None:
84
+ if _event_is_in_range(event, light.last_motion):
85
+ light.last_motion_event_id = event.id
89
86
 
90
87
 
91
88
  def _event_is_in_range(event: Event, dt: datetime | None) -> bool:
@@ -95,22 +92,20 @@ def _event_is_in_range(event: Event, dt: datetime | None) -> bool:
95
92
  )
96
93
 
97
94
 
98
- def _process_sensor_event(event: Event) -> None:
99
- if event.sensor is None:
100
- return
95
+ def _process_sensor_event(event: Event, sensor: Sensor) -> None:
101
96
  if event.type is EventType.MOTION_SENSOR:
102
- if _event_is_in_range(event, event.sensor.motion_detected_at):
103
- event.sensor.last_motion_event_id = event.id
97
+ if _event_is_in_range(event, sensor.motion_detected_at):
98
+ sensor.last_motion_event_id = event.id
104
99
  elif event.type in {EventType.SENSOR_CLOSED, EventType.SENSOR_OPENED}:
105
- if _event_is_in_range(event, event.sensor.open_status_changed_at):
106
- event.sensor.last_contact_event_id = event.id
100
+ if _event_is_in_range(event, sensor.open_status_changed_at):
101
+ sensor.last_contact_event_id = event.id
107
102
  elif event.type is EventType.SENSOR_EXTREME_VALUE:
108
- if _event_is_in_range(event, event.sensor.extreme_value_detected_at):
109
- event.sensor.extreme_value_detected_at = event.end
110
- event.sensor.last_value_event_id = event.id
103
+ if _event_is_in_range(event, sensor.extreme_value_detected_at):
104
+ sensor.extreme_value_detected_at = event.end
105
+ sensor.last_value_event_id = event.id
111
106
  elif event.type is EventType.SENSOR_ALARM:
112
- if _event_is_in_range(event, event.sensor.alarm_triggered_at):
113
- event.sensor.last_value_event_id = event.id
107
+ if _event_is_in_range(event, sensor.alarm_triggered_at):
108
+ sensor.last_value_event_id = event.id
114
109
 
115
110
 
116
111
  _CAMERA_SMART_AND_LINE_EVENTS = {
@@ -120,10 +115,7 @@ _CAMERA_SMART_AND_LINE_EVENTS = {
120
115
  _CAMERA_SMART_AUDIO_EVENT = EventType.SMART_AUDIO_DETECT
121
116
 
122
117
 
123
- def _process_camera_event(event: Event) -> None:
124
- if (camera := event.camera) is None:
125
- return
126
-
118
+ def _process_camera_event(event: Event, camera: Camera) -> None:
127
119
  event_type = event.type
128
120
  dt_attr, event_attr = CAMERA_EVENT_ATTR_MAP[event_type]
129
121
  dt: datetime | None = getattr(camera, dt_attr)
@@ -205,8 +197,8 @@ class Bootstrap(ProtectBaseObject):
205
197
  )
206
198
  data["macLookup"] = {}
207
199
  data["idLookup"] = {}
208
- for model_type in ModelType.bootstrap_models():
209
- key = f"{model_type}s"
200
+ for model_type in ModelType.bootstrap_models_types_set():
201
+ key = model_type.devices_key
210
202
  items: dict[str, ProtectModel] = {}
211
203
  for item in data[key]:
212
204
  if (
@@ -242,8 +234,8 @@ class Bootstrap(ProtectBaseObject):
242
234
  if "idLookup" in data:
243
235
  del data["idLookup"]
244
236
 
245
- for model_type in ModelType.bootstrap_models():
246
- attr = f"{model_type}s"
237
+ for model_type in ModelType.bootstrap_models_types_set():
238
+ attr = model_type.devices_key
247
239
  if attr in data and isinstance(data[attr], dict):
248
240
  data[attr] = list(data[attr].values())
249
241
 
@@ -310,7 +302,7 @@ class Bootstrap(ProtectBaseObject):
310
302
  if ref is None:
311
303
  return None
312
304
 
313
- devices: dict[str, ProtectModelWithId] = getattr(self, f"{ref.model.value}s")
305
+ devices: dict[str, ProtectModelWithId] = getattr(self, ref.model.devices_key)
314
306
  return cast(ProtectAdoptableDeviceModel, devices.get(ref.id))
315
307
 
316
308
  def get_device_from_id(self, device_id: str) -> ProtectAdoptableDeviceModel | None:
@@ -318,16 +310,17 @@ class Bootstrap(ProtectBaseObject):
318
310
  ref = self.id_lookup.get(device_id)
319
311
  if ref is None:
320
312
  return None
321
- devices: dict[str, ProtectModelWithId] = getattr(self, f"{ref.model.value}s")
313
+ devices: dict[str, ProtectModelWithId] = getattr(self, ref.model.devices_key)
322
314
  return cast(ProtectAdoptableDeviceModel, devices.get(ref.id))
323
315
 
324
316
  def process_event(self, event: Event) -> None:
325
- if event.type in CAMERA_EVENT_ATTR_MAP and event.camera is not None:
326
- _process_camera_event(event)
327
- elif event.type == EventType.MOTION_LIGHT and event.light is not None:
328
- _process_light_event(event)
329
- elif event.type == EventType.MOTION_SENSOR and event.sensor is not None:
330
- _process_sensor_event(event)
317
+ event_type = event.type
318
+ if event_type in CAMERA_EVENT_ATTR_MAP and (camera := event.camera):
319
+ _process_camera_event(event, camera)
320
+ elif event_type is EventType.MOTION_LIGHT and (light := event.light):
321
+ _process_light_event(event, light)
322
+ elif event_type is EventType.MOTION_SENSOR and (sensor := event.sensor):
323
+ _process_sensor_event(event, sensor)
331
324
 
332
325
  self.events[event.id] = event
333
326
 
@@ -351,21 +344,24 @@ class Bootstrap(ProtectBaseObject):
351
344
 
352
345
  def _process_add_packet(
353
346
  self,
347
+ model_type: ModelType,
354
348
  packet: WSPacket,
355
349
  data: dict[str, Any],
356
350
  ) -> WSSubscriptionMessage | None:
357
- obj = create_from_unifi_dict(data, api=self._api)
358
-
359
- if isinstance(obj, Event):
351
+ obj = create_from_unifi_dict(data, api=self._api, model_type=model_type)
352
+ if model_type is ModelType.EVENT:
353
+ if TYPE_CHECKING:
354
+ assert isinstance(obj, Event)
360
355
  self.process_event(obj)
361
- elif isinstance(obj, NVR):
356
+ if model_type is ModelType.NVR:
357
+ if TYPE_CHECKING:
358
+ assert isinstance(obj, NVR)
362
359
  self.nvr = obj
363
- elif (
364
- isinstance(obj, ProtectAdoptableDeviceModel)
365
- and obj.model is not None
366
- and obj.model.value in ModelType.bootstrap_models_set()
367
- ):
368
- key = f"{obj.model.value}s"
360
+ elif model_type in ModelType.bootstrap_models_types_set():
361
+ if TYPE_CHECKING:
362
+ assert isinstance(obj, ProtectAdoptableDeviceModel)
363
+ assert isinstance(obj.model, ModelType)
364
+ key = obj.model.devices_key
369
365
  if not self._api.ignore_unadopted or (
370
366
  obj.is_adopted and not obj.is_adopted_by_other
371
367
  ):
@@ -388,9 +384,12 @@ class Bootstrap(ProtectBaseObject):
388
384
  new_obj=obj,
389
385
  )
390
386
 
391
- def _process_remove_packet(self, packet: WSPacket) -> WSSubscriptionMessage | None:
392
- model: str | None = packet.action_frame.data.get("modelKey")
393
- devices: dict[str, ProtectDeviceModel] | None = getattr(self, f"{model}s", None)
387
+ def _process_remove_packet(
388
+ self, model_type: ModelType, packet: WSPacket
389
+ ) -> WSSubscriptionMessage | None:
390
+ devices: dict[str, ProtectDeviceModel] | None = getattr(
391
+ self, model_type.devices_key, None
392
+ )
394
393
 
395
394
  if devices is None:
396
395
  return None
@@ -450,12 +449,12 @@ class Bootstrap(ProtectBaseObject):
450
449
 
451
450
  def _process_device_update(
452
451
  self,
452
+ model_type: ModelType,
453
453
  packet: WSPacket,
454
454
  action: dict[str, Any],
455
455
  data: dict[str, Any],
456
456
  ignore_stats: bool,
457
457
  ) -> WSSubscriptionMessage | None:
458
- model_type = action["modelKey"]
459
458
  remove_keys = (
460
459
  STATS_AND_IGNORE_DEVICE_KEYS if ignore_stats else IGNORE_DEVICE_KEYS
461
460
  )
@@ -463,33 +462,31 @@ class Bootstrap(ProtectBaseObject):
463
462
  del data[key]
464
463
  # `last_motion` from cameras update every 100 milliseconds when a motion event is active
465
464
  # this overrides the behavior to only update `last_motion` when a new event starts
466
- if model_type == "camera" and "lastMotion" in data:
465
+ if model_type is ModelType.CAMERA and "lastMotion" in data:
467
466
  del data["lastMotion"]
468
467
  # nothing left to process
469
468
  if not data:
470
469
  self._create_stat(packet, None, True)
471
470
  return None
472
471
 
473
- key = f"{model_type}s"
474
- devices: dict[str, ProtectModelWithId] = getattr(self, key)
472
+ devices: dict[str, ProtectModelWithId] = getattr(self, model_type.devices_key)
475
473
  action_id: str = action["id"]
476
474
  if action_id not in devices:
477
475
  # ignore updates to events that phase out
478
- if model_type != _ModelType_Event_value:
476
+ if model_type is not ModelType.EVENT:
479
477
  _LOGGER.debug("Unexpected %s: %s", key, action_id)
480
478
  return None
481
479
 
482
480
  obj = devices[action_id]
483
- model = obj.model
484
481
  data = obj.unifi_dict_to_dict(data)
485
482
  old_obj = obj.copy()
486
483
  obj = obj.update_from_dict(deepcopy(data))
487
484
 
488
- if model is ModelType.EVENT:
485
+ if model_type is ModelType.EVENT:
489
486
  if TYPE_CHECKING:
490
487
  assert isinstance(obj, Event)
491
488
  self.process_event(obj)
492
- elif model is ModelType.CAMERA:
489
+ elif model_type is ModelType.CAMERA:
493
490
  if TYPE_CHECKING:
494
491
  assert isinstance(obj, Camera)
495
492
  if "last_ring" in data and obj.last_ring:
@@ -497,7 +494,7 @@ class Bootstrap(ProtectBaseObject):
497
494
  _LOGGER.debug("last_ring for %s (%s)", obj.id, is_recent)
498
495
  if is_recent:
499
496
  obj.set_ring_timeout()
500
- elif model is ModelType.SENSOR:
497
+ elif model_type is ModelType.SENSOR:
501
498
  if TYPE_CHECKING:
502
499
  assert isinstance(obj, Sensor)
503
500
  if "alarm_triggered_at" in data and obj.alarm_triggered_at:
@@ -539,13 +536,14 @@ class Bootstrap(ProtectBaseObject):
539
536
  self._create_stat(packet, None, True)
540
537
  return None
541
538
 
542
- if models and ModelType(model_key) not in models:
539
+ model_type = ModelType.from_string(model_key)
540
+ if models and model_type not in models:
543
541
  self._create_stat(packet, None, True)
544
542
  return None
545
543
 
546
544
  action_action: str = action["action"]
547
545
  if action_action == "remove":
548
- return self._process_remove_packet(packet)
546
+ return self._process_remove_packet(model_type, packet)
549
547
 
550
548
  if not data:
551
549
  self._create_stat(packet, None, True)
@@ -553,16 +551,13 @@ class Bootstrap(ProtectBaseObject):
553
551
 
554
552
  try:
555
553
  if action_action == "add":
556
- return self._process_add_packet(packet, data)
554
+ return self._process_add_packet(model_type, packet, data)
557
555
  if action_action == "update":
558
- if model_key == _ModelType_NVR_value:
556
+ if model_type is ModelType.NVR:
559
557
  return self._process_nvr_update(packet, data, ignore_stats)
560
-
561
- if (
562
- model_key in ModelType.bootstrap_models_set()
563
- or model_key == _ModelType_Event_value
564
- ):
558
+ if model_type in ModelType.bootstrap_models_types_and_event_set():
565
559
  return self._process_device_update(
560
+ model_type,
566
561
  packet,
567
562
  action,
568
563
  data,
@@ -583,7 +578,7 @@ class Bootstrap(ProtectBaseObject):
583
578
  msg = f"Validation error processing event: {action['id']}. Ignoring event."
584
579
  else:
585
580
  try:
586
- model_type = ModelType(action["modelKey"])
581
+ model_type = ModelType.from_string(action["modelKey"])
587
582
  device_id: str = action["id"]
588
583
  task = asyncio.create_task(self.refresh_device(model_type, device_id))
589
584
  self._refresh_tasks.add(task)
@@ -616,7 +611,7 @@ class Bootstrap(ProtectBaseObject):
616
611
  self.nvr = device
617
612
  else:
618
613
  devices: dict[str, ProtectModelWithId] = getattr(
619
- self, f"{model_type.value}s"
614
+ self, model_type.devices_key
620
615
  )
621
616
  devices[device.id] = device
622
617
  _LOGGER.debug("Successfully refresh model: %s %s", model_type, device_id)
uiprotect/data/convert.py CHANGED
@@ -63,6 +63,7 @@ def create_from_unifi_dict(
63
63
  data: dict[str, Any],
64
64
  api: ProtectApiClient | None = None,
65
65
  klass: type[ProtectModel] | None = None,
66
+ model_type: ModelType | None = None,
66
67
  ) -> ProtectModel:
67
68
  """
68
69
  Helper method to read the `modelKey` from a UFP JSON dict and convert to currect Python class.
@@ -71,6 +72,9 @@ def create_from_unifi_dict(
71
72
  if "modelKey" not in data:
72
73
  raise DataDecodeError("No modelKey")
73
74
 
75
+ if model_type is not None and klass is None:
76
+ klass = MODEL_TO_CLASS.get(model_type)
77
+
74
78
  if klass is None:
75
79
  klass = get_klass_from_dict(data)
76
80
 
uiprotect/data/types.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import enum
4
4
  from collections.abc import Callable, Coroutine
5
- from functools import cache
5
+ from functools import cache, cached_property
6
6
  from typing import Any, Literal, Optional, TypeVar, Union
7
7
 
8
8
  from packaging.version import Version as BaseVersion
@@ -109,31 +109,62 @@ class ModelType(str, UnknownValuesEnumMixin, enum.Enum):
109
109
  RECORDING_SCHEDULE = "recordingSchedule"
110
110
  UNKNOWN = "unknown"
111
111
 
112
+ @cached_property
113
+ def devices_key(self) -> str:
114
+ """Return the devices key."""
115
+ return f"{self.value}s"
116
+
117
+ @classmethod
118
+ @cache
119
+ def from_string(cls, value: str) -> ModelType:
120
+ return cls(value)
121
+
112
122
  @staticmethod
113
123
  @cache
114
- def bootstrap_models() -> tuple[str, ...]:
124
+ def bootstrap_model_types() -> tuple[ModelType, ...]:
125
+ """Return the bootstrap models as a tuple."""
115
126
  # TODO:
116
127
  # legacyUFV
117
128
  # display
118
-
119
129
  return (
120
- ModelType.CAMERA.value,
121
- ModelType.USER.value,
122
- ModelType.GROUP.value,
123
- ModelType.LIVEVIEW.value,
124
- ModelType.VIEWPORT.value,
125
- ModelType.LIGHT.value,
126
- ModelType.BRIDGE.value,
127
- ModelType.SENSOR.value,
128
- ModelType.DOORLOCK.value,
129
- ModelType.CHIME.value,
130
+ ModelType.CAMERA,
131
+ ModelType.USER,
132
+ ModelType.GROUP,
133
+ ModelType.LIVEVIEW,
134
+ ModelType.VIEWPORT,
135
+ ModelType.LIGHT,
136
+ ModelType.BRIDGE,
137
+ ModelType.SENSOR,
138
+ ModelType.DOORLOCK,
139
+ ModelType.CHIME,
140
+ )
141
+
142
+ @staticmethod
143
+ @cache
144
+ def bootstrap_models() -> tuple[str, ...]:
145
+ """Return the bootstrap models strings as a tuple."""
146
+ return tuple(
147
+ model_type.value for model_type in ModelType.bootstrap_model_types()
130
148
  )
131
149
 
132
150
  @staticmethod
133
151
  @cache
134
152
  def bootstrap_models_set() -> set[str]:
153
+ """Return the set of bootstrap models strings as a set."""
135
154
  return set(ModelType.bootstrap_models())
136
155
 
156
+ @staticmethod
157
+ @cache
158
+ def bootstrap_models_types_set() -> set[ModelType]:
159
+ """Return the set of bootstrap models as a set."""
160
+ return set(ModelType.bootstrap_model_types())
161
+
162
+ @staticmethod
163
+ @cache
164
+ def bootstrap_models_types_and_event_set() -> set[ModelType]:
165
+ """Return the set of bootstrap models and the event model as a set."""
166
+ return ModelType.bootstrap_models_types_set() | {ModelType.EVENT}
167
+
137
168
 
138
169
  @enum.unique
139
170
  class EventType(str, ValuesEnumMixin, enum.Enum):
uiprotect/data/user.py CHANGED
@@ -54,7 +54,7 @@ class Permission(ProtectBaseObject):
54
54
  if self.obj_ids == {"self"} or self.obj_ids is None:
55
55
  return None
56
56
 
57
- devices = getattr(self._api.bootstrap, f"{self.model.value}s")
57
+ devices = getattr(self._api.bootstrap, self.model.devices_key)
58
58
  return [devices[oid] for oid in self.obj_ids]
59
59
 
60
60
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 1.2.2
3
+ Version: 1.4.0
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
5
  Home-page: https://github.com/uilibs/uiprotect
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  uiprotect/__init__.py,sha256=llnQNtiBfwQG8IkQXovvFz4LZeFjrJx7XdmmUhu3a9E,289
2
2
  uiprotect/__main__.py,sha256=C_bHCOkv5qj6WMy-6ELoY3Y6HDhLxOa1a30CzmbZhsg,462
3
- uiprotect/api.py,sha256=hN1PvNhOTFIfOmBtnUCfpujIXBEBYIKttaYnqlS_2es,66463
3
+ uiprotect/api.py,sha256=t3E1wMja0av6ENtvXJHGv70WC6NCCWbF_wFIJxOLS0I,66463
4
4
  uiprotect/cli/__init__.py,sha256=sSLW9keVQOkgFcMW18HTDjRrt9sJ0KWjn9DJDA6f9Pc,8658
5
5
  uiprotect/cli/backup.py,sha256=ZiS7RZnJGKI8TJKLW2cOUzkRM8nyTvE5Ov_jZZGtvSM,36708
6
6
  uiprotect/cli/base.py,sha256=zpTm2kyJe_GLixnv3Uadke__iRLh64AEwQzp-2hqS7g,7730
@@ -15,12 +15,12 @@ uiprotect/cli/sensors.py,sha256=fQtcDJCVxs4VbAqcavgBy2ABiVxAW3GXtna6_XFBp2k,8153
15
15
  uiprotect/cli/viewers.py,sha256=2cyrp104ffIvgT0wYGIO0G35QMkEbFe7fSVqLwDXQYQ,2171
16
16
  uiprotect/data/__init__.py,sha256=OcfuJl2qXfHcj_mdnrHhzZ5tEIZrw8auziX5IE7dn-I,2938
17
17
  uiprotect/data/base.py,sha256=kkPrRhvJV7igGyB3wv1fDvWH26Xzlbz94BjJkDiTvU4,38375
18
- uiprotect/data/bootstrap.py,sha256=JsbdZQAdlmLNg7dSv02pAhx1n3_wtFfLqSoj7kd8A-0,21296
19
- uiprotect/data/convert.py,sha256=rOQplUMIdTMD2SbAx_iI9BNPDscnhDvyRVLEMDhtADg,2047
18
+ uiprotect/data/bootstrap.py,sha256=INRUEbLWnsEJuzlcoFlm7RAT-WPx6odje3aD092QKGo,21465
19
+ uiprotect/data/convert.py,sha256=8h6Il_DhMkPRDPj9F_rA2UZIlTuchS3BQD24peKpk2A,2185
20
20
  uiprotect/data/devices.py,sha256=Nq3bOko5PFf5LvEBoD4JV8kmbq50laRdh3VHMWX7t-0,111809
21
21
  uiprotect/data/nvr.py,sha256=jwJEl4o2kbvoYFB9wSJPghNj0905847-86ElT51nNs0,47580
22
- uiprotect/data/types.py,sha256=6Z5ZqWTbH4Igy0l4QJShqQZ_zvrJKD0G-hZLjoBNP-U,16193
23
- uiprotect/data/user.py,sha256=Y7DnnxwUOJRJYw5FntztAtwN-o-Fs48SNS3mHi4Y5To,7071
22
+ uiprotect/data/types.py,sha256=1dj9jCk7btAa2hkiEo1Y5NoAFsQtOroCWT6KQKqG5eo,17213
23
+ uiprotect/data/user.py,sha256=Wb-ZWSwIJbyUbfVuENtUYbuW-uftHNDcoMH85dvEjkw,7071
24
24
  uiprotect/data/websocket.py,sha256=WZJVA7EfYuKYMv-9jmvGgMWXKzE9ES25SKv1NQ2eHjc,6281
25
25
  uiprotect/exceptions.py,sha256=kgn0cRM6lTtgLza09SDa3ZiX6ue1QqHCOogQ4qu6KTQ,965
26
26
  uiprotect/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -30,8 +30,8 @@ uiprotect/test_util/__init__.py,sha256=d2g7afa0LSdixQ0kjEDYwafDFME_UlW2LzxpamZ2B
30
30
  uiprotect/test_util/anonymize.py,sha256=f-8ijU-_y9r-uAbhIPn0f0I6hzJpAkvJzc8UpWihObI,8478
31
31
  uiprotect/utils.py,sha256=6OLY8hNiCzk418PjJJIlFW7jjPzVt1vxBKEzBSqMeTk,18418
32
32
  uiprotect/websocket.py,sha256=IzDPyqbzrkOMREvahN-e2zdvVD0VABSCWy6jSoCwOT0,7299
33
- uiprotect-1.2.2.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
- uiprotect-1.2.2.dist-info/METADATA,sha256=pU-rmbBF_iFXCpjNQkujk-qjuiJBH1zMDw5ALwnlhQ4,10984
35
- uiprotect-1.2.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
- uiprotect-1.2.2.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
- uiprotect-1.2.2.dist-info/RECORD,,
33
+ uiprotect-1.4.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
+ uiprotect-1.4.0.dist-info/METADATA,sha256=BZe2l7Ns5304fpwkOl7u7HuJA8AjTg9D0ilGahzubEs,10984
35
+ uiprotect-1.4.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
+ uiprotect-1.4.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
+ uiprotect-1.4.0.dist-info/RECORD,,