memorysync 1.1.0__tar.gz → 1.1.1__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.4
2
2
  Name: memorysync
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: Official Python client for the MemorySync API.
5
5
  Project-URL: Homepage, https://memorysync.io
6
6
  Project-URL: Documentation, https://docs.memorysync.io
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "memorysync"
7
- version = "1.1.0"
7
+ version = "1.1.1"
8
8
  description = "Official Python client for the MemorySync API."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -0,0 +1 @@
1
+ __version__ = "1.1.1"
@@ -60,14 +60,45 @@ def _snake_key(key: str) -> str:
60
60
  return re.sub(r"(?<!^)(?=[A-Z])", "_", key).lower()
61
61
 
62
62
 
63
+ _OPAQUE_RESPONSE_KEYS = {"payload", "metadata", "geo", "configuration"}
64
+
65
+
63
66
  def _snake(value: Any) -> Any:
64
67
  if isinstance(value, dict):
65
- return {_snake_key(str(key)): _snake(item) for key, item in value.items()}
68
+ normalized: Dict[str, Any] = {}
69
+ for key, item in value.items():
70
+ snake_key = _snake_key(str(key))
71
+ normalized[snake_key] = item if snake_key in _OPAQUE_RESPONSE_KEYS else _snake(item)
72
+ return normalized
66
73
  if isinstance(value, list):
67
74
  return [_snake(item) for item in value]
68
75
  return value
69
76
 
70
77
 
78
+ def _project_override(value: Optional[str]) -> object:
79
+ if value is None:
80
+ return _UNSET
81
+ return cast(str, _project(value))
82
+
83
+
84
+ def _webhook_url(value: str) -> None:
85
+ _required_text(value, "url")
86
+ parsed = urlparse(value)
87
+ if parsed.scheme not in ("http", "https") or not parsed.netloc:
88
+ raise ValidationError("url must be an absolute HTTP or HTTPS URL")
89
+ if parsed.username or parsed.password:
90
+ raise ValidationError("url must not contain credentials")
91
+
92
+
93
+ def _string_sequence(values: Sequence[str], name: str) -> None:
94
+ if (
95
+ isinstance(values, (str, bytes))
96
+ or not values
97
+ or any(not isinstance(value, str) or not value.strip() for value in values)
98
+ ):
99
+ raise ValidationError(f"{name} must contain at least one non-empty string")
100
+
101
+
71
102
  def _detail(body: Any) -> Optional[str]:
72
103
  if not isinstance(body, dict):
73
104
  return None
@@ -156,7 +187,7 @@ class _ControlPlaneBase:
156
187
  raw: Any = response.json()
157
188
  except ValueError:
158
189
  raw = response.text or None
159
- if response.status_code >= 400:
190
+ if not 200 <= response.status_code < 300:
160
191
  _raise_response(response, raw)
161
192
  return _snake(raw)
162
193
 
@@ -165,12 +196,8 @@ def _validate_webhook(name: str, url: str, events: Sequence[str]) -> None:
165
196
  _required_text(name, "name")
166
197
  if len(name) > 128:
167
198
  raise ValidationError("name may contain at most 128 characters")
168
- _required_text(url, "url")
169
- parsed = urlparse(url)
170
- if parsed.scheme not in ("http", "https") or not parsed.netloc:
171
- raise ValidationError("url must be an absolute HTTP or HTTPS URL")
172
- if not events or any(not isinstance(event, str) or not event.strip() for event in events):
173
- raise ValidationError("events must contain at least one non-empty string")
199
+ _webhook_url(url)
200
+ _string_sequence(events, "events")
174
201
 
175
202
 
176
203
  class ControlPlaneClient(_ControlPlaneBase):
@@ -218,11 +245,11 @@ class ControlPlaneClient(_ControlPlaneBase):
218
245
  raise ValidationError("key_ids must contain between 1 and 100 entries")
219
246
  for key_id in ids:
