Skip to content

Commit

Permalink
style: format code with Black and isort
Browse files Browse the repository at this point in the history
This commit fixes the style issues introduced in 351a5d7 according to the output
from Black and isort.

Details: None
  • Loading branch information
deepsource-autofix[bot] committed Apr 10, 2024
1 parent 351a5d7 commit ce571a5
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 53 deletions.
2 changes: 1 addition & 1 deletion fennel_invest_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from fennel_invest_api.endpoints import Endpoints
from fennel_invest_api.fennel import Fennel

__all__ = ["Endpoints", "Fennel"]
__all__ = ["Endpoints", "Fennel"]
30 changes: 11 additions & 19 deletions fennel_invest_api/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@ class Endpoints:
def __init__(self):
self.accounts = "https://accounts.fennel.com"
self.graphql = "https://fennel-api.prod.fennel.com/graphql/"

def retrieve_bearer_url(self):
return f"{self.accounts}/passwordless/start"

def oauth_url(self):
return f"{self.accounts}/oauth/token"

def build_graphql_payload(self, query, variables={}):
return {
"operationName": None,
"variables": variables,
"query": query
}
return {"operationName": None, "variables": variables, "query": query}

def portfolio_query(self):
query = """
Expand All @@ -41,7 +37,7 @@ def portfolio_query(self):
}
"""
return json.dumps(self.build_graphql_payload(query))

def stock_holdings_query(self):
query = """
query MinimumPortfolioData {
Expand Down Expand Up @@ -86,10 +82,7 @@ def stock_search_query(self, symbol, count=5):
}
}
"""
variables = {
"query": symbol,
"count": count
}
variables = {"query": symbol, "count": count}
return json.dumps(self.build_graphql_payload(query, variables))

def stock_order_query(self, symbol, quantity, isin, side, priceRule):
Expand All @@ -111,17 +104,16 @@ def stock_order_query(self, symbol, quantity, isin, side, priceRule):
}
return json.dumps(self.build_graphql_payload(query, variables))


@staticmethod
def build_headers(Bearer, graphql=True):
headers = {
'accept': '*/*',
'accept-encoding': 'gzip',
'authorization': f"Bearer {Bearer}",
'content-type': 'application/json',
'host': 'fennel-api.prod.fennel.com',
'user-agent': 'Dart/3.3 (dart:io)',
"accept": "*/*",
"accept-encoding": "gzip",
"authorization": f"Bearer {Bearer}",
"content-type": "application/json",
"host": "fennel-api.prod.fennel.com",
"user-agent": "Dart/3.3 (dart:io)",
}
if not graphql:
headers['host'] = 'accounts.fennel.com'
headers["host"] = "accounts.fennel.com"
return headers
90 changes: 58 additions & 32 deletions fennel_invest_api/fennel.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def wrapper(self, *args, **kwargs):
if self.Bearer is None:
raise Exception("Bearer token is not set. Please login first.")
return func(self, *args, **kwargs)

return wrapper


Expand All @@ -22,7 +23,9 @@ def __init__(self, filename="fennel_credentials.pkl", path=None) -> None:
self.Refresh = None
self.ID_Token = None
self.timeout = 10
self.account_number = "00000000" # Fennel only has 1 account which they don't share
self.account_number = (
"00000000" # Fennel only has 1 account which they don't share
)
self.client_id = "FXGlhcVdamwozAFp8BZ2MWl6coPl6agX"
self.filename = filename
self.path = None
Expand All @@ -35,7 +38,7 @@ def _verify_filepath(self):
self.filename = os.path.join(self.path, self.filename)
if not os.path.exists(self.filename):
os.makedirs(self.path)

def _load_credentials(self):
self._verify_filepath()
if os.path.exists(self.filename):
Expand All @@ -45,16 +48,19 @@ def _load_credentials(self):
self.Refresh = credentials.get("Refresh")
self.ID_Token = credentials.get("ID_Token")
self.client_id = credentials.get("client_id", self.client_id)

def _save_credentials(self):
self._verify_filepath()
with open(self.filename, "wb") as f:
pickle.dump({
"Bearer": self.Bearer,
"Refresh": self.Refresh,
"ID_Token": self.ID_Token,
"client_id": self.client_id
}, f)
pickle.dump(
{
"Bearer": self.Bearer,
"Refresh": self.Refresh,
"ID_Token": self.ID_Token,
"client_id": self.client_id,
},
f,
)

