Skip to content

Commit f324f67

Browse files
committed
Merge branch 'main' into feature/session-helpers
2 parents fed8cbf + adaac88 commit f324f67

20 files changed

+159
-102
lines changed

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
),
2828
zip_safe=False,
2929
license=about["__license__"],
30-
install_requires=["httpx>=0.27.0", "pydantic==2.8.2"],
30+
install_requires=["httpx>=0.27.0", "pydantic==2.9.2"],
3131
extras_require={
3232
"dev": [
3333
"flake8",
@@ -37,7 +37,7 @@
3737
"six==1.16.0",
3838
"black==24.4.2",
3939
"twine==5.1.1",
40-
"mypy==1.11.0",
40+
"mypy==1.12.0",
4141
"httpx>=0.27.0",
4242
],
4343
":python_version<'3.4'": ["enum34"],

tests/test_async_http_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ async def test_bad_request_exceptions_exclude_expected_request_data(self):
227227
try:
228228
await self.http_client.request("bad_place")
229229
except BadRequestException as ex:
230-
assert str(ex) == "(message=No message, request_id=request-123)"
230+
assert str(ex) == "(message=No message, request_id=request-123, foo=bar)"
231231
except Exception as ex:
232232
assert ex.__class__ == BadRequestException
233233

tests/test_portal.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ def test_generate_link_sso(self, mock_portal_link, mock_http_client_with_respons
2222

2323
assert response.link == "https://id.workos.com/portal/launch?secret=secret"
2424

25+
def test_generate_link_domain_verification(
26+
self, mock_portal_link, mock_http_client_with_response
27+
):
28+
mock_http_client_with_response(self.http_client, mock_portal_link, 201)
29+
30+
response = self.portal.generate_link(
31+
intent="domain_verification",
32+
organization_id="org_01EHQMYV6MBK39QC5PZXHY59C3",
33+
)
34+
35+
assert response.link == "https://id.workos.com/portal/launch?secret=secret"
36+
2537
def test_generate_link_dsync(
2638
self, mock_portal_link, capture_and_mock_http_client_request
2739
):

tests/test_sso.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def mock_magic_link_profile(self):
2929
idp_id="",
3030
first_name=None,
3131
last_name=None,
32+
profile=None,
3233
groups=None,
3334
raw_attributes={},
3435
).dict()

tests/test_sync_http_client.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -204,15 +204,19 @@ def test_request_exceptions_include_expected_request_data(
204204
# This'll fail for sure here but... just using the nice error that'd come up
205205
assert ex.__class__ == expected_exception
206206

207-
def test_bad_request_exceptions_include_expected_request_data(self):
207+
def test_bad_request_exceptions_include_request_data(self):
208208
request_id = "request-123"
209209
error = "example_error"
210210
error_description = "Example error description"
211211

212212
self.http_client._client.request = MagicMock(
213213
return_value=httpx.Response(
214214
status_code=400,
215-
json={"error": error, "error_description": error_description},
215+
json={
216+
"error": error,
217+
"error_description": error_description,
218+
"foo": "bar",
219+
},
216220
headers={"X-Request-ID": request_id},
217221
),
218222
)
@@ -222,29 +226,11 @@ def test_bad_request_exceptions_include_expected_request_data(self):
222226
except BadRequestException as ex:
223227
assert (
224228
str(ex)
225-
== "(message=No message, request_id=request-123, error=example_error, error_description=Example error description)"
229+
== "(message=No message, request_id=request-123, error=example_error, error_description=Example error description, foo=bar)"
226230
)
227231
except Exception as ex:
228232
assert ex.__class__ == BadRequestException
229233

230-
def test_bad_request_exceptions_exclude_expected_request_data(self):
231-
request_id = "request-123"
232-
233-
self.http_client._client.request = MagicMock(
234-
return_value=httpx.Response(
235-
status_code=400,
236-
json={"foo": "bar"},
237-
headers={"X-Request-ID": request_id},
238-
),
239-
)
240-
241-
try:
242-
self.http_client.request("bad_place")
243-
except BadRequestException as ex:
244-
assert str(ex) == "(message=No message, request_id=request-123)"
245-
except Exception as ex:
246-
assert ex.__class__ == BadRequestException
247-
248234
def test_request_bad_body_raises_expected_exception_with_request_data(self):
249235
request_id = "request-123"
250236

tests/test_webhooks.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import pytest
3+
from workos.typing.webhooks import WebhookTypeAdapter
34
from workos.webhooks import Webhooks
45

56

@@ -122,3 +123,34 @@ def test_unrecognized_webhook_type_returns_untyped_webhook(
122123
)
123124
assert type(result).__name__ == "UntypedWebhook"
124125
assert result.dict() == json.loads(mock_unknown_webhook_body)
126+
127+
# TODO: This test should be updated in the next major version to expect
128+
# a DirectoryActivatedWebhook return type.
129+
def test_validate_dsync_activated_event(self):
130+
event_body = {
131+
"id": "event_01J8SX5FTXYD2YFWVTGJY49EM6",
132+
"data": {
133+
"id": "directory_01EHWNC0FCBHZ3BJ7EGKYXK0E6",
134+
"name": "Foo Corp's Directory",
135+
"type": "generic scim v2.0",
136+
"state": "active",
137+
"object": "directory",
138+
"domains": [
139+
{
140+
"id": "org_domain_01EZTR5N6Y9RQKHK2E9F31KZX6",
141+
"domain": "foo-corp.com",
142+
"object": "organization_domain",
143+
}
144+
],
145+
"created_at": "2021-06-25T19:07:33.155Z",
146+
"updated_at": "2021-06-25T19:07:33.155Z",
147+
"external_key": "UWuccu6o1E0GqkYs",
148+
"organization_id": "org_01EZTR6WYX1A0DSE2CYMGXQ24Y",
149+
},
150+
"event": "dsync.activated",
151+
"created_at": "2021-06-25T19:07:33.155Z",
152+
}
153+
154+
result = WebhookTypeAdapter.validate_json(json.dumps(event_body))
155+
assert type(result).__name__ == "UntypedWebhook"
156+
assert result.dict() == event_body

tests/utils/fixtures/mock_event.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from workos.types.events import DirectoryActivatedEvent
44
from workos.types.events.directory_payload_with_legacy_fields import (
55
DirectoryPayloadWithLegacyFields,
6+
DirectoryPayloadWithLegacyFieldsForEventsApi,
67
)
78

89

@@ -13,7 +14,7 @@ def __init__(self, id):
1314
object="event",
1415
id=id,
1516
event="dsync.activated",
16-
data=DirectoryPayloadWithLegacyFields(
17+
data=DirectoryPayloadWithLegacyFieldsForEventsApi(
1718
object="directory",
1819
id="dir_1234",
1920
organization_id="organization_id",

tests/utils/fixtures/mock_profile.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def __init__(self, id: str):
1010
1111
first_name="WorkOS",
1212
last_name="Demo",
13+
role={"slug": "admin"},
1314
groups=["Admins", "Developers"],
1415
organization_id="org_01FG53X8636WSNW2WEKB2C31ZB",
1516
connection_id="conn_01EMH8WAK20T42N2NBMNBCYHAG",

workos/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
__package_url__ = "https://github.com/workos-inc/workos-python"
1414

15-
__version__ = "5.3.0"
15+
__version__ = "5.6.0"
1616

1717
__author__ = "WorkOS"
1818

workos/exceptions.py

Lines changed: 20 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,70 +8,35 @@ class BaseRequestException(Exception):
88
def __init__(
99
self,
1010
response: httpx.Response,
11-
message: Optional[str] = None,
12-
error: Optional[str] = None,
13-
errors: Optional[Mapping[str, Any]] = None,
14-
error_description: Optional[str] = None,
15-
code: Optional[str] = None,
16-
pending_authentication_token: Optional[str] = None,
11+
response_json: Optional[Mapping[str, Any]],
1712
) -> None:
18-
super(BaseRequestException, self).__init__(message)
13+
super(BaseRequestException, self).__init__(response_json)
1914

20-
self.message = message
21-
self.error = error
22-
self.errors = errors
23-
self.code = code
24-
self.error_description = error_description
25-
self.pending_authentication_token = pending_authentication_token
26-
self.extract_and_set_response_related_data(response)
27-
28-
def extract_and_set_response_related_data(self, response: httpx.Response) -> None:
2915
self.response = response
16+
self.response_json = response_json
3017

31-
try:
32-
response_json = response.json()
33-
self.message = response_json.get("message")
34-
self.error = response_json.get("error")
35-
self.errors = response_json.get("errors")
36-
self.code = response_json.get("code")
37-
self.error_description = response_json.get("error_description")
38-
self.pending_authentication_token = response_json.get(
39-
"pending_authentication_token"
40-
)
41-
except ValueError:
42-
self.message = None
43-
self.error = None
44-
self.errors = None
45-
self.code = None
46-
self.error_description = None
47-
self.pending_authentication_token = None
48-
49-
headers = response.headers
50-
self.request_id = headers.get("X-Request-ID")
51-
52-
def __str__(self) -> str:
53-
message = self.message or "No message"
54-
exception = "(message=%s" % message
18+
self.message = self.extract_from_json("message", "No message")
19+
self.error = self.extract_from_json("error", "Unknown")
20+
self.errors = self.extract_from_json("errors", None)
21+
self.code = self.extract_from_json("code", None)
22+
self.error_description = self.extract_from_json("error_description", "Unknown")
5523

56-
if self.request_id is not None:
57-
exception += ", request_id=%s" % self.request_id
24+
self.request_id = response.headers.get("X-Request-ID")
5825

59-
if self.code is not None:
60-
exception += ", code=%s" % self.code
26+
def extract_from_json(self, key: str, alt: Optional[str] = None) -> Optional[str]:
27+
if self.response_json is None:
28+
return alt
6129

62-
if self.error is not None:
63-
exception += ", error=%s" % self.error
30+
return self.response_json.get(key, alt)
6431

65-
if self.errors is not None:
66-
exception += ", errors=%s" % self.errors
67-
68-
if self.error_description is not None:
69-
exception += ", error_description=%s" % self.error_description
32+
def __str__(self) -> str:
33+
exception = "(message=%s" % self.message
34+
exception += ", request_id=%s" % self.request_id
7035

71-
if self.pending_authentication_token is not None:
72-
exception += (
73-
", pending_authentication_token=%s" % self.pending_authentication_token
74-
)
36+
if self.response_json is not None:
37+
for key, value in self.response_json.items():
38+
if key != "message":
39+
exception += ", %s=%s" % (key, value)
7540

7641
return exception + ")"
7742

0 commit comments

Comments
 (0)