Jitendra12421 commited on
Commit
5bb1bb4
·
verified ·
1 Parent(s): 83edf2f

Upload 7 files

Browse files
Files changed (1) hide show
  1. kotak_neo.py +95 -7
kotak_neo.py CHANGED
@@ -6,6 +6,8 @@ from datetime import datetime, timezone
6
  from typing import Any
7
  from urllib.parse import quote
8
 
 
 
9
  import requests
10
 
11
 
@@ -199,18 +201,46 @@ class KotakNeoManager:
199
  with self._lock:
200
  self._ensure_authenticated_locked()
201
 
202
- holdings_raw = self._request_trading_api_locked("portfolio/v1/holdings")
203
- positions_raw = self._request_trading_api_locked("quick/user/positions")
204
- trades_raw = self._request_trading_api_locked("quick/user/trades")
205
- orders_raw = self._request_trading_api_locked("quick/user/orders")
206
- limits_raw = self._request_trading_api_locked("quick/user/limits")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
  holdings = _extract_items(holdings_raw)
209
  positions = _extract_items(positions_raw)
210
  trades = sorted(_extract_items(trades_raw), key=_sort_key, reverse=True)
211
  orders = sorted(_extract_items(orders_raw), key=_sort_key, reverse=True)
212
 
213
- quotes = self._fetch_quotes_locked(self._instrument_tokens_for_quotes(holdings, positions))
 
 
 
 
214
  quote_map = self._build_quote_map(quotes)
215
 
216
  normalized_holdings = [self._normalize_holding(item, quote_map) for item in holdings]
@@ -293,6 +323,45 @@ class KotakNeoManager:
293
  self._raise_for_error(response, data, session_sensitive=True)
294
  return data
295
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  def _fetch_quotes_locked(self, instrument_tokens: list[dict[str, str]]) -> dict[str, Any]:
297
  if not instrument_tokens:
298
  return {"data": []}
@@ -325,6 +394,21 @@ class KotakNeoManager:
325
  return parsed
326
  return {"data": parsed}
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  def _raise_for_error(
329
  self,
330
  response: requests.Response,
@@ -341,12 +425,16 @@ class KotakNeoManager:
341
  data.get("emsg"),
342
  )
343
 
344
- if response.status_code == 403 or stat == "not_ok":
 
345
  if session_sensitive:
346
  self._clear_session_locked()
347
  raise KotakNeoSessionRequired(message or "Kotak Neo session expired.")
348
  raise KotakNeoError(message or "Kotak Neo rejected the request.")
349
 
 
 
 
350
  if response.status_code >= 400 or (st_code is not None and st_code >= 400):
351
  raise KotakNeoError(message or f"Kotak Neo request failed with HTTP {response.status_code}.")
352
 
 
6
  from typing import Any
7
  from urllib.parse import quote
8
 
9
+ import json
10
+
11
  import requests
12
 
13
 
 
201
  with self._lock:
202
  self._ensure_authenticated_locked()
203
 
204
+ holdings_raw = self._safe_account_call_locked(
205
+ "holdings",
206
+ lambda: self._request_trading_api_locked("portfolio/v1/holdings"),
207
+ default={"data": []},
208
+ )
209
+ positions_raw = self._safe_account_call_locked(
210
+ "positions",
211
+ lambda: self._request_trading_api_locked("quick/user/positions"),
212
+ default={"data": []},
213
+ )
214
+ trades_raw = self._safe_account_call_locked(
215
+ "trades",
216
+ lambda: self._request_trading_api_locked("quick/user/trades"),
217
+ default={"data": []},
218
+ )
219
+ orders_raw = self._safe_account_call_locked(
220
+ "orders",
221
+ lambda: self._request_trading_api_locked("quick/user/orders"),
222
+ default={"data": []},
223
+ )
224
+ limits_raw = self._safe_account_call_locked(
225
+ "limits",
226
+ lambda: self._post_trading_api_locked(
227
+ "quick/user/limits",
228
+ payload={"seg": "ALL", "exch": "ALL", "prod": "ALL"},
229
+ content_type="application/x-www-form-urlencoded",
230
+ ),
231
+ default={},
232
+ )
233
 
