hookbridge 1.2.0__tar.gz → 1.3.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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(mypy hookbridge:*)",
5
+ "Bash(ruff check:*)"
6
+ ]
7
+ }
8
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hookbridge
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Official HookBridge SDK for Python
5
5
  Project-URL: Homepage, https://hookbridge.io
6
6
  Project-URL: Documentation, https://hookbridge.io/docs/sdks/python
@@ -44,16 +44,14 @@ from hookbridge.types import (
44
44
  MessageSummary,
45
45
  Metrics,
46
46
  MetricsWindow,
47
- NotificationPreferences,
48
47
  PauseState,
49
- Personalization,
50
48
  PortalSession,
51
49
  Project,
50
+ ReplayableMessageStatus,
52
51
  ReplayAllMessagesResponse,
53
52
  ReplayBatchMessagesResponse,
54
53
  ReplayBatchResult,
55
54
  ReplayMessageResponse,
56
- ReplayableMessageStatus,
57
55
  RotateSecretResponse,
58
56
  SendWebhookResponse,
59
57
  SigningKey,
@@ -67,7 +65,7 @@ from hookbridge.types import (
67
65
  UsageHistoryRow,
68
66
  )
69
67
 
70
- __version__ = "1.2.0"
68
+ __version__ = "1.3.0"
71
69
 
72
70
  __all__ = [
73
71
  "HookBridge",
@@ -101,9 +99,7 @@ __all__ = [
101
99
  "MessageSummary",
102
100
  "Metrics",
103
101
  "MetricsWindow",
104
- "NotificationPreferences",
105
102
  "PauseState",
106
- "Personalization",
107
103
  "PortalSession",
108
104
  "Project",
109
105
  "ReplayAllMessagesResponse",
@@ -51,16 +51,14 @@ from hookbridge.types import (
51
51
  MessageSummary,
52
52
  Metrics,
53
53
  MetricsWindow,
54
- NotificationPreferences,
55
54
  PauseState,
56
- Personalization,
57
55
  PortalSession,
58
56
  Project,
57
+ ReplayableMessageStatus,
59
58
  ReplayAllMessagesResponse,
60
59
  ReplayBatchMessagesResponse,
61
60
  ReplayBatchResult,
62
61
  ReplayMessageResponse,
63
- ReplayableMessageStatus,
64
62
  RotateSecretResponse,
65
63
  SendWebhookResponse,
66
64
  SigningKey,
@@ -95,9 +93,7 @@ def _query_string(params: dict[str, Any]) -> str:
95
93
  for key, value in params.items():
96
94
  if value is None:
97
95
  continue
98
- if isinstance(value, datetime):
99
- encoded[key] = value.isoformat()
100
- elif isinstance(value, date):
96
+ if isinstance(value, (datetime, date)):
101
97
  encoded[key] = value.isoformat()
102
98
  else:
103
99
  encoded[key] = str(value)
@@ -111,6 +107,14 @@ def _with_query(path: str, params: dict[str, Any]) -> str:
111
107
  return f"{path}?{query}"
112
108
 
113
109
 
110
+ def _json_value(value: Any) -> Any:
111
+ if isinstance(value, datetime):
112
+ return value.isoformat()
113
+ if isinstance(value, date):
114
+ return value.isoformat()
115
+ return value
116
+
117
+
114
118
  def _parse_message(data: dict[str, Any]) -> Message:
115
119
  return Message(
116
120
  id=data["id"],
@@ -294,31 +298,6 @@ def _parse_rotate_secret(data: dict[str, Any]) -> RotateSecretResponse:
294
298
  )
295
299
 
296
300
 
297
- def _parse_personalization(data: dict[str, Any]) -> Personalization:
298
- return Personalization(
299
- id=data["id"],
300
- user_id=data["user_id"],
301
- timezone=data["timezone"],
302
- date_format=data["date_format"],
303
- locale=data["locale"],
304
- created_at=_parse_datetime(data["created_at"]),
305
- updated_at=_parse_datetime(data["updated_at"]),
306
- )
307
-
308
-
309
- def _parse_notification_preferences(data: dict[str, Any]) -> NotificationPreferences:
310
- return NotificationPreferences(
311
- id=data["id"],
312
- user_id=data["user_id"],
313
- enabled_alert_types=data["enabled_alert_types"],
314
- delivery_channels=data["delivery_channels"],
315
- failure_rate_threshold=data["failure_rate_threshold"],
316
- pending_backlog_threshold=data["pending_backlog_threshold"],
317
- created_at=_parse_datetime(data["created_at"]),
318
- updated_at=_parse_datetime(data["updated_at"]),
319
- )
320
-
321
-
322
301
  def _parse_usage_history_row(data: dict[str, Any]) -> UsageHistoryRow:
323
302
  return UsageHistoryRow(
324
303
  period_start=_parse_date(data["period_start"]),
@@ -558,7 +537,9 @@ class HookBridge:
558
537
  return _parse_replay_all_messages(self._request("POST", path)["data"])
559
538
 
560
539
  def replay_batch_messages(self, message_ids: list[str]) -> ReplayBatchMessagesResponse:
561
- response = self._request("POST", "/v1/messages/replay-batch", json={"message_ids": message_ids})
540
+ response = self._request(
541
+ "POST", "/v1/messages/replay-batch", json={"message_ids": message_ids}
542
+ )
562
543
  return _parse_replay_batch_messages(response["data"])
563
544
 
564
545
  def get_logs(
@@ -605,7 +586,8 @@ class HookBridge:
605
586
  def get_dlq_messages(
606
587
  self, *, limit: Optional[int] = None, cursor: Optional[str] = None
607
588
  ) -> DLQMessagesResponse:
608
- response = self._request("GET", _with_query("/v1/dlq/messages", {"limit": limit, "cursor": cursor}))
589
+ path = _with_query("/v1/dlq/messages", {"limit": limit, "cursor": cursor})
590
+ response = self._request("GET", path)
609
591
  data = response["data"]
610
592
  return DLQMessagesResponse(
611
593
  messages=[_parse_message_summary(item) for item in data.get("messages", [])],
@@ -650,7 +632,11 @@ class HookBridge:
650
632
  return _parse_project(self._request("GET", f"/v1/projects/{project_id}")["data"])
651
633
 
652
634
  def update_project(
653
- self, project_id: str, *, name: Optional[str] = None, rate_limit_default: Optional[int] = None
635
+ self,
636
+ project_id: str,
637
+ *,
638
+ name: Optional[str] = None,
639
+ rate_limit_default: Optional[int] = None,
654
640
  ) -> Project:
655
641
  body: dict[str, Any] = {}
656
642
  if name is not None:
@@ -697,7 +683,8 @@ class HookBridge:
697
683
  def list_endpoints(
698
684
  self, *, limit: Optional[int] = None, cursor: Optional[str] = None
699
685
  ) -> ListEndpointsResponse:
700
- response = self._request("GET", _with_query("/v1/endpoints", {"limit": limit, "cursor": cursor}))
686
+ path = _with_query("/v1/endpoints", {"limit": limit, "cursor": cursor})
687
+ response = self._request("GET", path)
701
688
  meta = response.get("meta", {})
702
689
  next_cursor = meta.get("next_cursor")
703
690
  return ListEndpointsResponse(
@@ -746,55 +733,6 @@ class HookBridge:
746
733
  def rotate_endpoint_secret(self, endpoint_id: str) -> RotateSecretResponse:
747
734
  return self.create_endpoint_signing_key(endpoint_id)
748
735
 
749
- def get_personalization(self) -> Personalization:
750
- return _parse_personalization(
751
- self._request("GET", "/v1/settings/personalization")["data"]
752
- )
753
-
754
- def update_personalization(
755
- self,
756
- *,
757
- timezone: Optional[str] = None,
758
- date_format: Optional[str] = None,
759
- locale: Optional[str] = None,
760
- ) -> Personalization:
761
- body: dict[str, Any] = {}
762
- if timezone is not None:
763
- body["timezone"] = timezone
764
- if date_format is not None:
765
- body["date_format"] = date_format
766
- if locale is not None:
767
- body["locale"] = locale
768
- return _parse_personalization(
769
- self._request("PUT", "/v1/settings/personalization", json=body)["data"]
770
- )
771
-
772
- def get_notification_preferences(self) -> NotificationPreferences:
773
- return _parse_notification_preferences(
774
- self._request("GET", "/v1/settings/notifications")["data"]
775
- )
776
-
777
- def update_notification_preferences(
778
- self,
779
- *,
780
- enabled_alert_types: Optional[list[str]] = None,
781
- delivery_channels: Optional[list[str]] = None,
782
- failure_rate_threshold: Optional[int] = None,
783
- pending_backlog_threshold: Optional[int] = None,
784
- ) -> NotificationPreferences:
785
- body: dict[str, Any] = {}
786
- if enabled_alert_types is not None:
787
- body["enabled_alert_types"] = enabled_alert_types
788
- if delivery_channels is not None:
789
- body["delivery_channels"] = delivery_channels
790
- if failure_rate_threshold is not None:
791
- body["failure_rate_threshold"] = failure_rate_threshold
792
- if pending_backlog_threshold is not None:
793
- body["pending_backlog_threshold"] = pending_backlog_threshold
794
- return _parse_notification_preferences(
795
- self._request("PUT", "/v1/settings/notifications", json=body)["data"]
796
- )
797
-
798
736
  def create_checkout(self, *, plan: str, interval: str) -> CheckoutSession:
799
737
  data = self._request(
800
738
  "POST", "/v1/billing/checkout", json={"plan": plan, "interval": interval}
@@ -1038,7 +976,10 @@ class HookBridge:
1038
976
  status: Optional[MessageStatus] = None,
1039
977
  endpoint_id: Optional[str] = None,
1040
978
  ) -> ExportRecord:
1041
- body: dict[str, Any] = {"start_time": start_time, "end_time": end_time}
979
+ body: dict[str, Any] = {
980
+ "start_time": _json_value(start_time),
981
+ "end_time": _json_value(end_time),
982
+ }
1042
983
  if status is not None:
1043
984
  body["status"] = status
1044
985
  if endpoint_id is not None:
@@ -1243,7 +1184,8 @@ class AsyncHookBridge:
1243
1184
  await self._request("POST", f"/v1/dlq/replay/{message_id}")
1244
1185
 
1245
1186
  async def list_api_keys(self) -> list[APIKeyInfo]:
1246
- return [_parse_api_key_info(item) for item in (await self._request("GET", "/v1/api-keys"))["data"]]
1187
+ response = await self._request("GET", "/v1/api-keys")
1188
+ return [_parse_api_key_info(item) for item in response["data"]]
1247
1189
 
1248
1190
  async def create_api_key(
1249
1191
  self, mode: APIKeyMode, *, label: Optional[str] = None
@@ -1264,9 +1206,12 @@ class AsyncHookBridge:
1264
1206
  await self._request("DELETE", f"/v1/api-keys/{key_id}")
1265
1207
 
1266
1208
  async def list_projects(self) -> list[Project]:
1267
- return [_parse_project(item) for item in (await self._request("GET", "/v1/projects"))["data"]]
1209
+ response = await self._request("GET", "/v1/projects")
1210
+ return [_parse_project(item) for item in response["data"]]
1268
1211
 
1269
- async def create_project(self, name: str, *, rate_limit_default: Optional[int] = None) -> Project:
1212
+ async def create_project(
1213
+ self, name: str, *, rate_limit_default: Optional[int] = None
1214
+ ) -> Project:
1270
1215
  body: dict[str, Any] = {"name": name}
1271
1216
  if rate_limit_default is not None:
1272
1217
  body["rate_limit_default"] = rate_limit_default
@@ -1276,14 +1221,19 @@ class AsyncHookBridge:
1276
1221
  return _parse_project((await self._request("GET", f"/v1/projects/{project_id}"))["data"])
1277
1222
 
1278
1223
  async def update_project(
1279
- self, project_id: str, *, name: Optional[str] = None, rate_limit_default: Optional[int] = None
1224
+ self,
1225
+ project_id: str,
1226
+ *,
1227
+ name: Optional[str] = None,
1228
+ rate_limit_default: Optional[int] = None,
1280
1229
  ) -> Project:
1281
1230
  body: dict[str, Any] = {}
1282
1231
  if name is not None:
1283
1232
  body["name"] = name
1284
1233
  if rate_limit_default is not None:
1285
1234
  body["rate_limit_default"] = rate_limit_default
1286
- return _parse_project((await self._request("PUT", f"/v1/projects/{project_id}", json=body))["data"])
1235
+ response = await self._request("PUT", f"/v1/projects/{project_id}", json=body)
1236
+ return _parse_project(response["data"])
1287
1237
 
1288
1238
  async def delete_project(self, project_id: str) -> None:
1289
1239
  await self._request("DELETE", f"/v1/projects/{project_id}")
@@ -1374,55 +1324,6 @@ class AsyncHookBridge:
1374
1324
  async def rotate_endpoint_secret(self, endpoint_id: str) -> RotateSecretResponse:
1375
1325
  return await self.create_endpoint_signing_key(endpoint_id)
1376
1326
 
1377
- async def get_personalization(self) -> Personalization:
1378
- return _parse_personalization(
1379
- (await self._request("GET", "/v1/settings/personalization"))["data"]
1380
- )
1381
-
1382
- async def update_personalization(
1383
- self,
1384
- *,
1385
- timezone: Optional[str] = None,
1386
- date_format: Optional[str] = None,
1387
- locale: Optional[str] = None,
1388
- ) -> Personalization:
1389
- body: dict[str, Any] = {}
1390
- if timezone is not None:
1391
- body["timezone"] = timezone
1392
- if date_format is not None:
1393
- body["date_format"] = date_format
1394
- if locale is not None:
1395
- body["locale"] = locale
1396
- return _parse_personalization(
1397
- (await self._request("PUT", "/v1/settings/personalization", json=body))["data"]
1398
- )
1399
-
1400
- async def get_notification_preferences(self) -> NotificationPreferences:
1401
- return _parse_notification_preferences(
1402
- (await self._request("GET", "/v1/settings/notifications"))["data"]
1403
- )
1404
-
1405
- async def update_notification_preferences(
1406
- self,
1407
- *,
1408
- enabled_alert_types: Optional[list[str]] = None,
1409
- delivery_channels: Optional[list[str]] = None,
1410
- failure_rate_threshold: Optional[int] = None,
1411
- pending_backlog_threshold: Optional[int] = None,
1412
- ) -> NotificationPreferences:
1413
- body: dict[str, Any] = {}
1414
- if enabled_alert_types is not None:
1415
- body["enabled_alert_types"] = enabled_alert_types
1416
- if delivery_channels is not None:
1417
- body["delivery_channels"] = delivery_channels
1418
- if failure_rate_threshold is not None:
1419
- body["failure_rate_threshold"] = failure_rate_threshold
1420
- if pending_backlog_threshold is not None:
1421
- body["pending_backlog_threshold"] = pending_backlog_threshold
1422
- return _parse_notification_preferences(
1423
- (await self._request("PUT", "/v1/settings/notifications", json=body))["data"]
1424
- )
1425
-
1426
1327
  async def create_checkout(self, *, plan: str, interval: str) -> CheckoutSession:
1427
1328
  data = (
1428
1329
  await self._request(
@@ -1674,7 +1575,10 @@ class AsyncHookBridge:
1674
1575
  status: Optional[MessageStatus] = None,
1675
1576
  endpoint_id: Optional[str] = None,
1676
1577
  ) -> ExportRecord:
1677
- body: dict[str, Any] = {"start_time": start_time, "end_time": end_time}
1578
+ body: dict[str, Any] = {
1579
+ "start_time": _json_value(start_time),
1580
+ "end_time": _json_value(end_time),
1581
+ }
1678
1582
  if status is not None:
1679
1583
  body["status"] = status
1680
1584
  if endpoint_id is not None:
@@ -1682,10 +1586,12 @@ class AsyncHookBridge:
1682
1586
  return _parse_export_record((await self._request("POST", "/v1/exports", json=body))["data"])
1683
1587
 
1684
1588
  async def list_exports(self) -> list[ExportRecord]:
1685
- return [_parse_export_record(item) for item in (await self._request("GET", "/v1/exports"))["data"]]
1589
+ response = await self._request("GET", "/v1/exports")
1590
+ return [_parse_export_record(item) for item in response["data"]]
1686
1591
 
1687
1592
  async def get_export(self, export_id: str) -> ExportRecord:
1688
- return _parse_export_record((await self._request("GET", f"/v1/exports/{export_id}"))["data"])
1593
+ response = await self._request("GET", f"/v1/exports/{export_id}")
1594
+ return _parse_export_record(response["data"])
1689
1595
 
1690
1596
  async def download_export(self, export_id: str) -> str:
1691
1597
  response = await self._request(
@@ -243,29 +243,6 @@ class Project:
243
243
  created_at: datetime
244
244
 
245
245
 
246
- @dataclass(frozen=True)
247
- class Personalization:
248
- id: str
249
- user_id: str
250
- timezone: str
251
- date_format: str
252
- locale: str
253
- created_at: datetime
254
- updated_at: datetime
255
-
256
-
257
- @dataclass(frozen=True)
258
- class NotificationPreferences:
259
- id: str
260
- user_id: str
261
- enabled_alert_types: list[str]
262
- delivery_channels: list[str]
263
- failure_rate_threshold: int
264
- pending_backlog_threshold: int
265
- created_at: datetime
266
- updated_at: datetime
267
-
268
-
269
246
  @dataclass(frozen=True)
270
247
  class CheckoutSession:
271
248
  session_id: str
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hookbridge"
7
- version = "1.2.0"
7
+ version = "1.3.0"
8
8
  description = "Official HookBridge SDK for Python"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -2,6 +2,9 @@
2
2
  Tests for the asynchronous HookBridge client.
3
3
  """
4
4
 
5
+ import json
6
+ from datetime import UTC, datetime
7
+
5
8
  import pytest
6
9
  from pytest_httpx import HTTPXMock
7
10
 
@@ -305,6 +308,42 @@ class TestAsyncEndpoints:
305
308
  assert result.id == endpoint_id
306
309
  assert result.rate_limit_rps == 10
307
310
 
311
+
312
+ class TestAsyncExports:
313
+ """Tests for async export operations."""
314
+
315
+ @pytest.mark.skipif(LIVE_MODE, reason="Mock test only")
316
+ async def test_create_export_serializes_datetimes(
317
+ self, async_client: AsyncHookBridge, httpx_mock: HTTPXMock
318
+ ) -> None:
319
+ httpx_mock.add_response(
320
+ method="POST",
321
+ url=f"{async_client._base_url}/v1/exports",
322
+ json={
323
+ "data": {
324
+ "id": "01935abc-def0-7123-4567-890abcdef077",
325
+ "project_id": "proj_abc123",
326
+ "status": "pending",
327
+ "filter_start_time": "2025-12-01T00:00:00+00:00",
328
+ "filter_end_time": "2025-12-06T23:59:59+00:00",
329
+ "created_at": "2025-12-06T12:00:00Z",
330
+ },
331
+ "meta": {"request_id": "req-12345"},
332
+ },
333
+ )
334
+
335
+ start = datetime(2025, 12, 1, 0, 0, 0, tzinfo=UTC)
336
+ end = datetime(2025, 12, 6, 23, 59, 59, tzinfo=UTC)
337
+ await async_client.create_export(start_time=start, end_time=end, endpoint_id="ep_123")
338
+
339
+ request = httpx_mock.get_request()
340
+ assert request is not None
341
+ assert json.loads(request.content) == {
342
+ "start_time": "2025-12-01T00:00:00+00:00",
343
+ "end_time": "2025-12-06T23:59:59+00:00",
344
+ "endpoint_id": "ep_123",
345
+ }
346
+
308
347
  @pytest.mark.skipif(LIVE_MODE, reason="Mock test only")
309
348
  async def test_list_endpoints(
310
349
  self, async_client: AsyncHookBridge, httpx_mock: HTTPXMock
@@ -3,6 +3,7 @@ Spec parity tests for the synchronous HookBridge client.
3
3
  """
4
4
 
5
5
  import json
6
+ from datetime import UTC, datetime
6
7
 
7
8
  import pytest
8
9
  from pytest_httpx import HTTPXMock
@@ -11,7 +12,6 @@ from hookbridge import HookBridge
11
12
 
12
13
  from .conftest import LIVE_MODE
13
14
 
14
-
15
15
  PROJECT_ID = "01935abc-def0-7123-4567-890abcdef012"
16
16
  INBOUND_ENDPOINT_ID = "01935abc-def0-7123-4567-890abcdef099"
17
17
  MESSAGE_ID = "01935abc-def0-7123-4567-890abcdef013"
@@ -236,71 +236,6 @@ class TestSigningKeyParity:
236
236
  client.delete_endpoint_signing_key(ENDPOINT_ID, SIGNING_KEY_ID)
237
237
 
238
238
 
239
- class TestSettingsParity:
240
- @pytest.mark.skipif(LIVE_MODE, reason="Mock test only")
241
- def test_personalization_settings(self, client: HookBridge, httpx_mock: HTTPXMock) -> None:
242
- payload = {
243
- "data": {
244
- "id": "550e8400-e29b-41d4-a716-446655440000",
245
- "user_id": "user_abc123",
246
- "timezone": "America/New_York",
247
- "date_format": "MM/DD/YYYY",
248
- "locale": "en-US",
249
- "created_at": "2025-12-01T10:00:00Z",
250
- "updated_at": "2025-12-06T12:00:00Z",
251
- },
252
- "meta": {"request_id": "req-12345"},
253
- }
254
- httpx_mock.add_response(
255
- method="GET",
256
- url=f"{client._base_url}/v1/settings/personalization",
257
- json=payload,
258
- )
259
- httpx_mock.add_response(
260
- method="PUT",
261
- url=f"{client._base_url}/v1/settings/personalization",
262
- json=payload,
263
- )
264
-
265
- current = client.get_personalization()
266
- updated = client.update_personalization(timezone="America/New_York")
267
-
268
- assert current.locale == "en-US"
269
- assert updated.timezone == "America/New_York"
270
-
271
- @pytest.mark.skipif(LIVE_MODE, reason="Mock test only")
272
- def test_notification_settings(self, client: HookBridge, httpx_mock: HTTPXMock) -> None:
273
- payload = {
274
- "data": {
275
- "id": "550e8400-e29b-41d4-a716-446655440001",
276
- "user_id": "user_abc123",
277
- "enabled_alert_types": ["high_failure_rate"],
278
- "delivery_channels": ["email"],
279
- "failure_rate_threshold": 10,
280
- "pending_backlog_threshold": 100,
281
- "created_at": "2025-12-01T10:00:00Z",
282
- "updated_at": "2025-12-06T12:00:00Z",
283
- },
284
- "meta": {"request_id": "req-12345"},
285
- }
286
- httpx_mock.add_response(
287
- method="GET",
288
- url=f"{client._base_url}/v1/settings/notifications",
289
- json=payload,
290
- )
291
- httpx_mock.add_response(
292
- method="PUT",
293
- url=f"{client._base_url}/v1/settings/notifications",
294
- json=payload,
295
- )
296
-
297
- current = client.get_notification_preferences()
298
- updated = client.update_notification_preferences(delivery_channels=["email"])
299
-
300
- assert current.enabled_alert_types == ["high_failure_rate"]
301
- assert updated.pending_backlog_threshold == 100
302
-
303
-
304
239
  class TestBillingParity:
305
240
  @pytest.mark.skipif(LIVE_MODE, reason="Mock test only")
306
241
  def test_checkout_and_portal(self, client: HookBridge, httpx_mock: HTTPXMock) -> None:
@@ -700,3 +635,35 @@ class TestExportParity:
700
635
  assert listed[0].row_count == 5000
701
636
  assert fetched.status == "completed"
702
637
  assert download_url == "https://downloads.example.com/export.csv"
638
+
639
+ @pytest.mark.skipif(LIVE_MODE, reason="Mock test only")
640
+ def test_create_export_serializes_datetimes(
641
+ self, client: HookBridge, httpx_mock: HTTPXMock
642
+ ) -> None:
643
+ httpx_mock.add_response(
644
+ method="POST",
645
+ url=f"{client._base_url}/v1/exports",
646
+ json={
647
+ "data": {
648
+ "id": EXPORT_ID,
649
+ "project_id": "proj_abc123",
650
+ "status": "pending",
651
+ "filter_start_time": "2025-12-01T00:00:00+00:00",
652
+ "filter_end_time": "2025-12-06T23:59:59+00:00",
653
+ "created_at": "2025-12-06T12:00:00Z",
654
+ },
655
+ "meta": {"request_id": "req-12345"},
656
+ },
657
+ )
658
+
659
+ start = datetime(2025, 12, 1, 0, 0, 0, tzinfo=UTC)
660
+ end = datetime(2025, 12, 6, 23, 59, 59, tzinfo=UTC)
661
+ client.create_export(start_time=start, end_time=end, endpoint_id=ENDPOINT_ID)
662
+
663
+ request = httpx_mock.get_request()
664
+ assert request is not None
665
+ assert json.loads(request.content) == {
666
+ "start_time": "2025-12-01T00:00:00+00:00",
667
+ "end_time": "2025-12-06T23:59:59+00:00",
668
+ "endpoint_id": ENDPOINT_ID,
669
+ }
@@ -1,7 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(mypy hookbridge:*)"
5
- ]
6
- }
7
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes