Skip to content

Commit d64bc22

Browse files
authored
Merge pull request #32 from ebastos/main
Use `uv` and general clean-up
2 parents 7df8bff + 10488a3 commit d64bc22

13 files changed

Lines changed: 1515 additions & 402 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ Installation
77
------------
88

99
```bash
10-
pip install ws-api
10+
uv add ws-api
1111
```
1212

1313
Usage
1414
-----
1515

16-
Note: You'll need the keyring package to run the code below. Install with: `pip install keyring`
16+
Note: You'll need the keyring package to run the code below. Install with: `uv add keyring`
1717

1818
```python
1919
from datetime import datetime

pyproject.toml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "ws-api"
7+
version = "0.28.1"
8+
description = "Access your Wealthsimple account using their (GraphQL) API."
9+
readme = "README.md"
10+
license = {file = "LICENSE"}
11+
authors = [
12+
{name = "Guillaume Boudreau", email = "guillaume@pommepause.com"},
13+
]
14+
maintainers = [
15+
{name = "Guillaume Boudreau", email = "guillaume@pommepause.com"},
16+
]
17+
classifiers = [
18+
"Development Status :: 3 - Alpha",
19+
"Intended Audience :: Developers",
20+
"Operating System :: OS Independent",
21+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
22+
"Programming Language :: Python :: 3",
23+
"Programming Language :: Python :: 3.10",
24+
"Programming Language :: Python :: 3.13",
25+
]
26+
requires-python = ">=3.10"
27+
dependencies = [
28+
"requests",
29+
]
30+
keywords = ["wealthsimple"]
31+
32+
[project.optional-dependencies]
33+
dev = [
34+
"pytest",
35+
"ruff",
36+
]
37+
38+
[project.urls]
39+
Homepage = "https://github.com/gboudreau/ws-api-python"
40+
Repository = "https://github.com/gboudreau/ws-api-python"
41+
42+
[tool.hatch.build.targets.wheel]
43+
packages = ["ws_api"]
44+
45+
[dependency-groups]
46+
dev = [
47+
"pytest",
48+
"pytest-cov>=7.0.0",
49+
"ruff",
50+
]

setup.cfg

Lines changed: 0 additions & 3 deletions
This file was deleted.

setup.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

tests/test_exceptions.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from ws_api.exceptions import LoginFailedException, WSApiException
2+
3+
4+
def test_wsapi_exception_init_and_str():
5+
response = {"error": "test_error"}
6+
exc = WSApiException("Test message", response=response)
7+
assert str(exc) == "Test message; Response: {'error': 'test_error'}"
8+
assert exc.response == response
9+
10+
11+
def test_wsapi_exception_no_response():
12+
exc = WSApiException("Test message")
13+
assert str(exc) == "Test message; Response: None"
14+
assert exc.response is None
15+
16+
17+
def test_login_failed_exception():
18+
response = {"error": "invalid_grant"}
19+
exc = LoginFailedException("Login failed", response=response)
20+
assert isinstance(exc, WSApiException)
21+
assert str(exc) == "Login failed; Response: {'error': 'invalid_grant'}"

tests/test_session.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import json
2+
3+
from ws_api.session import OAuthSession, WSAPISession
4+
5+
6+
def test_oauth_session_init():
7+
session = OAuthSession()
8+
assert session.client_id is None
9+
assert session.access_token is None
10+
assert session.refresh_token is None
11+
12+
13+
def test_wsapi_session_init():
14+
session = WSAPISession()
15+
assert session.client_id is None
16+
assert session.access_token is None
17+
assert session.refresh_token is None
18+
assert session.session_id is None
19+
assert session.wssdi is None
20+
assert session.token_info is None
21+
22+
23+
def test_wsapi_session_to_json():
24+
session = WSAPISession()
25+
session.client_id = "test_client_id"
26+
session.access_token = "test_access_token"
27+
session.refresh_token = "test_refresh_token"
28+
session.session_id = "test_session_id"
29+
session.wssdi = "test_wssdi"
30+
session.token_info = {"key": "value"}
31+
32+
json_str = session.to_json()
33+
data = json.loads(json_str)
34+
35+
assert data["client_id"] == "test_client_id"
36+
assert data["access_token"] == "test_access_token"
37+
assert data["refresh_token"] == "test_refresh_token"
38+
assert data["session_id"] == "test_session_id"
39+
assert data["wssdi"] == "test_wssdi"
40+
assert data["token_info"] == {"key": "value"}
41+
42+
43+
def test_wsapi_session_from_json():
44+
data = {
45+
"client_id": "test_client_id",
46+
"access_token": "test_access_token",
47+
"refresh_token": "test_refresh_token",
48+
"session_id": "test_session_id",
49+
"wssdi": "test_wssdi",
50+
"token_info": {"key": "value"},
51+
}
52+
json_str = json.dumps(data)
53+
54+
session = WSAPISession.from_json(json_str)
55+
56+
assert session.client_id == "test_client_id"
57+
assert session.access_token == "test_access_token"
58+
assert session.refresh_token == "test_refresh_token"
59+
assert session.session_id == "test_session_id"
60+
assert session.wssdi == "test_wssdi"
61+
assert session.token_info == {"key": "value"}

tests/test_wealthsimple_api.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import uuid
2+
from unittest.mock import MagicMock, patch
3+
4+
import pytest
5+
6+
from ws_api.session import WSAPISession
7+
from ws_api.wealthsimple_api import WealthsimpleAPI, WealthsimpleAPIBase
8+
9+
10+
def test_wealthsimple_api_set_user_agent():
11+
WealthsimpleAPI.set_user_agent("Test User Agent")
12+
assert WealthsimpleAPI.user_agent == "Test User Agent"
13+
14+
15+
def test_wealthsimple_api_uuidv4():
16+
uuid_str = WealthsimpleAPI.uuidv4()
17+
assert isinstance(uuid_str, str)
18+
assert len(uuid_str) == 36
19+
uuid.UUID(uuid_str, version=4)
20+
21+
22+
@pytest.fixture
23+
def mock_session():
24+
sess = WSAPISession()
25+
sess.client_id = "test_client_id"
26+
sess.access_token = "test_access_token"
27+
sess.refresh_token = "test_refresh_token"
28+
sess.session_id = "test_session_id"
29+
sess.wssdi = "test_wssdi"
30+
return sess
31+
32+
33+
def test_wealthsimple_api_init_with_session(mock_session):
34+
api = WealthsimpleAPI(mock_session)
35+
assert api.session.client_id == "test_client_id"
36+
assert api.session.access_token == "test_access_token"
37+
assert api.session.refresh_token == "test_refresh_token"
38+
assert api.session.session_id == "test_session_id"
39+
assert api.session.wssdi == "test_wssdi"
40+
41+
42+
@patch("requests.request")
43+
def test_send_http_request_post(mock_request, mock_session):
44+
mock_resp = MagicMock()
45+
mock_resp.json.return_value = {"status": "ok"}
46+
mock_request.return_value = mock_resp
47+
48+
api = WealthsimpleAPIBase(mock_session)
49+
50+
result = api.send_http_request(
51+
"https://test.example.com/api",
52+
"POST",
53+
{"grant_type": "password", "username": "test", "password": "test"},
54+
)
55+
56+
assert result == {"status": "ok"}
57+
58+
mock_request.assert_called_once()
59+
args, kwargs = mock_request.call_args
60+
assert args[0] == "POST"
61+
assert args[1] == "https://test.example.com/api"
62+
assert kwargs["json"] == {
63+
"grant_type": "password",
64+
"username": "test",
65+
"password": "test",
66+
}
67+
headers = kwargs["headers"]
68+
assert headers["Content-Type"] == "application/json"
69+
assert headers["x-ws-session-id"] == "test_session_id"
70+
assert headers["Authorization"] == "Bearer test_access_token"
71+
assert headers["x-ws-device-id"] == "test_wssdi"
72+
73+
74+
@patch("requests.request")
75+
def test_send_get_request(mock_request, mock_session):
76+
mock_resp = MagicMock()
77+
mock_resp.json.return_value = {"status": "ok"}
78+
mock_request.return_value = mock_resp
79+
80+
api = WealthsimpleAPIBase(mock_session)
81+
82+
result = api.send_get("https://test.example.com/get")
83+
84+
assert result == {"status": "ok"}
85+
86+
mock_request.assert_called_once()
87+
args, kwargs = mock_request.call_args
88+
assert args[0] == "GET"
89+
assert args[1] == "https://test.example.com/get"
90+
headers = kwargs["headers"]
91+
assert "x-ws-session-id" in headers
92+
assert headers["x-ws-session-id"] == "test_session_id"

0 commit comments

Comments
 (0)