Skip to content

Commit 8c224dd

Browse files
Working hours
1 parent b9dbe50 commit 8c224dd

8 files changed

Lines changed: 124 additions & 2 deletions

File tree

linkedapi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
from linkedapi.types import __all__ as _types_all
7979
from linkedapi.webhooks import parse_webhook_event
8080

81-
__version__ = "1.3.5"
81+
__version__ = "1.3.6"
8282
PredefinedOperation = Operation
8383

8484
__all__ = [

linkedapi/core/operation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def status(self, workflow_id: str) -> WorkflowInProgressResponse | MappedRespons
6060
workflow_id=workflow_id,
6161
workflow_status=workflow_result.workflow_status,
6262
message=workflow_result.message,
63+
pending_reason=workflow_result.pending_reason,
6364
)
6465

6566
completion = self._get_completion(workflow_result)

linkedapi/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
"linkedinAccountSignedOut",
6767
"languageNotSupported",
6868
"workflowTimeout",
69+
"outsideWorkingHours",
70+
"workingHoursWaitExpired",
6971
"httpError",
7072
"tooManyRequests",
7173
"accountNotFound",
@@ -87,6 +89,8 @@
8789
"linkedinAccountSignedOut",
8890
"languageNotSupported",
8991
"workflowTimeout",
92+
"outsideWorkingHours",
93+
"workingHoursWaitExpired",
9094
"httpError",
9195
"tooManyRequests",
9296
"accountNotFound",

linkedapi/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@
235235
WorkflowFailure,
236236
WorkflowInProgressResponse,
237237
WorkflowInProgressStatus,
238+
WorkflowPendingReason,
238239
WorkflowResponse,
239240
WorkflowStartedResponse,
240241
WorkflowStatus,
@@ -448,6 +449,7 @@
448449
"WorkflowFailure",
449450
"WorkflowInProgressResponse",
450451
"WorkflowInProgressStatus",
452+
"WorkflowPendingReason",
451453
"WorkflowResponse",
452454
"WorkflowStartedResponse",
453455
"WorkflowStatus",

linkedapi/types/workflow.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
WorkflowRunningStatus = Literal["running"]
1010
WorkflowInProgressStatus = Literal["pending", "running"]
1111
WorkflowStatus = Literal["pending", "running", "completed", "failed"]
12+
WorkflowPendingReason = Literal["queued", "outsideWorkingHours"]
1213
WorkflowDefinition = dict[str, Any] | list[dict[str, Any]]
1314
WorkflowCompletion = dict[str, Any] | list[dict[str, Any]]
1415

@@ -22,12 +23,15 @@ class WorkflowStartedResponse(LinkedApiModel):
2223
workflow_id: str
2324
workflow_status: WorkflowInProgressStatus
2425
message: str | None = None
26+
# Optional so an older API that does not send it still parses.
27+
pending_reason: WorkflowPendingReason | None = None
2528

2629

2730
class WorkflowInProgressResponse(LinkedApiModel):
2831
workflow_id: str
2932
workflow_status: WorkflowInProgressStatus
3033
message: str | None = None
34+
pending_reason: WorkflowPendingReason | None = None
3135

3236

3337
class WorkflowFailure(LinkedApiModel):
@@ -43,6 +47,7 @@ class WorkflowResponse(LinkedApiModel):
4347
workflow_id: str
4448
workflow_status: WorkflowStatus
4549
message: str | None = None
50+
pending_reason: WorkflowPendingReason | None = None
4651
completion: WorkflowCompletion | None = None
4752
failure: WorkflowFailure | None = None
4853

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.3.5"
7+
version = "1.3.6"
88
description = "Official synchronous Python SDK for Linked API."
99
readme = "README.md"
1010
requires-python = ">=3.10"

tests/test_errors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def test_error_type_sets_match_node_contract() -> None:
2121
"linkedinAccountSignedOut",
2222
"languageNotSupported",
2323
"workflowTimeout",
24+
"outsideWorkingHours",
25+
"workingHoursWaitExpired",
2426
"httpError",
2527
"tooManyRequests",
2628
"accountNotFound",
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)