linkedapi 1.3.5__tar.gz → 1.3.6__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.
- {linkedapi-1.3.5 → linkedapi-1.3.6}/PKG-INFO +1 -1
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/__init__.py +1 -1
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/core/operation.py +1 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/errors.py +4 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/__init__.py +2 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/workflow.py +5 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/pyproject.toml +1 -1
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_errors.py +2 -0
- linkedapi-1.3.6/tests/test_working_hours_contract.py +108 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/.github/workflows/publish.yaml +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/.gitignore +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/LICENSE +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/README.md +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/admin/__init__.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/admin/accounts.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/admin/admin.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/admin/http_client.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/admin/limits.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/admin/subscription.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/admin/webhooks.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/client.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/config.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/core/__init__.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/core/polling.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/http/__init__.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/http/base.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/http/linked_api_http_client.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/mappers/__init__.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/mappers/array.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/mappers/base.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/mappers/send_message.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/mappers/simple.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/mappers/then.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/mappers/void.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/__init__.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/accept_invitation.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/check_connection_status.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/comment_on_post.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/create_post.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/custom_workflow.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/fetch_company.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/fetch_job.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/fetch_person.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/fetch_post.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/ignore_invitation.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/manage_conversation.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/nv_fetch_company.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/nv_fetch_person.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/nv_manage_conversation.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/nv_search_companies.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/nv_search_people.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/nv_send_message.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/nv_sync_conversation.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/nv_sync_inbox.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/react_to_comment.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/react_to_post.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/remove_connection.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/reply_to_comment.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/retrieve_connections.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/retrieve_feed.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/retrieve_invitations.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/retrieve_pending_requests.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/retrieve_performance.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/retrieve_ssi.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/search_companies.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/search_jobs.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/search_people.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/send_connection_request.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/send_message.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/sync_conversation.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/sync_inbox.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/sync_network.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/operations/withdraw_connection_request.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/py.typed +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/account.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/admin/__init__.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/admin/accounts.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/admin/config.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/admin/limits.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/admin/subscription.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/base.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/company.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/connection.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/feed.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/jobs.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/message.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/network.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/params.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/person.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/post.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/responses.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/search_companies.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/search_people.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/statistics.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/types/webhooks.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/webhooks/__init__.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/linkedapi/webhooks/parse.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/conftest.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_admin.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_direct_methods.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_http_client.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_mappers.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_models.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_operations.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_polling.py +0 -0
- {linkedapi-1.3.5 → linkedapi-1.3.6}/tests/test_webhooks.py +0 -0
|
@@ -60,6 +60,7 @@ class Operation(ABC, Generic[TParams, TResult]):
|
|
|
60
60
|
workflow_id=workflow_id,
|
|
61
61
|
workflow_status=workflow_result.workflow_status,
|
|
62
62
|
message=workflow_result.message,
|
|
63
|
+
pending_reason=workflow_result.pending_reason,
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
completion = self._get_completion(workflow_result)
|
|
@@ -66,6 +66,8 @@ LinkedApiErrorType = Literal[
|
|
|
66
66
|
"linkedinAccountSignedOut",
|
|
67
67
|
"languageNotSupported",
|
|
68
68
|
"workflowTimeout",
|
|
69
|
+
"outsideWorkingHours",
|
|
70
|
+
"workingHoursWaitExpired",
|
|
69
71
|
"httpError",
|
|
70
72
|
"tooManyRequests",
|
|
71
73
|
"accountNotFound",
|
|
@@ -87,6 +89,8 @@ LINKED_API_ERROR_TYPES: tuple[str, ...] = (
|
|
|
87
89
|
"linkedinAccountSignedOut",
|
|
88
90
|
"languageNotSupported",
|
|
89
91
|
"workflowTimeout",
|
|
92
|
+
"outsideWorkingHours",
|
|
93
|
+
"workingHoursWaitExpired",
|
|
90
94
|
"httpError",
|
|
91
95
|
"tooManyRequests",
|
|
92
96
|
"accountNotFound",
|
|
@@ -235,6 +235,7 @@ from linkedapi.types.workflow import (
|
|
|
235
235
|
WorkflowFailure,
|
|
236
236
|
WorkflowInProgressResponse,
|
|
237
237
|
WorkflowInProgressStatus,
|
|
238
|
+
WorkflowPendingReason,
|
|
238
239
|
WorkflowResponse,
|
|
239
240
|
WorkflowStartedResponse,
|
|
240
241
|
WorkflowStatus,
|
|
@@ -448,6 +449,7 @@ __all__ = [
|
|
|
448
449
|
"WorkflowFailure",
|
|
449
450
|
"WorkflowInProgressResponse",
|
|
450
451
|
"WorkflowInProgressStatus",
|
|
452
|
+
"WorkflowPendingReason",
|
|
451
453
|
"WorkflowResponse",
|
|
452
454
|
"WorkflowStartedResponse",
|
|
453
455
|
"WorkflowStatus",
|
|
@@ -9,6 +9,7 @@ WorkflowPendingStatus = Literal["pending"]
|
|
|
9
9
|
WorkflowRunningStatus = Literal["running"]
|
|
10
10
|
WorkflowInProgressStatus = Literal["pending", "running"]
|
|
11
11
|
WorkflowStatus = Literal["pending", "running", "completed", "failed"]
|
|
12
|
+
WorkflowPendingReason = Literal["queued", "outsideWorkingHours"]
|
|
12
13
|
WorkflowDefinition = dict[str, Any] | list[dict[str, Any]]
|
|
13
14
|
WorkflowCompletion = dict[str, Any] | list[dict[str, Any]]
|
|
14
15
|
|
|
@@ -22,12 +23,15 @@ class WorkflowStartedResponse(LinkedApiModel):
|
|
|
22
23
|
workflow_id: str
|
|
23
24
|
workflow_status: WorkflowInProgressStatus
|
|
24
25
|
message: str | None = None
|
|
26
|
+
# Optional so an older API that does not send it still parses.
|
|
27
|
+
pending_reason: WorkflowPendingReason | None = None
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
class WorkflowInProgressResponse(LinkedApiModel):
|
|
28
31
|
workflow_id: str
|
|
29
32
|
workflow_status: WorkflowInProgressStatus
|
|
30
33
|
message: str | None = None
|
|
34
|
+
pending_reason: WorkflowPendingReason | None = None
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
class WorkflowFailure(LinkedApiModel):
|
|
@@ -43,6 +47,7 @@ class WorkflowResponse(LinkedApiModel):
|
|
|
43
47
|
workflow_id: str
|
|
44
48
|
workflow_status: WorkflowStatus
|
|
45
49
|
message: str | None = None
|
|
50
|
+
pending_reason: WorkflowPendingReason | None = None
|
|
46
51
|
completion: WorkflowCompletion | None = None
|
|
47
52
|
failure: WorkflowFailure | None = None
|
|
48
53
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from unittest.mock import Mock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from linkedapi.core.operation import Operation
|
|
9
|
+
from linkedapi.errors import LINKED_API_ERROR_TYPES
|
|
10
|
+
from linkedapi.types import (
|
|
11
|
+
WorkflowInProgressResponse,
|
|
12
|
+
WorkflowResponse,
|
|
13
|
+
WorkflowStartedResponse,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _StubMapper:
|
|
18
|
+
def map_request(self, params: Any) -> Any:
|
|
19
|
+
return params
|
|
20
|
+
|
|
21
|
+
def map_response(self, completion: Any) -> Any:
|
|
22
|
+
return completion
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _StubOperation(Operation): # type: ignore[type-arg]
|
|
26
|
+
operation_name = "fetchPerson"
|
|
27
|
+
|
|
28
|
+
def __init__(self, http_client: Any) -> None:
|
|
29
|
+
super().__init__(http_client)
|
|
30
|
+
self.mapper = _StubMapper() # type: ignore[assignment]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _operation(workflow_result: dict[str, Any]) -> _StubOperation:
|
|
34
|
+
http_client = Mock()
|
|
35
|
+
http_client.get.return_value = Mock(
|
|
36
|
+
error=None,
|
|
37
|
+
result=WorkflowResponse.model_validate(workflow_result),
|
|
38
|
+
)
|
|
39
|
+
return _StubOperation(http_client)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestPendingReasonModels:
|
|
43
|
+
def test_in_progress_response_accepts_a_pending_reason(self) -> None:
|
|
44
|
+
response = WorkflowInProgressResponse(
|
|
45
|
+
workflowId="wf-1",
|
|
46
|
+
workflowStatus="pending",
|
|
47
|
+
pendingReason="outsideWorkingHours",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
assert response.pending_reason == "outsideWorkingHours"
|
|
51
|
+
|
|
52
|
+
def test_started_response_accepts_a_pending_reason(self) -> None:
|
|
53
|
+
response = WorkflowStartedResponse(
|
|
54
|
+
workflowId="wf-1",
|
|
55
|
+
workflowStatus="pending",
|
|
56
|
+
pendingReason="queued",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
assert response.pending_reason == "queued"
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize(
|
|
62
|
+
"model",
|
|
63
|
+
[WorkflowInProgressResponse, WorkflowStartedResponse, WorkflowResponse],
|
|
64
|
+
)
|
|
65
|
+
def test_field_is_optional_for_back_compat(self, model: Any) -> None:
|
|
66
|
+
response = model(workflowId="wf-1", workflowStatus="running")
|
|
67
|
+
|
|
68
|
+
assert response.pending_reason is None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestStatusCarriesPendingReason:
|
|
72
|
+
def test_carries_the_outside_working_hours_reason(self) -> None:
|
|
73
|
+
operation = _operation(
|
|
74
|
+
{
|
|
75
|
+
"workflowId": "wf-1",
|
|
76
|
+
"workflowStatus": "pending",
|
|
77
|
+
"pendingReason": "outsideWorkingHours",
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
result = operation.status("wf-1")
|
|
82
|
+
|
|
83
|
+
assert isinstance(result, WorkflowInProgressResponse)
|
|
84
|
+
assert result.pending_reason == "outsideWorkingHours"
|
|
85
|
+
|
|
86
|
+
def test_carries_the_queued_reason(self) -> None:
|
|
87
|
+
operation = _operation(
|
|
88
|
+
{"workflowId": "wf-1", "workflowStatus": "pending", "pendingReason": "queued"}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
result = operation.status("wf-1")
|
|
92
|
+
|
|
93
|
+
assert isinstance(result, WorkflowInProgressResponse)
|
|
94
|
+
assert result.pending_reason == "queued"
|
|
95
|
+
|
|
96
|
+
def test_keeps_none_when_the_api_sends_nothing(self) -> None:
|
|
97
|
+
operation = _operation({"workflowId": "wf-1", "workflowStatus": "running"})
|
|
98
|
+
|
|
99
|
+
result = operation.status("wf-1")
|
|
100
|
+
|
|
101
|
+
assert isinstance(result, WorkflowInProgressResponse)
|
|
102
|
+
assert result.pending_reason is None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestWorkingHoursErrorTypes:
|
|
106
|
+
@pytest.mark.parametrize("error_type", ["outsideWorkingHours", "workingHoursWaitExpired"])
|
|
107
|
+
def test_error_type_is_known(self, error_type: str) -> None:
|
|
108
|
+
assert error_type in LINKED_API_ERROR_TYPES
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|