Skip to content

Commit af22eff

Browse files
Navigator alerts
1 parent 2e88a6b commit af22eff

9 files changed

Lines changed: 209 additions & 3 deletions

File tree

‎linkedapi/__init__.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
NvFetchPerson,
5454
NvFetchPersonMapper,
5555
NvManageConversation,
56+
NvRetrieveAlerts,
5657
NvSearchCompanies,
5758
NvSearchPeople,
5859
NvSendMessage,
@@ -89,7 +90,7 @@
8990
verify_webhook_signature,
9091
)
9192

92-
__version__ = "1.4.8"
93+
__version__ = "1.4.9"
9394
PredefinedOperation = Operation
9495

9596
__all__ = [
@@ -137,6 +138,7 @@
137138
"NvFetchPerson",
138139
"NvFetchPersonMapper",
139140
"NvManageConversation",
141+
"NvRetrieveAlerts",
140142
"NvSearchCompanies",
141143
"NvSearchPeople",
142144
"NvSendMessage",

‎linkedapi/client.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
NvFetchCompany,
2626
NvFetchPerson,
2727
NvManageConversation,
28+
NvRetrieveAlerts,
2829
NvSearchCompanies,
2930
NvSearchPeople,
3031
NvSendMessage,
@@ -114,6 +115,7 @@ def __init__(self, config: LinkedApiConfig | HttpClient[Any]) -> None:
114115
self.nv_sync_conversation = NvSyncConversation(self.http_client)
115116
self.nv_sync_inbox = NvSyncInbox(self.http_client)
116117
self.nv_manage_conversation = NvManageConversation(self.http_client)
118+
self.nv_retrieve_alerts = NvRetrieveAlerts(self.http_client)
117119
self.nv_search_companies = NvSearchCompanies(self.http_client)
118120
self.nv_search_people = NvSearchPeople(self.http_client)
119121
self.nv_fetch_company = NvFetchCompany(self.http_client)
@@ -159,6 +161,7 @@ def __init__(self, config: LinkedApiConfig | HttpClient[Any]) -> None:
159161
self.nv_sync_conversation,
160162
self.nv_sync_inbox,
161163
self.nv_manage_conversation,
164+
self.nv_retrieve_alerts,
162165
self.nv_search_companies,
163166
self.nv_search_people,
164167
self.nv_fetch_company,

‎linkedapi/operations/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from linkedapi.operations.nv_fetch_company import NvFetchCompany, NvFetchCompanyMapper
1616
from linkedapi.operations.nv_fetch_person import NvFetchPerson, NvFetchPersonMapper
1717
from linkedapi.operations.nv_manage_conversation import NvManageConversation
18+
from linkedapi.operations.nv_retrieve_alerts import NvRetrieveAlerts
1819
from linkedapi.operations.nv_search_companies import NvSearchCompanies
1920
from linkedapi.operations.nv_search_people import NvSearchPeople
2021
from linkedapi.operations.nv_send_message import NvSendMessage
@@ -66,6 +67,7 @@
6667
"NvFetchPerson",
6768
"NvFetchPersonMapper",
6869
"NvManageConversation",
70+
"NvRetrieveAlerts",
6971
"NvSearchCompanies",
7072
"NvSearchPeople",
7173
"NvSendMessage",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from __future__ import annotations
2+
3+
from linkedapi.core import Operation
4+
from linkedapi.mappers import ArrayWorkflowMapper
5+
from linkedapi.types import NvAlert, NvRetrieveAlertsParams
6+
7+
8+
class NvRetrieveAlerts(Operation[NvRetrieveAlertsParams, list[NvAlert]]):
9+
"""Retrieve Sales Navigator lead and account alerts."""
10+
11+
operation_name = "nvRetrieveAlerts"
12+
mapper = ArrayWorkflowMapper[NvRetrieveAlertsParams, NvAlert]("nv.retrieveAlerts", NvAlert)

‎linkedapi/types/__init__.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@
137137
NetworkPollRequest,
138138
SyncNetworkParams,
139139
)
140+
from linkedapi.types.nv_alerts import (
141+
NvAlert,
142+
NvAlertSubject,
143+
NvRetrieveAlertsFilter,
144+
NvRetrieveAlertsParams,
145+
)
140146
from linkedapi.types.params import BaseActionParams, LimitParams, LimitSinceParams
141147
from linkedapi.types.person import (
142148
BaseFetchPersonParams,
@@ -410,6 +416,8 @@
410416
"NetworkPollRequest",
411417
"NetworkWebhookEvent",
412418
"NetworkWebhookEventData",
419+
"NvAlert",
420+
"NvAlertSubject",
413421
"NvBaseFetchCompanyParams",
414422
"NvBaseFetchCompanyParamsWide",
415423
"NvCompany",
@@ -423,6 +431,8 @@
423431
"NvManageConversationParams",
424432
"NvOpenPersonPageParams",
425433
"NvOpenPersonPageResult",
434+
"NvRetrieveAlertsFilter",
435+
"NvRetrieveAlertsParams",
426436
"NvSearchCompaniesFilter",
427437
"NvSearchCompaniesParams",
428438
"NvSearchCompanyResult",

‎linkedapi/types/nv_alerts.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from __future__ import annotations
2+
3+
from typing import Literal
4+
5+
from pydantic import Field
6+
7+
from linkedapi.types.base import LinkedApiModel
8+
from linkedapi.types.params import BaseActionParams
9+
10+
11+
class NvRetrieveAlertsFilter(LinkedApiModel):
12+
category: Literal["lead", "account"] | None = None
13+
types: list[str] | None = None
14+
since: str | None = None
15+
exclude_suggested: bool | None = None
16+
17+
18+
class NvRetrieveAlertsParams(BaseActionParams):
19+
limit: int | None = Field(default=None, ge=1, le=1000)
20+
sort: Literal["latest", "relevance"] | None = None
21+
filter: NvRetrieveAlertsFilter | None = None
22+
23+
24+
class NvAlertSubject(LinkedApiModel):
25+
type: Literal["lead", "account"] | str
26+
urn: str
27+
name: str | None
28+
url: str
29+
saved: bool | None
30+
31+
32+
class NvAlert(LinkedApiModel):
33+
id: str
34+
type: str
35+
published_at: str
36+
subject: NvAlertSubject
37+
headline: str | None
38+
text: str | None
39+
subline: str | None
40+
url: str | None
41+
related_entity_urn: str | None

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "linkedapi"
7-
version = "1.4.8"
7+
version = "1.4.9"
88
description = "Official synchronous Python SDK for Linked API."
99
readme = "README.md"
1010
requires-python = ">=3.10"

‎tests/test_nv_retrieve_alerts.py‎

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
5+
import pytest
6+
import requests_mock
7+
from pydantic import ValidationError
8+
9+
from linkedapi import (
10+
LinkedApi,
11+
LinkedApiConfig,
12+
NvAlert,
13+
NvRetrieveAlertsFilter,
14+
NvRetrieveAlertsParams,
15+
)
16+
17+
ALERT = {
18+
"id": "urn:li:notification:123",
19+
"type": "FUTURE_MACHINE_TYPE",
20+
"publishedAt": "2026-10-01T00:00:00.000Z",
21+
"subject": {
22+
"type": "lead",
23+
"urn": "urn:li:fs_salesProfile:123",
24+
"name": None,
25+
"url": "https://www.linkedin.com/sales/lead/123",
26+
"saved": None,
27+
},
28+
"headline": None,
29+
"text": None,
30+
"subline": None,
31+
"url": None,
32+
"relatedEntityUrn": None,
33+
}
34+
35+
36+
@pytest.mark.parametrize(
37+
"params, expected",
38+
[
39+
(NvRetrieveAlertsParams(), {}),
40+
(
41+
NvRetrieveAlertsParams(
42+
limit=1000,
43+
sort="relevance",
44+
filter=NvRetrieveAlertsFilter(
45+
category="account",
46+
types=["FUTURE_MACHINE_TYPE"],
47+
since="2026-10-01T00:00:00Z",
48+
exclude_suggested=True,
49+
),
50+
),
51+
{
52+
"limit": 1000,
53+
"sort": "relevance",
54+
"filter": {
55+
"category": "account",
56+
"types": ["FUTURE_MACHINE_TYPE"],
57+
"since": "2026-10-01T00:00:00Z",
58+
"excludeSuggested": True,
59+
},
60+
},
61+
),
62+
(
63+
NvRetrieveAlertsParams(
64+
filter=NvRetrieveAlertsFilter(types=[], exclude_suggested=False)
65+
),
66+
{"filter": {"types": [], "excludeSuggested": False}},
67+
),
68+
],
69+
)
70+
def test_alerts_root_mapping_and_nullable_result(
71+
requests_mock: requests_mock.Mocker,
72+
params: NvRetrieveAlertsParams,
73+
expected: dict[str, Any],
74+
) -> None:
75+
client = LinkedApi(
76+
LinkedApiConfig(
77+
linked_api_token="test", identification_token="test", base_url="https://api.test"
78+
)
79+
)
80+
requests_mock.post(
81+
"https://api.test/workflows",
82+
json={"success": True, "result": {"workflowId": "alerts", "workflowStatus": "pending"}},
83+
)
84+
requests_mock.get(
85+
"https://api.test/workflows/alerts",
86+
json={
87+
"success": True,
88+
"result": {
89+
"workflowStatus": "completed",
90+
"completion": {"actionType": "nv.retrieveAlerts", "success": True, "data": [ALERT]},
91+
},
92+
},
93+
)
94+
operation = client.nv_retrieve_alerts
95+
assert operation in client.operations
96+
workflow = operation.execute(params)
97+
result = operation.result(workflow.workflow_id)
98+
assert requests_mock.request_history[0].json() == {
99+
"actionType": "nv.retrieveAlerts",
100+
**expected,
101+
}
102+
assert result.errors == []
103+
assert result.data is not None
104+
assert isinstance(result.data[0], NvAlert)
105+
assert result.data[0].model_dump(by_alias=True) == ALERT
106+
107+
108+
@pytest.mark.parametrize("limit", [0, 1001])
109+
def test_alerts_limit_bounds(limit: int) -> None:
110+
with pytest.raises(ValidationError):
111+
NvRetrieveAlertsParams(limit=limit)
112+
113+
114+
@pytest.mark.parametrize(
115+
"completion, expected_data, expected_errors",
116+
[
117+
({"success": True, "data": []}, [], []),
118+
(
119+
{"success": False, "error": {"type": "unexpectedError", "message": "Capture failed"}},
120+
None,
121+
[{"type": "unexpectedError", "message": "Capture failed"}],
122+
),
123+
],
124+
)
125+
def test_empty_alerts_and_failures_remain_distinct(
126+
completion: dict[str, Any],
127+
expected_data: list[Any] | None,
128+
expected_errors: list[dict[str, str]],
129+
) -> None:
130+
mapper = LinkedApi(
131+
LinkedApiConfig(linked_api_token="test", identification_token="test")
132+
).nv_retrieve_alerts.mapper
133+
result = mapper.map_response({"actionType": "nv.retrieveAlerts", **completion})
134+
assert result.data == expected_data
135+
assert [error.model_dump() for error in result.errors] == expected_errors

‎tests/test_operations.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
5959
"nv_sync_conversation",
6060
"nv_sync_inbox",
6161
"nv_manage_conversation",
62+
"nv_retrieve_alerts",
6263
"react_to_post",
6364
"endorse_person_skill",
6465
"endorse_person_skills",
@@ -77,7 +78,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
7778
assert hasattr(operation, "execute")
7879
assert hasattr(operation, "result")
7980
assert hasattr(operation, "cancel")
80-
assert len(linkedapi.operations) == 43
81+
assert len(linkedapi.operations) == 44
8182

8283

8384
def test_operation_mappers_match_node_contract() -> None:

0 commit comments

Comments
 (0)