11import random
2+ import re
23from time import sleep
34import requests
45import webbrowser
@@ -85,7 +86,7 @@ def refresh_access_token(self):
8586 }
8687 if not self .post_token_request (data ):
8788 self .logger .error ("Failed to refresh access token." )
88- return False
89+ self . manual_authorization_flow ()
8990 self .token_info = self .load_token ()
9091 return self .validate_token ()
9192
@@ -119,7 +120,8 @@ def validate_token(self, force=False):
119120 print ("Token expired or invalid." )
120121 # get AAPL to validate token
121122 params = {'symbol' : 'AAPL' }
122- response = self .make_request (endpoint = f"{ self .config .MARKET_DATA_BASE_URL } /chains" , params = params , validating = True )
123+ response = self .make_request (endpoint = f"{ self .config .MARKET_DATA_BASE_URL } /chains" , params = params ,
124+ validating = True )
123125 print (response )
124126 if response :
125127 self .logger .info ("Token validated successfully." )
@@ -129,29 +131,76 @@ def validate_token(self, force=False):
129131 return False
130132
131133 def make_request (self , endpoint , method = "GET" , ** kwargs ):
134+ """
135+ Make authenticated HTTP requests.
136+
137+ Args:
138+ endpoint (str): The API endpoint.
139+ method (str, optional): The HTTP method. Defaults to "GET".
140+ **kwargs: Additional parameters for the request.
141+
142+ Returns:
143+ dict: The JSON response from the API if available, else None.
144+
145+ Raises:
146+ HTTPError: If the request fails.
147+ """
148+ # Introduce a random delay to avoid hitting the server too quickly
132149 sleep (0.5 + random .randint (0 , 1000 ) / 1000 )
133- """ Make authenticated HTTP requests. """
150+
151+ # Validate the token if not in a validation process
134152 if 'validating' not in kwargs :
135153 if not self .validate_token ():
136154 self .logger .info ("Token expired or invalid, re-authenticating." )
137- self .manual_authorization_flow ()
155+ self .refresh_access_token ()
138156 kwargs .pop ('validating' , None )
157+
158+ # Construct the full URL if the base URL is not included
139159 if self .config .API_BASE_URL not in endpoint :
140160 url = f"{ self .config .API_BASE_URL } { endpoint } "
141161 else :
142162 url = endpoint
143- print (f"Making request to { url } with method { method } and kwargs { kwargs } (validating already popped if present)" )
163+
164+ self .logger .debug (f"Making request to { url } with method { method } and kwargs { kwargs } " )
165+
166+ # Set the authorization headers
144167 headers = {'Authorization' : f"Bearer { self .token_info ['access_token' ]} " }
168+
169+ # Make the HTTP request
145170 response = self .session .request (method , url , headers = headers , ** kwargs )
146- print ( response . status_code )
147- print ( response . text )
171+
172+ # Handle token expiration during the request
148173 if response .status_code == 401 :
149174 self .logger .warning ("Token expired during request. Refreshing token..." )
150- self .manual_authorization_flow ()
175+ self .refresh_access_token ()
151176 headers = {'Authorization' : f"Bearer { self .token_info ['access_token' ]} " }
152177 response = self .session .request (method , url , headers = headers , ** kwargs )
178+
179+ # Log for non-200 responses
180+ if response .status_code != 200 :
181+ order_pattern = r"https://api\.schwabapi\.com/trader/v1/accounts/.*/orders"
182+ if response .status_code == 201 and re .match (order_pattern , url ):
183+ location = response .headers .get ('location' )
184+ if location :
185+ order_id = location .split ('/' )[- 1 ]
186+ self .logger .debug (f"Order placed successfully. Order ID: { order_id } " )
187+ return {"order_id" : order_id , "success" : True }
188+ else :
189+ self .logger .error ("201 response without a location header." )
190+ return None
191+
153192 response .raise_for_status ()
154- return response .json ()
193+
194+ # Check if response content is empty before parsing as JSON
195+ if response .content :
196+ try :
197+ return response .json ()
198+ except json .JSONDecodeError as e :
199+ self .logger .error (f"Error decoding JSON response: { e } " )
200+ raise requests .exceptions .JSONDecodeError (e .msg , e .doc , e .pos )
201+ else :
202+ self .logger .debug ("Empty response content" )
203+ return None
155204
156205 def get_user_preferences (self ):
157206 """Retrieve user preferences."""
0 commit comments