Skip to content

Commit 1afa8ee

Browse files
authored
Merge version 5 (#339)
1 parent 5ed2044 commit 1afa8ee

File tree

164 files changed

+10581
-6276
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

164 files changed

+10581
-6276
lines changed

Diff for: .github/workflows/ci.yml

+6-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: CI
33
on:
44
push:
55
branches:
6-
- 'main'
6+
- "main"
77
pull_request: {}
88

99
defaults:
@@ -17,13 +17,13 @@ jobs:
1717
strategy:
1818
fail-fast: false
1919
matrix:
20-
python: ['3.8', '3.9', '3.10', '3.11']
20+
python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
2121
steps:
2222
- uses: actions/checkout@v3
2323
- uses: actions/setup-python@v5
2424
with:
2525
python-version: ${{ matrix.python }}
26-
cache: 'pip'
26+
cache: "pip"
2727
cache-dependency-path: setup.py
2828

2929
- name: Install dependencies
@@ -38,5 +38,8 @@ jobs:
3838
flake8 . --extend-exclude .devbox --count --select=E9,F7,F82 --show-source --statistics
3939
flake8 . --extend-exclude .devbox --count --exit-zero --max-complexity=10 --statistics
4040
41+
- name: Type Check
42+
run: mypy
43+
4144
- name: Test
4245
run: python -m pytest

Diff for: .gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,9 @@ dmypy.json
131131

132132
# VSCode
133133
.vscode/
134+
135+
# macOS
136+
.DS_Store
137+
138+
# Intellij
139+
.idea/

Diff for: README.md

+15-4
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,24 @@ python setup.py install
2525

2626
## Configuration
2727

28-
The package will need to be configured with your [api key](https://dashboard.workos.com/api-keys) at a minimum and [client id](https://dashboard.workos.com/sso/configuration) if you plan on using SSO:
28+
The package will need to be configured with your [api key and client ID](https://dashboard.workos.com/api-keys).
2929

3030
```python
31-
import workos
31+
from workos import WorkOSClient
3232

33-
workos.api_key = "sk_1234"
34-
workos.client_id = "client_1234"
33+
workos_client = WorkOSClient(
34+
api_key="sk_1234", client_id="client_1234"
35+
)
36+
```
37+
38+
The SDK also provides asyncio support for some SDK methods, via the async client:
39+
40+
```python
41+
from workos import AsyncWorkOSClient
42+
43+
async_workos_client = AsyncWorkOSClient(
44+
api_key="sk_1234", client_id="client_1234"
45+
)
3546
```
3647

3748
## SDK Versioning

Diff for: mypy.ini

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[mypy]
2+
packages = workos
3+
warn_return_any = True
4+
warn_unused_configs = True
5+
warn_unreachable = True
6+
warn_redundant_casts = True
7+
warn_no_return = True
8+
warn_unused_ignores = True
9+
implicit_reexport = False
10+
strict_equality = True
11+
strict = True

Diff for: setup.py

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import sys
32
from setuptools import setup, find_packages
43

54
base_dir = os.path.dirname(__file__)
@@ -20,24 +19,26 @@
2019
description=about["__description__"],
2120
long_description=long_description,
2221
long_description_content_type="text/markdown",
22+
package_data={"workos": ["py.typed"]},
2323
packages=find_packages(
2424
exclude=[
2525
"tests*",
2626
]
2727
),
2828
zip_safe=False,
2929
license=about["__license__"],
30-
install_requires=["requests>=2.22.0"],
30+
install_requires=["httpx>=0.27.0", "pydantic==2.8.2"],
3131
extras_require={
3232
"dev": [
3333
"flake8",
34-
"pytest==8.1.1",
35-
"pytest-cov==2.8.1",
36-
"six==1.13.0",
37-
"black==22.3.0",
38-
"twine==4.0.2",
39-
"requests==2.30.0",
40-
"urllib3==2.0.2",
34+
"pytest==8.3.2",
35+
"pytest-asyncio==0.23.8",
36+
"pytest-cov==5.0.0",
37+
"six==1.16.0",
38+
"black==24.4.2",
39+
"twine==5.1.1",
40+
"mypy==1.11.0",
41+
"httpx>=0.27.0",
4142
],
4243
":python_version<'3.4'": ["enum34"],
4344
},
@@ -48,10 +49,10 @@
4849
"Operating System :: OS Independent",
4950
"Programming Language :: Python",
5051
"Programming Language :: Python :: 3",
51-
"Programming Language :: Python :: 3.4",
52-
"Programming Language :: Python :: 3.5",
53-
"Programming Language :: Python :: 3.6",
54-
"Programming Language :: Python :: 3.7",
5552
"Programming Language :: Python :: 3.8",
53+
"Programming Language :: Python :: 3.9",
54+
"Programming Language :: Python :: 3.10",
55+
"Programming Language :: Python :: 3.11",
56+
"Programming Language :: Python :: 3.12",
5657
],
5758
)

Diff for: tests/conftest.py

+129-51
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,158 @@
1-
import pytest
2-
import requests
3-
4-
import workos
5-
6-
7-
class MockResponse(object):
8-
def __init__(self, response_dict, status_code, headers=None):
9-
self.response_dict = response_dict
10-
self.status_code = status_code
11-
self.headers = {} if headers is None else headers
12-
13-
if "content-type" not in self.headers:
14-
self.headers["content-type"] = "application/json"
15-
16-
def json(self):
17-
return self.response_dict
1+
from typing import Any, Callable, Mapping, Optional
2+
from unittest.mock import AsyncMock, MagicMock
183

4+
import httpx
5+
import pytest
196

20-
class MockRawResponse(object):
21-
def __init__(self, content, status_code, headers=None):
22-
self.content = content
23-
self.status_code = status_code
24-
self.headers = {} if headers is None else headers
7+
from tests.utils.client_configuration import ClientConfiguration
8+
from tests.utils.list_resource import list_data_to_dicts, list_response_of
9+
from workos.types.list_resource import WorkOSListResource
10+
from workos.utils.http_client import AsyncHTTPClient, HTTPClient, SyncHTTPClient
2511

2612

2713
@pytest.fixture
28-
def set_api_key(monkeypatch):
29-
monkeypatch.setattr(workos, "api_key", "sk_test")
14+
def sync_http_client_for_test():
15+
return SyncHTTPClient(
16+
api_key="sk_test",
17+
base_url="https://api.workos.test/",
18+
client_id="client_b27needthisforssotemxo",
19+
version="test",
20+
)
3021

3122

3223
@pytest.fixture
33-
def set_client_id(monkeypatch):
34-
monkeypatch.setattr(workos, "client_id", "client_b27needthisforssotemxo")
24+
def async_http_client_for_test():
25+
return AsyncHTTPClient(
26+
api_key="sk_test",
27+
base_url="https://api.workos.test/",
28+
client_id="client_b27needthisforssotemxo",
29+
version="test",
30+
)
3531

3632

3733
@pytest.fixture
38-
def set_api_key_and_client_id(set_api_key, set_client_id):
39-
pass
34+
def mock_http_client_with_response(monkeypatch):
35+
def inner(
36+
http_client: HTTPClient,
37+
response_dict: Optional[dict] = None,
38+
status_code: int = 200,
39+
headers: Optional[Mapping[str, str]] = None,
40+
):
41+
mock_class = (
42+
AsyncMock if isinstance(http_client, AsyncHTTPClient) else MagicMock
43+
)
44+
mock = mock_class(
45+
return_value=httpx.Response(
46+
status_code=status_code, headers=headers, json=response_dict
47+
),
48+
)
49+
monkeypatch.setattr(http_client._client, "request", mock)
50+
51+
return inner
4052

4153

4254
@pytest.fixture
43-
def mock_request_method(monkeypatch):
44-
def inner(method, response_dict, status_code, headers=None):
45-
def mock(*args, **kwargs):
46-
return MockResponse(response_dict, status_code, headers=headers)
55+
def capture_and_mock_http_client_request(monkeypatch):
56+
def inner(
57+
http_client: HTTPClient,
58+
response_dict: Optional[dict] = None,
59+
status_code: int = 200,
60+
headers: Optional[Mapping[str, str]] = None,
61+
):
62+
request_kwargs = {}
4763

48-
monkeypatch.setattr(requests, method, mock)
64+
def capture_and_mock(*args, **kwargs):
65+
request_kwargs.update(kwargs)
4966

50-
return inner
67+
return httpx.Response(
68+
status_code=status_code,
69+
headers=headers,
70+
json=response_dict,
71+
)
5172

73+
mock_class = (
74+
AsyncMock if isinstance(http_client, AsyncHTTPClient) else MagicMock
75+
)
76+
mock = mock_class(side_effect=capture_and_mock)
5277

53-
@pytest.fixture
54-
def mock_raw_request_method(monkeypatch):
55-
def inner(method, content, status_code, headers=None):
56-
def mock(*args, **kwargs):
57-
return MockRawResponse(content, status_code, headers=headers)
78+
monkeypatch.setattr(http_client._client, "request", mock)
5879

59-
monkeypatch.setattr(requests, method, mock)
80+
return request_kwargs
6081

6182
return inner
6283

6384

6485
@pytest.fixture
65-
def capture_and_mock_request(monkeypatch):
66-
def inner(method, response_dict, status_code, headers=None):
67-
request_args = []
68-
request_kwargs = {}
86+
def mock_pagination_request_for_http_client(monkeypatch):
87+
# Mocking pagination correctly requires us to index into a list of data
88+
# and correctly set the before and after metadata in the response.
89+
def inner(
90+
http_client: HTTPClient,
91+
data_list: list,
92+
status_code: int = 200,
93+
headers: Optional[Mapping[str, str]] = None,
94+
):
95+
# For convenient index lookup, store the list of object IDs.
96+
data_ids = list(map(lambda x: x["id"], data_list))
97+
98+
def mock_function(*args, **kwargs):
99+
params = kwargs.get("params") or {}
100+
request_after = params.get("after", None)
101+
limit = params.get("limit", 10)
102+
103+
if request_after is None:
104+
# First page
105+
start = 0
106+
else:
107+
# A subsequent page, return the first item _after_ the index we locate
108+
start = data_ids.index(request_after) + 1
109+
data = data_list[start : start + limit]
110+
if len(data) < limit or len(data) == 0:
111+
# No more data, set after to None
112+
after = None
113+
else:
114+
# Set after to the last item in this page of results
115+
after = data[-1]["id"]
116+
117+
return httpx.Response(
118+
status_code=status_code,
119+
headers=headers,
120+
json=list_response_of(data=data, before=request_after, after=after),
121+
)
122+
123+
mock_class = (
124+
AsyncMock if isinstance(http_client, AsyncHTTPClient) else MagicMock
125+
)
126+
mock = mock_class(side_effect=mock_function)
127+
128+
monkeypatch.setattr(http_client._client, "request", mock)
69129

70-
def capture_and_mock(*args, **kwargs):
71-
request_args.extend(args)
72-
request_kwargs.update(kwargs)
73-
74-
return MockResponse(response_dict, status_code, headers=headers)
130+
return inner
75131

76-
monkeypatch.setattr(requests, method, capture_and_mock)
77132

78-
return (request_args, request_kwargs)
133+
@pytest.fixture
134+
def test_sync_auto_pagination(
135+
mock_pagination_request_for_http_client,
136+
):
137+
def inner(
138+
http_client: SyncHTTPClient,
139+
list_function: Callable[[], WorkOSListResource],
140+
expected_all_page_data: dict,
141+
list_function_params: Optional[Mapping[str, Any]] = None,
142+
):
143+
mock_pagination_request_for_http_client(
144+
http_client=http_client,
145+
data_list=expected_all_page_data,
146+
status_code=200,
147+
)
148+
149+
results = list_function(**list_function_params or {})
150+
all_results = []
151+
152+
for result in results:
153+
all_results.append(result)
154+
155+
assert len(list(all_results)) == len(expected_all_page_data)
156+
assert (list_data_to_dicts(all_results)) == expected_all_page_data
79157

80158
return inner

0 commit comments

Comments
 (0)