Skip to content

Commit 7805618

Browse files
committed
code quality improvements
1 parent a4bdaaa commit 7805618

7 files changed

Lines changed: 345 additions & 151 deletions

File tree

.claude/settings.local.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"WebFetch(domain:portfolio.sharesight.com)",
5+
"Bash(python -c:*)",
6+
"Bash(pip install:*)"
7+
]
8+
}
9+
}

README.md

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@
22

33
API to interface with Sharesight's v2 API
44

5-
- Supports PUSH, PUT, DELETE and GET requests
6-
- Only supports a single portfolio request per instance of the API
5+
- Supports POST, PUT, PATCH, DELETE and GET requests
6+
- Automatic retry with exponential backoff for transient errors (429, 500, 502, 503)
7+
- Custom exception classes for structured error handling
8+
- Async context manager support (`async with`)
9+
- Convenience methods for common API operations
710

811
# **How to use** #
9-
See the pytest.py file for an example
12+
See the example.py file for an example
1013

11-
This whole thing is designed to be asynchronous
14+
This whole thing is designed to be asynchronous
1215

1316
Added support for refresh token, no need to feed in clientID, clientSecret or authCode if token file exists
1417

15-
This API was designed to handle all the tokens requirements, but you are able to manage it yourself, removing the need to use get_token_data()
18+
This API was designed to handle all the tokens requirements, but you are able to manage it yourself, removing the need to use get_token_data()
1619
and validate_token(), by passing the access token into get_api_request().
1720

1821
# **How to install** #
1922
Do ```pip install SharesightAPI```
2023

21-
# **How to test using pytest.py** #
24+
# **How to test using example.py** #
2225

23-
To test the API, run the pytest.py file, with the variables in blank filled in, it will update specific post specific
26+
To test the API, run the example.py file, with the variables in blank filled in, it will update specific post specific
2427
data points to the console, and a json file with the output will be made
2528