220
247
  _positive_id(key_id, "key_id")
221
- raw = self._request("POST", "/org/api-keys/bulk-revoke", json={"key_ids": ids}, project_id=project_id or _UNSET)
248
+ raw = self._request("POST", "/org/api-keys/bulk-revoke", json={"key_ids": ids}, project_id=_project_override(project_id))
222
249
  return cast(BulkRevokeResponse, raw)
223
250
 
224
251
  def test_api_key(self, key_id: int, *, project_id: Optional[str] = None) -> ApiKeyTestResponse:
225
- raw = self._request("POST", f"/org/api-keys/{_positive_id(key_id, 'key_id')}/test", project_id=project_id or _UNSET)
252
+ raw = self._request("POST", f"/org/api-keys/{_positive_id(key_id, 'key_id')}/test", project_id=_project_override(project_id))
226
253
  return cast(ApiKeyTestResponse, raw)
227
254
 
228
255
  def login(self, email: str, password: str) -> LoginResponse:
@@ -322,14 +349,13 @@ class ControlPlaneClient(_ControlPlaneBase):
322
349
  _validate_webhook(name, url, events)
323
350
  if description is not None and len(description) > 500:
324
351
  raise ValidationError("description may contain at most 500 characters")
325
- selected = project_id or self._project_id
326
352
  data = _body(name=name, url=url, events=list(events), description=description,
327
353
  retry_config=retry_config, signature_config=signature_config,
328
354
  project_id=project_id)
329
- return cast(Webhook, self._request("POST", "/org/webhooks", json=data, project_id=selected))
355
+ return cast(Webhook, self._request("POST", "/org/webhooks", json=data, project_id=_project_override(project_id)))
330
356
 
331
357
  def list_webhooks(self, *, project_id: Optional[str] = None) -> WebhooksResponse:
332
- return cast(WebhooksResponse, self._request("GET", "/org/webhooks", project_id=project_id or _UNSET))
358
+ return cast(WebhooksResponse, self._request("GET", "/org/webhooks", project_id=_project_override(project_id)))
333
359
 
334
360
 