def _clear_credentials(self):
self._verify_filepath()
Expand All @@ -77,7 +83,7 @@ def login(self, email, wait_for_code=True, code=None):
"email": email,
"client_id": self.client_id,
"connection": "email",
"send": "code"
"send": "code",
}
response = self.session.post(url, json=payload)
if response.status_code != 200:
Expand All @@ -94,15 +100,15 @@ def login(self, email, wait_for_code=True, code=None):
"username": email,
"scope": "openid profile offline_access email",
"audience": "https://meta.api.fennel.com/graphql",
"realm": "email"
"realm": "email",
}
response = self.session.post(url, json=payload)
if response.status_code != 200:
raise Exception(f"Failed to login: {response.text}")
response = response.json()
self.Bearer = response['access_token']
self.Refresh = response['refresh_token']
self.ID_Token = response['id_token']
self.Bearer = response["access_token"]
self.Refresh = response["refresh_token"]
self.ID_Token = response["id_token"]
# refresh_token() # Refresh token after login?
self._save_credentials()
return True
Expand All @@ -113,15 +119,15 @@ def refresh_token(self):
"grant_type": "refresh_token",
"client_id": self.client_id,
"refresh_token": self.Refresh,
"scope": "openid profile offline_access email"
"scope": "openid profile offline_access email",
}
response = self.session.post(url, json=payload)
if response.status_code != 200:
raise Exception(f"Failed to refresh bearer token: {response.text}")
response = response.json()
self.Bearer = f"{response['access_token']}"
self.Refresh = response['refresh_token']
self.ID_Token = response['id_token']
self.Refresh = response["refresh_token"]
self.ID_Token = response["id_token"]
return response

def _verify_login(self):
Expand All @@ -141,30 +147,42 @@ def _verify_login(self):
def get_portfolio_summary(self):
query = self.endpoints.portfolio_query()
headers = self.endpoints.build_headers(self.Bearer)
response = self.session.post(self.endpoints.graphql, headers=headers, data=query)
response = self.session.post(
self.endpoints.graphql, headers=headers, data=query
)
if response.status_code != 200:
raise Exception(f"Portfolio Request failed with status code {response.status_code}: {response.text}")
raise Exception(
f"Portfolio Request failed with status code {response.status_code}: {response.text}"
)
return response.json()

@check_login
def get_stock_holdings(self):
query = self.endpoints.stock_holdings_query()
headers = self.endpoints.build_headers(self.Bearer)
response = self.session.post(self.endpoints.graphql, headers=headers, data=query)
response = self.session.post(
self.endpoints.graphql, headers=headers, data=query
)
if response.status_code != 200:
raise Exception(f"Stock Holdings Request failed with status code {response.status_code}: {response.text}")
raise Exception(
f"Stock Holdings Request failed with status code {response.status_code}: {response.text}"
)
response = response.json()
return response['data']['portfolio']['bulbs']
return response["data"]["portfolio"]["bulbs"]

@check_login
def is_market_open(self):
query = self.endpoints.is_market_open_query()
headers = self.endpoints.build_headers(self.Bearer)
response = self.session.post(self.endpoints.graphql, headers=headers, data=query)
response = self.session.post(
self.endpoints.graphql, headers=headers, data=query
)
if response.status_code != 200:
raise Exception(f"Market Open Request failed with status code {response.status_code}: {response.text}")
raise Exception(
f"Market Open Request failed with status code {response.status_code}: {response.text}"
)
response = response.json()
return response['data']['securityMarketInfo']['isOpen']
return response["data"]["securityMarketInfo"]["isOpen"]

@check_login
def place_order(self, ticker, quantity, side, price="market"):
Expand All @@ -176,14 +194,22 @@ def place_order(self, ticker, quantity, side, price="market"):
# Search for stock "isin"
query = self.endpoints.stock_search_query(ticker)
headers = self.endpoints.build_headers(self.Bearer)
search_response = self.session.post(self.endpoints.graphql, headers=headers, data=query)
search_response = self.session.post(
self.endpoints.graphql, headers=headers, data=query
)
if search_response.status_code != 200:
raise Exception(f"Stock Search Request failed with status code {search_response.status_code}: {search_response.text}")
raise Exception(
f"Stock Search Request failed with status code {search_response.status_code}: {search_response.text}"
)
search_response = search_response.json()
isin = search_response['data']['searchSearch']['searchSecurities'][0]['isin']
isin = search_response["data"]["searchSearch"]["searchSecurities"][0]["isin"]
# Place order
query = self.endpoints.stock_order_query(ticker, quantity, isin, side, price)
order_response = self.session.post(self.endpoints.graphql, headers=headers, data=query)
order_response = self.session.post(
self.endpoints.graphql, headers=headers, data=query
)
if order_response.status_code != 200:
raise Exception(f"Order Request failed with status code {order_response.status_code}: {order_response.text}")
return order_response.json()
raise Exception(
f"Order Request failed with status code {order_response.status_code}: {order_response.text}"
)
return order_response.json()
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
author="Nelson Dane",
packages=["fennel_invest_api"],
install_requires=["requests", "python-dotenv"],
)
)

0 comments on commit ce571a5

Please sign in to comment.