234
  holdings = _extract_items(holdings_raw)
235
  positions = _extract_items(positions_raw)
236
  trades = sorted(_extract_items(trades_raw), key=_sort_key, reverse=True)
237
  orders = sorted(_extract_items(orders_raw), key=_sort_key, reverse=True)
238
 
239
+ quotes = self._safe_account_call_locked(
240
+ "quotes",
241
+ lambda: self._fetch_quotes_locked(self._instrument_tokens_for_quotes(holdings, positions)),
242
+ default={"data": []},
243
+ )
244
  quote_map = self._build_quote_map(quotes)
245
 
246
  normalized_holdings = [self._normalize_holding(item, quote_map) for item in holdings]
 
323
  self._raise_for_error(response, data, session_sensitive=True)
324
  return data
325
 
326
+ def _post_trading_api_locked(
327
+ self,
328
+ path: str,
329
+ payload: dict[str, Any],
330
+ *,
331
+ content_type: str = "application/json",
332
+ ) -> dict[str, Any]:
333
+ self._ensure_authenticated_locked()
334
+ headers = {
335
+ "Sid": self.edit_sid or "",
336
+ "Auth": self.edit_token or "",
337
+ "Accept": "application/json",
338
+ "Content-Type": content_type,
339
+ }
340
+ query_params = {"sId": self.server_id or ""}
341
+ url = f"{self.base_url.rstrip('/')}/{path.lstrip('/')}"
342
+
343
+ if content_type == "application/x-www-form-urlencoded":
344
+ body = {"jData": json.dumps(payload)}
345
+ response = requests.post(
346
+ url,
347
+ headers=headers,
348
+ params=query_params,
349
+ data=body,
350
+ timeout=DEFAULT_TIMEOUT_SECONDS,
351
+ )
352
+ else:
353
+ response = requests.post(
354
+ url,
355
+ headers=headers,
356
+ params=query_params,
357
+ json=payload,
358
+ timeout=DEFAULT_TIMEOUT_SECONDS,
359
+ )
360
+
361
+ data = self._decode_response(response)
362
+ self._raise_for_error(response, data, session_sensitive=True)
363
+ return data
364
+
365
  def _fetch_quotes_locked(self, instrument_tokens: list[dict[str, str]]) -> dict[str, Any]:
366
  if not instrument_tokens:
367
  return {"data": []}
 
394
  return parsed
395
  return {"data": parsed}
396
 
397
+ def _safe_account_call_locked(
398
+ self,
399
+ label: str,
400
+ fn,
401
+ *,
402
+ default: dict[str, Any],
403
+ ) -> dict[str, Any]:
404
+ try:
405
+ return fn()
406
+ except KotakNeoSessionRequired:
407
+ raise
408
+ except Exception as exc:
409
+ print(f"[kotak] {label} call failed: {exc}", flush=True)
410
+ return default
411
+
412
  def _raise_for_error(
413
  self,
414
  response: requests.Response,
 
425
  data.get("emsg"),
426
  )
427
 
428
+ session_expired = response.status_code == 403 or "invalid session" in (message or "").lower()
429
+ if session_expired:
430
  if session_sensitive:
431
  self._clear_session_locked()
432
  raise KotakNeoSessionRequired(message or "Kotak Neo session expired.")
433
  raise KotakNeoError(message or "Kotak Neo rejected the request.")
434
 
435
+ if stat == "not_ok":
436
+ raise KotakNeoError(message or "Kotak Neo rejected the request.")
437
+
438
  if response.status_code >= 400 or (st_code is not None and st_code >= 400):
439
  raise KotakNeoError(message or f"Kotak Neo request failed with HTTP {response.status_code}.")
440