335
361
  def update_webhook(
@@ -345,24 +371,22 @@ class ControlPlaneClient(_ControlPlaneBase):
345
371
  if len(name) > 128:
346
372
  raise ValidationError("name may contain at most 128 characters")
347
373
  if url is not None:
348
- parsed = urlparse(url)
349
- if parsed.scheme not in ("http", "https") or not parsed.netloc:
350
- raise ValidationError("url must be an absolute HTTP or HTTPS URL")
374
+ _webhook_url(url)
351
375
  if description is not None and len(description) > 500:
352
376
  raise ValidationError("description may contain at most 500 characters")
353
- if events is not None and (not events or any(not event.strip() for event in events)):
354
- raise ValidationError("events must contain at least one non-empty string")
377
+ if events is not None:
378
+ _string_sequence(events, "events")
355
379
  data = _body(name=name, url=url, description=description,
356
380
  events=list(events) if events is not None else None,
357
381
  retry_config=retry_config, signature_config=signature_config)
358
382
  if not data:
359
383
  raise ValidationError("update_webhook requires at least one editable field")
360
384
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
361
- return cast(Webhook, self._request("PATCH", path, json=data, project_id=project_id or _UNSET))
385
+ return cast(Webhook, self._request("PATCH", path, json=data, project_id=_project_override(project_id)))
362
386
 
363
387
  def delete_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> None:
364
388
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
365
- self._request("DELETE", path, project_id=project_id or _UNSET)
389
+ self._request("DELETE", path, project_id=_project_override(project_id))
366
390
 
367
391
  def test_webhook(
368
392
  self, endpoint_id: int, *, event_type: str = "memory.created",
@@ -370,7 +394,7 @@ class ControlPlaneClient(_ControlPlaneBase):
370
394
  ) -> WebhookTestResponse:
371
395
  _required_text(event_type, "event_type")
372
396
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/test"
373
- return cast(WebhookTestResponse, self._request("POST", path, json={"event_type": event_type}, project_id=project_id or _UNSET))
397
+ return cast(WebhookTestResponse, self._request("POST", path, json={"event_type": event_type}, project_id=_project_override(project_id)))
374
398
 
375
399
  def replay_webhook_deliveries(
376
400
  self, endpoint_id: int, *, since_minutes: int = 60,
@@ -379,11 +403,10 @@ class ControlPlaneClient(_ControlPlaneBase):
379
403
  ) -> WebhookReplayResponse:
380
404
  _bounded(since_minutes, "since_minutes", 1, 10080)
381
405
  _bounded(limit, "limit", 1, 1000)
382
- if not statuses or any(not isinstance(status, str) or not status.strip() for status in statuses):
383
- raise ValidationError("statuses must contain at least one non-empty string")
406
+ _string_sequence(statuses, "statuses")
384
407
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/replay"
385
408
  data = {"since_minutes": since_minutes, "statuses": list(statuses), "limit": limit}
386
- return cast(WebhookReplayResponse, self._request("POST", path, json=data, project_id=project_id or _UNSET))
409
+ return cast(WebhookReplayResponse, self._request("POST", path, json=data, project_id=_project_override(project_id)))
387
410
 
388
411
  def list_webhook_deliveries(
389
412
  self, endpoint_id: int, *, page: int = 1, page_size: int = 25,
@@ -395,7 +418,7 @@ class ControlPlaneClient(_ControlPlaneBase):
395
418
  _required_text(status, "status")
396
419
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/deliveries"
397
420
  params = _body(page=page, page_size=page_size, status_filter=status)
398
- return cast(WebhookDeliveriesResponse, self._request("GET", path, params=params, project_id=project_id or _UNSET))
421
+ return cast(WebhookDeliveriesResponse, self._request("GET", path, params=params, project_id=_project_override(project_id)))
399
422
 
400
423
 
401
424
  class AsyncControlPlaneClient(_ControlPlaneBase):
@@ -441,11 +464,11 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
441
464
  raise ValidationError("key_ids must contain between 1 and 100 entries")
442
465
  for key_id in ids:
443
466
  _positive_id(key_id, "key_id")
444
- raw = await self._request("POST", "/org/api-keys/bulk-revoke", json={"key_ids": ids}, project_id=project_id or _UNSET)
467
+ raw = await self._request("POST", "/org/api-keys/bulk-revoke", json={"key_ids": ids}, project_id=_project_override(project_id))
445
468
  return cast(BulkRevokeResponse, raw)
446
469
 
447
470
  async def test_api_key(self, key_id: int, *, project_id: Optional[str] = None) -> ApiKeyTestResponse:
448
- raw = await self._request("POST", f"/org/api-keys/{_positive_id(key_id, 'key_id')}/test", project_id=project_id or _UNSET)
471
+ raw = await self._request("POST", f"/org/api-keys/{_positive_id(key_id, 'key_id')}/test", project_id=_project_override(project_id))
449
472
  return cast(ApiKeyTestResponse, raw)
450
473
 
451
474
  async def login(self, email: str, password: str) -> LoginResponse:
@@ -548,15 +571,14 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
548
571
  _validate_webhook(name, url, events)
549
572
  if description is not None and len(description) > 500:
550
573
  raise ValidationError("description may contain at most 500 characters")
551
- selected = project_id or self._project_id
552
574
  data = _body(name=name, url=url, events=list(events), description=description,
553
575
  retry_config=retry_config, signature_config=signature_config,
554
576
  project_id=project_id)
555
- raw = await self._request("POST", "/org/webhooks", json=data, project_id=selected)
577
+ raw = await self._request("POST", "/org/webhooks", json=data, project_id=_project_override(project_id))
556
578
  return cast(Webhook, raw)
557
579
 
558
580
  async def list_webhooks(self, *, project_id: Optional[str] = None) -> WebhooksResponse:
559
- return cast(WebhooksResponse, await self._request("GET", "/org/webhooks", project_id=project_id or _UNSET))
581
+ return cast(WebhooksResponse, await self._request("GET", "/org/webhooks", project_id=_project_override(project_id)))
560
582
 
561
583
 
562
584
  async def update_webhook(
@@ -572,25 +594,23 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
572
594
  if len(name) > 128:
573
595
  raise ValidationError("name may contain at most 128 characters")
574
596
  if url is not None:
575
- parsed = urlparse(url)
576
- if parsed.scheme not in ("http", "https") or not parsed.netloc:
577
- raise ValidationError("url must be an absolute HTTP or HTTPS URL")
597
+ _webhook_url(url)
578
598
  if description is not None and len(description) > 500:
579
599
  raise ValidationError("description may contain at most 500 characters")
580
- if events is not None and (not events or any(not event.strip() for event in events)):
581
- raise ValidationError("events must contain at least one non-empty string")
600
+ if events is not None:
601
+ _string_sequence(events, "events")
582
602
  data = _body(name=name, url=url, description=description,
583
603
  events=list(events) if events is not None else None,
584
604
  retry_config=retry_config, signature_config=signature_config)
585
605
  if not data:
586
606
  raise ValidationError("update_webhook requires at least one editable field")
587
607
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
588
- raw = await self._request("PATCH", path, json=data, project_id=project_id or _UNSET)
608
+ raw = await self._request("PATCH", path, json=data, project_id=_project_override(project_id))
589
609
  return cast(Webhook, raw)
590
610
 
591
611
  async def delete_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> None:
592
612
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
593
- await self._request("DELETE", path, project_id=project_id or _UNSET)
613
+ await self._request("DELETE", path, project_id=_project_override(project_id))
594
614
 
595
615
  async def test_webhook(
596
616
  self, endpoint_id: int, *, event_type: str = "memory.created",
@@ -598,7 +618,7 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
598
618
  ) -> WebhookTestResponse:
599
619
  _required_text(event_type, "event_type")
600
620
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/test"
601
- raw = await self._request("POST", path, json={"event_type": event_type}, project_id=project_id or _UNSET)
621
+ raw = await self._request("POST", path, json={"event_type": event_type}, project_id=_project_override(project_id))
602
622
  return cast(WebhookTestResponse, raw)
603
623
 
604
624
  async def replay_webhook_deliveries(
@@ -608,11 +628,10 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
608
628
  ) -> WebhookReplayResponse:
609
629
  _bounded(since_minutes, "since_minutes", 1, 10080)
610
630
  _bounded(limit, "limit", 1, 1000)
611
- if not statuses or any(not isinstance(status, str) or not status.strip() for status in statuses):
612
- raise ValidationError("statuses must contain at least one non-empty string")
631
+ _string_sequence(statuses, "statuses")
613
632
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/replay"
614
633
  data = {"since_minutes": since_minutes, "statuses": list(statuses), "limit": limit}
615
- raw = await self._request("POST", path, json=data, project_id=project_id or _UNSET)
634
+ raw = await self._request("POST", path, json=data, project_id=_project_override(project_id))
616
635
  return cast(WebhookReplayResponse, raw)
617
636
 
618
637
  async def list_webhook_deliveries(
@@ -625,5 +644,5 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
625
644
  _required_text(status, "status")
626
645
  path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/deliveries"
627
646
  params = _body(page=page, page_size=page_size, status_filter=status)
628
- raw = await self._request("GET", path, params=params, project_id=project_id or _UNSET)
647
+ raw = await self._request("GET", path, params=params, project_id=_project_override(project_id))
629
648
  return cast(WebhookDeliveriesResponse, raw)
@@ -1 +0,0 @@
1
- __version__ = "1.1.0"
File without changes
File without changes
File without changes