2629
# **How to get API token** #
@@ -31,24 +34,28 @@ Read [here](https://portfolio.sharesight.com/api/) (you may need to get in conta
3134

3235
To start, call and assign (like this)
3336

34-
`sharesight = SharesightAPI.SharesightAPI(client_id, client_secret, authorization_code, redirect_uri, token_url, api_url_base, use_token_file, True, token_file_name)`
35-
36-
Sharesight has some recommendations for defaults as seen [here](https://portfolio.sharesight.com/api/2/authentication_flow):
37+
```python
38+
sharesight = SharesightAPI.SharesightAPI(client_id, client_secret, authorization_code, redirect_uri, token_url, api_url_base)
39+
```
3740

41+
Or use the async context manager:
3842

43+
```python
44+
async with SharesightAPI.SharesightAPI(client_id, client_secret, authorization_code, redirect_uri, token_url, api_url_base) as sharesight:
45+
access_token = await sharesight.validate_token()
46+
# ... use the API
47+
```
3948

49+
Sharesight has some recommendations for defaults as seen [here](https://portfolio.sharesight.com/api/2/authentication_flow):
4050

4151
+ redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
4252
+ token_url = 'https://api.sharesight.com/oauth2/token'
4353
+ api_url_base = 'https://api.sharesight.com/api/v2/'
4454

4555
I have assumed some things (if left blank):
4656

47-
+ token_file = 'sharesight_token.txt'
57+
+ token_file = 'sharesight_token_<client_id>.txt'
4858
+ debugging = False
49-
+ useEdge = False (Edge is the developer testing profile/account, it has a different root URL)
50-
51-
Setting token_file to be 'HA.txt' will append the client ID to sharesight_token. eg: sharesight_token_4123213214123.txt
5259

5360
Then; to get the existing data contained within the token file (optional), run this to get the values and store it within the constructor:
5461

@@ -61,10 +68,44 @@ This returns the current access_token, which can be passed in to use in API call
6168

6269
`access_token = await sharesight.validate_token()`
6370

71+
# **Convenience Methods** #
72+
73+
Instead of constructing endpoint lists manually, you can use built-in convenience methods:
74+
75+
```python
76+
# List all portfolios
77+
portfolios = await sharesight.list_portfolios()
78+
79+
# Get a specific portfolio
80+
portfolio = await sharesight.get_portfolio(portfolio_id)
81+
82+
# Get portfolio performance (with optional date range)
83+
performance = await sharesight.get_portfolio_performance(portfolio_id, start_date="2024-01-01", end_date="2024-12-31")
84+
85+
# List holdings in a portfolio
86+
holdings = await sharesight.list_holdings(portfolio_id)
87+
88+
# Get a specific holding
89+
holding = await sharesight.get_holding(holding_id)
90+
91+
# List trades
92+
trades = await sharesight.list_trades(portfolio_id)
93+
94+
# Create a trade
95+
trade = await sharesight.create_trade(portfolio_id, trade_data)
6496

97+
# Cash accounts
98+
cash_accounts = await sharesight.list_cash_accounts()
99+
cash_account = await sharesight.get_cash_account(cash_account_id)
65100

66-
To make an API call (get): call .get_api_request(endpoint, endpoint_list_version), making the endpoint being a list of the API version, the call URL, and the params if applicable. It will return a dictionary with the response.
67-
You are able to parse through the access_token, otherwise it will default to the current access token in the constructor.
101+
# Groups
102+
groups = await sharesight.list_groups()
103+
```
104+
105+
# **Raw API Requests** #
106+
107+
To make an API call (get): call .get_api_request(endpoint), making the endpoint being a list of the API version, the call URL, and the params if applicable. It will return a dictionary with the response.
108+
You are able to parse through the access_token, otherwise it will default to the current access token in the constructor.
68109

69110
example: `await sharesight.get_api_request(["v2","portfolios", None])`
70111

@@ -80,19 +121,45 @@ you can see a full list of v2 endpoints [here](https://portfolio.sharesight.com/
80121

81122
Call `delete_token()` to remove the Token file from the instance (will cause a new auth_code to be needed)
82123

83-
To close the connection, call `close()`
124+
To close the connection, call `close()` (or use the `async with` context manager for automatic cleanup)
125+
126+
# **Custom Exceptions** #
127+
128+
The library provides custom exception classes for structured error handling:
129+
130+
```python
131+
from SharesightAPI import SharesightError, SharesightAuthError, SharesightAPIError, SharesightRateLimitError
132+
```
133+
134+
- `SharesightError` - Base exception for all Sharesight API errors
135+
- `SharesightAuthError` - Authentication failures
136+
- `SharesightAPIError` - API request failures (has `status_code`, `message`, `response_data` attributes)
137+
- `SharesightRateLimitError` - Rate limiting (429) with optional `retry_after` attribute
138+
139+
# **Retry Configuration** #
140+
141+
The client automatically retries on transient errors (429, 500, 502, 503) with exponential backoff:
142+
143+
```python
144+
sharesight = SharesightAPI.SharesightAPI(
145+
client_id, client_secret, authorization_code, redirect_uri, token_url, api_url_base,
146+
max_retries=3, # Maximum retry attempts (default: 3)
147+
retry_backoff=1.0 # Base backoff time in seconds (default: 1.0)
148+
)
149+
```
150+
151+
For 429 responses, the `Retry-After` header is respected when present.
84152

85153
# **Manual Token Handling** #
86154

87155
This is an alternative to saving the token in the current directory, this allows you to handle all the token functions.
88156

89-
To store your own token data elsewhere, call `return_token()` to gets the currently saved token information, which can be called after the token is validated (see pytest for more details)
157+
To store your own token data elsewhere, call `return_token()` to gets the currently saved token information, which can be called after the token is validated (see example.py for more details)
90158

91159
(This removes the need for save_token and load_token methods, as you'll be handling it, but the token will still be refreshed)
92160

93-
Token data is returned like this:
161+
Token data is returned like this:
94162

95163
`{ 'auth_code': 12345, 'access_token': 12345, 'token_expiry': 12345, 'refresh_token': 12345 }`
96164

97165
To then inject your token into the API, you need to call `inject_token(token_data)` where token_data is the token, in the same format as above
98-

0 commit comments

Comments
 (0)