Skip to content

Commit e2b231b

Browse files
Profile viewers
1 parent efe4a3d commit e2b231b

6 files changed

Lines changed: 113 additions & 1 deletion

File tree

linkedapi/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
RetrieveInvitations,
6464
RetrievePendingRequests,
6565
RetrievePerformance,
66+
RetrieveProfileViewers,
6667
RetrieveSSI,
6768
SearchCompanies,
6869
SearchJobs,
@@ -78,7 +79,7 @@
7879
from linkedapi.types import __all__ as _types_all
7980
from linkedapi.webhooks import parse_webhook_event
8081

81-
__version__ = "1.3.8"
82+
__version__ = "1.3.9"
8283
PredefinedOperation = Operation
8384

8485
__all__ = [
@@ -140,6 +141,7 @@
140141
"RetrieveFeed",
141142
"RetrievePendingRequests",
142143
"RetrievePerformance",
144+
"RetrieveProfileViewers",
143145
"RetrieveSSI",
144146
"SearchCompanies",
145147
"SearchJobs",

linkedapi/client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
RetrieveInvitations,
3737
RetrievePendingRequests,
3838
RetrievePerformance,
39+
RetrieveProfileViewers,
3940
RetrieveSSI,
4041
SearchCompanies,
4142
SearchJobs,
@@ -98,6 +99,7 @@ def __init__(self, config: LinkedApiConfig | HttpClient[Any]) -> None:
9899
self.retrieve_feed = RetrieveFeed(self.http_client)
99100
self.retrieve_ssi = RetrieveSSI(self.http_client)
100101
self.retrieve_performance = RetrievePerformance(self.http_client)
102+
self.retrieve_profile_viewers = RetrieveProfileViewers(self.http_client)
101103
self.nv_send_message = NvSendMessage(self.http_client)
102104
self.nv_sync_conversation = NvSyncConversation(self.http_client)
103105
self.nv_sync_inbox = NvSyncInbox(self.http_client)
@@ -138,6 +140,7 @@ def __init__(self, config: LinkedApiConfig | HttpClient[Any]) -> None:
138140
self.retrieve_feed,
139141
self.retrieve_ssi,
140142
self.retrieve_performance,
143+
self.retrieve_profile_viewers,
141144
self.nv_send_message,
142145
self.nv_sync_conversation,
143146
self.nv_sync_inbox,

linkedapi/operations/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from linkedapi.operations.retrieve_invitations import RetrieveInvitations
2727
from linkedapi.operations.retrieve_pending_requests import RetrievePendingRequests
2828
from linkedapi.operations.retrieve_performance import RetrievePerformance
29+
from linkedapi.operations.retrieve_profile_viewers import RetrieveProfileViewers
2930
from linkedapi.operations.retrieve_ssi import RetrieveSSI
3031
from linkedapi.operations.search_companies import SearchCompanies
3132
from linkedapi.operations.search_jobs import SearchJobs
@@ -72,6 +73,7 @@
7273
"RetrieveInvitations",
7374
"RetrievePendingRequests",
7475
"RetrievePerformance",
76+
"RetrieveProfileViewers",
7577
"RetrieveSSI",
7678
"SearchCompanies",
7779
"SearchJobs",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
from linkedapi.core import Operation
4+
from linkedapi.mappers import ArrayWorkflowMapper
5+
from linkedapi.types import ProfileViewer, RetrieveProfileViewersParams
6+
7+
8+
class RetrieveProfileViewers(Operation[RetrieveProfileViewersParams, list[ProfileViewer]]):
9+
"""Retrieve the viewers visible on the current account's LinkedIn profile."""
10+
11+
operation_name = "retrieveProfileViewers"
12+
mapper = ArrayWorkflowMapper[RetrieveProfileViewersParams, ProfileViewer](
13+
"st.retrieveProfileViewers",
14+
ProfileViewer,
15+
)

linkedapi/types/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@
177177
ReplyToCommentParams,
178178
ReplyToCommentResult,
179179
)
180+
from linkedapi.types.profile_viewers import (
181+
ProfileViewer,
182+
ProfileViewerType,
183+
RetrieveProfileViewersParams,
184+
)
180185
from linkedapi.types.responses import LinkedApiRequestError, LinkedApiResponse
181186
from linkedapi.types.search_companies import (
182187
AnnualRevenueFilter,
@@ -383,6 +388,8 @@
383388
"PostReactionsRetrievalConfig",
384389
"PostReposter",
385390
"PostType",
391+
"ProfileViewer",
392+
"ProfileViewerType",
386393
"ReactToCommentParams",
387394
"ReactToPostParams",
388395
"Reaction",
@@ -402,6 +409,7 @@
402409
"RetrieveFeedParams",
403410
"RetrievePendingRequestsResult",
404411
"RetrievePerformanceResult",
412+
"RetrieveProfileViewersParams",
405413
"RetrieveSSIResult",
406414
"SalaryPeriod",
407415
"SearchCompaniesFilter",

linkedapi/types/profile_viewers.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from __future__ import annotations
2+
3+
from typing import Literal
4+
5+
from pydantic import Field, model_validator
6+
7+
from linkedapi.types.base import LinkedApiModel
8+
from linkedapi.types.params import BaseActionParams
9+
10+
ProfileViewerType = Literal["identified", "anonymous"]
11+
12+
13+
class RetrieveProfileViewersParams(BaseActionParams):
14+
limit: int | None = Field(default=None, ge=1, le=300)
15+
since: str | None = None
16+
17+
18+
class ProfileViewer(LinkedApiModel):
19+
viewer_type: ProfileViewerType
20+
viewed_at: str | None
21+
viewed_ago: str | None
22+
name: str | None = None
23+
public_url: str | None = None
24+
urn: str | None = None
25+
headline: str | None = None
26+
connection_degree: Literal[1, 2, 3] | None = None
27+
avatar_url: str | None = None
28+
description: str | None = None
29+
search_url: str | None = None
30+
31+
@model_validator(mode="after")
32+
def validate_type_fields(self) -> ProfileViewer:
33+
type_fields = {
34+
"name",
35+
"public_url",
36+
"urn",
37+
"headline",
38+
"connection_degree",
39+
"avatar_url",
40+
"description",
41+
"search_url",
42+
}
43+
required_fields = {
44+
"identified": {
45+
"name",
46+
"public_url",
47+
"urn",
48+
"headline",
49+
"connection_degree",
50+
"avatar_url",
51+
},
52+
"anonymous": {"description", "search_url"},
53+
}[self.viewer_type]
54+
missing_fields = required_fields - self.model_fields_set
55+
if missing_fields:
56+
msg = (
57+
f"{self.viewer_type} profile viewer is missing required fields: "
58+
f"{', '.join(sorted(missing_fields))}"
59+
)
60+
raise ValueError(msg)
61+
62+
unexpected_fields = (self.model_fields_set & type_fields) - required_fields
63+
if unexpected_fields:
64+
msg = (
65+
f"{self.viewer_type} profile viewer contains fields for another type: "
66+
f"{', '.join(sorted(unexpected_fields))}"
67+
)
68+
raise ValueError(msg)
69+
70+
required_values = {
71+
"identified": {"name": self.name, "public_url": self.public_url},
72+
"anonymous": {"description": self.description, "search_url": self.search_url},
73+
}[self.viewer_type]
74+
null_fields = [name for name, value in required_values.items() if value is None]
75+
if null_fields:
76+
msg = (
77+
f"{self.viewer_type} profile viewer requires non-null fields: "
78+
f"{', '.join(sorted(null_fields))}"
79+
)
80+
raise ValueError(msg)
81+
82+
return self

0 commit comments

Comments
 (0)