11import os
22import pickle
3- import pyotp
43
4+ import pyotp
55import requests
66
77from firstrade import urls
8- from firstrade .exceptions import LoginRequestError , LoginResponseError , AccountResponseError
8+ from firstrade .exceptions import (
9+ AccountResponseError ,
10+ LoginRequestError ,
11+ LoginResponseError ,
12+ )
913
1014
1115class FTSession :
12-
1316 """
1417 Class creating a session for Firstrade.
1518
@@ -47,7 +50,17 @@ class FTSession:
4750 _handle_mfa():
4851 Handles multi-factor authentication.
4952 """
50- def __init__ (self , username , password , pin = None , email = None , phone = None , mfa_secret = None , profile_path = None ):
53+
54+ def __init__ (
55+ self ,
56+ username ,
57+ password ,
58+ pin = None ,
59+ email = None ,
60+ phone = None ,
61+ mfa_secret = None ,
62+ profile_path = None ,
63+ ):
5164 """
5265 Initializes a new instance of the FTSession class.
5366
@@ -88,7 +101,7 @@ def login(self):
88101 self .session .headers ["ftat" ] = ftat
89102 response = self .session .get (url = "https://api3x.firstrade.com/" , timeout = 10 )
90103 self .session .headers ["access-token" ] = urls .access_token ()
91-
104+
92105 data = {
93106 "username" : r"" + self .username ,
94107 "password" : r"" + self .password ,
@@ -99,7 +112,11 @@ def login(self):
99112 data = data ,
100113 )
101114 self .login_json = response .json ()
102- if "mfa" not in self .login_json and "ftat" in self .login_json and self .login_json ["error" ] == "" :
115+ if (
116+ "mfa" not in self .login_json
117+ and "ftat" in self .login_json
118+ and self .login_json ["error" ] == ""
119+ ):
103120 self .session .headers ["sid" ] = self .login_json ["sid" ]
104121 return False
105122 self .t_token = self .login_json .get ("t_token" )
@@ -108,10 +125,10 @@ def login(self):
108125 if response .status_code != 200 :
109126 raise LoginRequestError (response .status_code )
110127 if self .login_json ["error" ] != "" :
111- raise LoginResponseError (self .login_json [' error' ])
128+ raise LoginResponseError (self .login_json [" error" ])
112129 need_code = self ._handle_mfa ()
113- if self .login_json ["error" ]!= "" :
114- raise LoginResponseError (self .login_json [' error' ])
130+ if self .login_json ["error" ] != "" :
131+ raise LoginResponseError (self .login_json [" error" ])
115132 if need_code :
116133 return True
117134 self .session .headers ["ftat" ] = self .login_json ["ftat" ]
@@ -126,23 +143,23 @@ def login_two(self, code):
126143 "verificationSid" : self .session .headers ["sid" ],
127144 "remember_for" : "30" ,
128145 "t_token" : self .t_token ,
129- }
146+ }
130147 response = self .session .post (urls .verify_pin (), data = data )
131148 self .login_json = response .json ()
132- if self .login_json ["error" ]!= "" :
133- raise LoginResponseError (self .login_json [' error' ])
149+ if self .login_json ["error" ] != "" :
150+ raise LoginResponseError (self .login_json [" error" ])
134151 self .session .headers ["ftat" ] = self .login_json ["ftat" ]
135152 self .session .headers ["sid" ] = self .login_json ["sid" ]
136153 self ._save_cookies ()
137-
138- def delete_cookies (self ):
154+
155+ def delete_cookies (self ):
139156 """Deletes the session cookies."""
140157 if self .profile_path is not None :
141158 path = os .path .join (self .profile_path , f"ft_cookies{ self .username } .pkl" )
142159 else :
143160 path = f"ft_cookies{ self .username } .pkl"
144161 os .remove (path )
145-
162+
146163 def _load_cookies (self ):
147164 """
148165 Checks if session cookies were saved.
@@ -152,7 +169,9 @@ def _load_cookies(self):
152169 """
153170
154171 ftat = ""
155- directory = os .path .abspath (self .profile_path ) if self .profile_path is not None else "."
172+ directory = (
173+ os .path .abspath (self .profile_path ) if self .profile_path is not None else "."
174+ )
156175 if not os .path .exists (directory ):
157176 os .makedirs (directory )
158177
@@ -162,7 +181,7 @@ def _load_cookies(self):
162181 with open (filepath , "rb" ) as f :
163182 ftat = pickle .load (f )
164183 return ftat
165-
184+
166185 def _save_cookies (self ):
167186 """Saves session cookies to a file."""
168187 if self .profile_path is not None :
@@ -175,7 +194,7 @@ def _save_cookies(self):
175194 with open (path , "wb" ) as f :
176195 ftat = self .session .headers .get ("ftat" )
177196 pickle .dump (ftat , f )
178-
197+
179198 @staticmethod
180199 def _mask_email (email ):
181200 """
@@ -187,12 +206,12 @@ def _mask_email(email):
187206 Returns:
188207 str: The masked email address.
189208 """
190- local , domain = email .split ('@' )
191- masked_local = local [0 ] + '*' * 4
192- domain_name , tld = domain .split ('.' )
193- masked_domain = domain_name [0 ] + '*' * 4
209+ local , domain = email .split ("@" )
210+ masked_local = local [0 ] + "*" * 4
211+ domain_name , tld = domain .split ("." )
212+ masked_domain = domain_name [0 ] + "*" * 4
194213 return f"{ masked_local } @{ masked_domain } .{ tld } "
195-
214+
196215 def _handle_mfa (self ):
197216 """
198217 Handles multi-factor authentication.
@@ -207,11 +226,13 @@ def _handle_mfa(self):
207226 data = {
208227 "pin" : self .pin ,
209228 "remember_for" : "30" ,
210- "t_token" : self .t_token ,
229+ "t_token" : self .t_token ,
211230 }
212231 response = self .session .post (urls .verify_pin (), data = data )
213232 self .login_json = response .json ()
214- elif not self .login_json ["mfa" ] and (self .email is not None or self .phone is not None ):
233+ elif not self .login_json ["mfa" ] and (
234+ self .email is not None or self .phone is not None
235+ ):
215236 for item in self .otp_options :
216237 if item ["channel" ] == "sms" and self .phone is not None :
217238 if self .phone in item ["recipientMask" ]:
@@ -223,7 +244,7 @@ def _handle_mfa(self):
223244 if self .email == item ["recipientMask" ]:
224245 data = {
225246 "recipientId" : item ["recipientId" ],
226- "t_token" : self .t_token ,
247+ "t_token" : self .t_token ,
227248 }
228249 response = self .session .post (urls .request_code (), data = data )
229250 elif self .login_json ["mfa" ] and self .mfa_secret is not None :
@@ -242,7 +263,6 @@ def _handle_mfa(self):
242263 self .session .headers ["sid" ] = self .login_json ["verificationSid" ]
243264 return True
244265
245-
246266 def __getattr__ (self , name ):
247267 """
248268 Forwards unknown attribute access to session object.
@@ -279,7 +299,7 @@ def __init__(self, session):
279299 self .all_accounts = response .json ()
280300 for item in self .all_accounts ["items" ]:
281301 self .account_numbers .append (item ["account" ])
282- self .account_balances [item [' account' ]] = item ["total_value" ]
302+ self .account_balances [item [" account" ]] = item ["total_value" ]
283303
284304 def get_account_balances (self , account ):
285305 """Gets account balances for a given account.
@@ -290,7 +310,7 @@ def get_account_balances(self, account):
290310 Returns:
291311 dict: Dict of the response from the API.
292312 """
293- response = self .session .get (urls .account_balances (account ))
313+ response = self .session .get (urls .account_balances (account ))
294314 return response .json ()
295315
296316 def get_positions (self , account ):
@@ -302,10 +322,10 @@ def get_positions(self, account):
302322 Returns:
303323 dict: Dict of the response from the API.
304324 """
305-
325+
306326 response = self .session .get (urls .account_positions (account ))
307327 return response .json ()
308-
328+
309329 def get_account_history (self , account ):
310330 """Gets account history for a given account.
311331
@@ -317,7 +337,7 @@ def get_account_history(self, account):
317337 """
318338 response = self .session .get (urls .account_history (account ))
319339 return response .json ()
320-
340+
321341 def get_orders (self , account ):
322342 """
323343 Retrieves existing order data for a given account.
@@ -348,7 +368,5 @@ def cancel_order(self, order_id):
348368 "order_id" : order_id ,
349369 }
350370
351- response = self .session .post (
352- url = urls .cancel_order (), data = data
353- )
354- return response .json ()
371+ response = self .session .post (url = urls .cancel_order (), data = data )
372+ return response .json ()
0 commit comments