bw-essentials-core 0.1.17__py3-none-any.whl → 0.1.33__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bw_essentials/constants/services.py +1 -0
- bw_essentials/email_client/email_client.py +31 -18
- bw_essentials/services/api_client.py +36 -0
- bw_essentials/services/broker.py +8 -3
- bw_essentials/services/compliance.py +78 -0
- bw_essentials/services/market_pricer.py +71 -17
- bw_essentials/services/master_data.py +49 -0
- bw_essentials/services/model_portfolio_reporting.py +30 -1
- bw_essentials/services/notification.py +19 -15
- bw_essentials/services/payment.py +46 -1
- bw_essentials/services/portfolio_catalogue.py +61 -2
- bw_essentials/services/portfolio_content.py +1 -1
- bw_essentials/services/trade_placement.py +262 -9
- bw_essentials/services/user_portfolio.py +398 -1
- bw_essentials/services/user_portfolio_reporting.py +20 -1
- {bw_essentials_core-0.1.17.dist-info → bw_essentials_core-0.1.33.dist-info}/METADATA +1 -1
- bw_essentials_core-0.1.33.dist-info/RECORD +32 -0
- bw_essentials_core-0.1.17.dist-info/RECORD +0 -31
- {bw_essentials_core-0.1.17.dist-info → bw_essentials_core-0.1.33.dist-info}/WHEEL +0 -0
- {bw_essentials_core-0.1.17.dist-info → bw_essentials_core-0.1.33.dist-info}/top_level.txt +0 -0
|
@@ -132,7 +132,7 @@ class EmailClient:
|
|
|
132
132
|
cc_addresses: Union[str, List[str], None],
|
|
133
133
|
subject: str,
|
|
134
134
|
body: str,
|
|
135
|
-
attachment_path: str,
|
|
135
|
+
attachment_path: Optional[Union[str, List[str]]],
|
|
136
136
|
is_html: bool = True,
|
|
137
137
|
):
|
|
138
138
|
"""
|
|
@@ -143,7 +143,7 @@ class EmailClient:
|
|
|
143
143
|
cc_addresses (Union[str, List[str], None]): One or more CC addresses (optional).
|
|
144
144
|
subject (str): Email subject line.
|
|
145
145
|
body (str): Email body content.
|
|
146
|
-
attachment_path (str):
|
|
146
|
+
attachment_path (Optional[Union[str, List[str]]]): File path(s) for attachments.
|
|
147
147
|
is_html (bool): If True, body is interpreted as HTML; otherwise, plain text.
|
|
148
148
|
"""
|
|
149
149
|
logger.info(
|
|
@@ -158,7 +158,7 @@ class EmailClient:
|
|
|
158
158
|
cc_addresses: Union[str, List[str], None],
|
|
159
159
|
subject: str,
|
|
160
160
|
body: str,
|
|
161
|
-
attachment_path: Optional[str],
|
|
161
|
+
attachment_path: Optional[Union[str, List[str]]],
|
|
162
162
|
is_html: bool,
|
|
163
163
|
):
|
|
164
164
|
"""
|
|
@@ -169,7 +169,7 @@ class EmailClient:
|
|
|
169
169
|
cc_addresses (Union[str, List[str], None]): CC addresses.
|
|
170
170
|
subject (str): Subject of the email.
|
|
171
171
|
body (str): Email body content.
|
|
172
|
-
attachment_path (Optional[str]): File path for
|
|
172
|
+
attachment_path (Optional[Union[str, List[str]]]): File path(s) for attachments.
|
|
173
173
|
is_html (bool): True if the body is HTML-formatted.
|
|
174
174
|
"""
|
|
175
175
|
msg = MIMEMultipart()
|
|
@@ -180,23 +180,36 @@ class EmailClient:
|
|
|
180
180
|
|
|
181
181
|
msg.attach(MIMEText(body, 'html' if is_html else 'plain'))
|
|
182
182
|
logger.debug("Email headers and body constructed")
|
|
183
|
+
attachment_paths = []
|
|
183
184
|
|
|
184
185
|
if attachment_path:
|
|
185
|
-
if
|
|
186
|
-
|
|
186
|
+
if isinstance(attachment_path, list):
|
|
187
|
+
attachment_paths = attachment_path
|
|
187
188
|
else:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
189
|
+
attachment_paths = [attachment_path]
|
|
190
|
+
|
|
191
|
+
for path in attachment_paths:
|
|
192
|
+
if not path:
|
|
193
|
+
continue
|
|
194
|
+
if not os.path.exists(path):
|
|
195
|
+
logger.warning("Attachment file not found: %s", path)
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
with open(path, 'rb') as attachment:
|
|
200
|
+
part = MIMEBase('application', 'octet-stream')
|
|
201
|
+
part.set_payload(attachment.read())
|
|
202
|
+
encoders.encode_base64(part)
|
|
203
|
+
part.add_header(
|
|
204
|
+
'Content-Disposition',
|
|
205
|
+
'attachment',
|
|
206
|
+
filename=os.path.basename(path)
|
|
207
|
+
)
|
|
208
|
+
msg.attach(part)
|
|
209
|
+
logger.info("Attachment added: %s", os.path.basename(path))
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.exception("Failed to read attachment file: %s", path)
|
|
212
|
+
raise e
|
|
200
213
|
|
|
201
214
|
try:
|
|
202
215
|
with smtplib.SMTP(self.smtp_host, self.smtp_port) as server:
|
|
@@ -15,6 +15,7 @@ import logging
|
|
|
15
15
|
import os
|
|
16
16
|
import sys
|
|
17
17
|
import time
|
|
18
|
+
import httpx
|
|
18
19
|
from importlib.util import spec_from_file_location, module_from_spec
|
|
19
20
|
from typing import Optional, Dict, Any
|
|
20
21
|
|
|
@@ -195,6 +196,41 @@ class ApiClient:
|
|
|
195
196
|
"""
|
|
196
197
|
return self._request("get", url, endpoint, params=params)
|
|
197
198
|
|
|
199
|
+
async def _async_get(self, url: str, endpoint: str, headers: dict | None = None, params: dict | None = None):
|
|
200
|
+
"""
|
|
201
|
+
Async GET request, aligned with the sync _request() style.
|
|
202
|
+
"""
|
|
203
|
+
headers = self._update_headers(headers or {})
|
|
204
|
+
params = params or {}
|
|
205
|
+
formatted_url = f"{url.rstrip('/')}/{endpoint.lstrip('/')}"
|
|
206
|
+
|
|
207
|
+
logger.info(f"GET {formatted_url} | Headers: {headers} | Params: {params}")
|
|
208
|
+
|
|
209
|
+
start = time.time()
|
|
210
|
+
try:
|
|
211
|
+
async with httpx.AsyncClient() as client:
|
|
212
|
+
response = await client.get(formatted_url, headers=headers, params=params)
|
|
213
|
+
|
|
214
|
+
elapsed_time_ms = (time.time() - start) * 1000
|
|
215
|
+
|
|
216
|
+
# parse JSON safely
|
|
217
|
+
try:
|
|
218
|
+
json_data = response.json()
|
|
219
|
+
except ValueError:
|
|
220
|
+
logger.error(f"Non-JSON response from {formatted_url}")
|
|
221
|
+
json_data = None
|
|
222
|
+
|
|
223
|
+
self._log_response("GET", formatted_url, response.status_code, elapsed_time_ms, json_data)
|
|
224
|
+
|
|
225
|
+
response.raise_for_status()
|
|
226
|
+
return json_data
|
|
227
|
+
|
|
228
|
+
except Exception as exc:
|
|
229
|
+
elapsed_time_ms = (time.time() - start) * 1000
|
|
230
|
+
logger.error(f"GET {formatted_url} failed after {elapsed_time_ms:.2f}ms")
|
|
231
|
+
logger.exception(exc)
|
|
232
|
+
raise
|
|
233
|
+
|
|
198
234
|
def _post(
|
|
199
235
|
self,
|
|
200
236
|
url: str,
|
bw_essentials/services/broker.py
CHANGED
|
@@ -30,6 +30,7 @@ APIs supported:
|
|
|
30
30
|
- Acknowledge surveillance orders
|
|
31
31
|
"""
|
|
32
32
|
import logging
|
|
33
|
+
from typing import Optional
|
|
33
34
|
|
|
34
35
|
from bw_essentials.constants.services import Services
|
|
35
36
|
from bw_essentials.services.api_client import ApiClient
|
|
@@ -139,22 +140,26 @@ class Broker(ApiClient):
|
|
|
139
140
|
logger.info(f"{response =}")
|
|
140
141
|
return response["data"]
|
|
141
142
|
|
|
142
|
-
def get_holdings(self, broker_name: str, user_id: str) ->
|
|
143
|
+
def get_holdings(self, broker_name: str, user_id: str, product_type: Optional[str] = None) -> dict:
|
|
143
144
|
"""
|
|
144
145
|
Fetch user holdings from the broker service.
|
|
145
146
|
|
|
146
147
|
Args:
|
|
147
148
|
broker_name (str): Broker name.
|
|
148
149
|
user_id (str): User ID.
|
|
150
|
+
product_type (str, optional): Product type.
|
|
149
151
|
|
|
150
152
|
Returns:
|
|
151
|
-
|
|
153
|
+
dict: Dict of user holdings.
|
|
152
154
|
"""
|
|
153
155
|
logger.info(f"In - get_holdings {user_id =}")
|
|
156
|
+
params = {"user_id": user_id}
|
|
157
|
+
if product_type:
|
|
158
|
+
params['product_type'] = product_type
|
|
154
159
|
response = self._get(
|
|
155
160
|
url=self.base_url,
|
|
156
161
|
endpoint=self.urls["holdings"].format(broker_name),
|
|
157
|
-
params=
|
|
162
|
+
params=params
|
|
158
163
|
)
|
|
159
164
|
logger.info(f"{response =}")
|
|
160
165
|
return response["data"]["holdings"]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from bw_essentials.constants.services import Services
|
|
3
|
+
from bw_essentials.services.api_client import ApiClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Compliance(ApiClient):
|
|
10
|
+
"""
|
|
11
|
+
Compliance service integration class.
|
|
12
|
+
|
|
13
|
+
This class facilitates communication with the compliance service.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, user, tenant_id: str = None):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the Compliance service wrapper.
|
|
19
|
+
|
|
20
|
+
:param user: The user initiating the request.
|
|
21
|
+
"""
|
|
22
|
+
super().__init__(user)
|
|
23
|
+
self.base_url = self.get_base_url(Services.COMPLIANCE.value)
|
|
24
|
+
self.name = Services.COMPLIANCE.value
|
|
25
|
+
self.tenant_id = tenant_id
|
|
26
|
+
self.urls = {
|
|
27
|
+
"kra_data": "kra/data"
|
|
28
|
+
}
|
|
29
|
+
logger.info("Compliance service initialized.")
|
|
30
|
+
|
|
31
|
+
def _headers(self):
|
|
32
|
+
"""
|
|
33
|
+
Construct headers for compliance service requests.
|
|
34
|
+
|
|
35
|
+
:return: Dictionary of headers.
|
|
36
|
+
"""
|
|
37
|
+
return {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
**({'X-Tenant-ID': self.tenant_id} if self.tenant_id else {})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def get_kra_data(self, payload):
|
|
43
|
+
"""
|
|
44
|
+
Fetches KRA details by calling API.
|
|
45
|
+
|
|
46
|
+
This method prepares the request headers, constructs the API endpoint,
|
|
47
|
+
sends a POST request with the provided payload, and returns the
|
|
48
|
+
parsed KRA data received from the compliance service.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
payload (dict):
|
|
53
|
+
Dictionary containing the required fields for KRA lookup.
|
|
54
|
+
Example:
|
|
55
|
+
{
|
|
56
|
+
"pan": "ABCDE1234F",
|
|
57
|
+
"dob": "1985-05-12"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
dict:
|
|
62
|
+
Parsed KRA data dictionary returned by the Compliance Service.
|
|
63
|
+
Returns an empty dictionary if `data` is missing from the response.
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
{
|
|
67
|
+
"kra_status": "KYC_REGISTERED",
|
|
68
|
+
"name": "John Doe",
|
|
69
|
+
"dob": "1985-05-12",
|
|
70
|
+
...
|
|
71
|
+
}
|
|
72
|
+
"""
|
|
73
|
+
logger.info(f"In get_kra_data {payload =}")
|
|
74
|
+
url = f"{self.base_url}"
|
|
75
|
+
self.headers = self._headers()
|
|
76
|
+
kra_data = self._post(url=url, endpoint=self.urls.get('kra_data'), json=payload)
|
|
77
|
+
logger.info(f"Out get_kra_data {kra_data =}")
|
|
78
|
+
return kra_data.get('data', {})
|
|
@@ -76,14 +76,14 @@ class MarketPricer(ApiClient):
|
|
|
76
76
|
Retrieves live market prices for a list of securities on a specific exchange.
|
|
77
77
|
|
|
78
78
|
Args:
|
|
79
|
-
securities (
|
|
79
|
+
securities (list): List of security symbols for which live prices are requested.
|
|
80
80
|
exchange (str): The exchange on which the securities are traded.
|
|
81
81
|
Returns:
|
|
82
|
-
|
|
82
|
+
dict: A dictionary containing the live market price data for the specified securities.
|
|
83
83
|
|
|
84
84
|
Example:
|
|
85
85
|
market_pricer = MarketPricer(user)
|
|
86
|
-
securities = "TCS,RELIANCE"
|
|
86
|
+
securities = ["TCS", "RELIANCE"]
|
|
87
87
|
exchange = "NSE"
|
|
88
88
|
live_prices = market_pricer.get_live_prices(securities, exchange)
|
|
89
89
|
|
|
@@ -96,20 +96,21 @@ class MarketPricer(ApiClient):
|
|
|
96
96
|
|
|
97
97
|
API Response:
|
|
98
98
|
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
99
|
+
"data": {
|
|
100
|
+
"TCS": {
|
|
101
|
+
"security": "TCS",
|
|
102
|
+
"exchange": "NSE",
|
|
103
|
+
"price": 3207.8,
|
|
104
|
+
"timestamp": "2026-01-09 16:00:00"
|
|
105
|
+
},
|
|
106
|
+
"RELIANCE": {
|
|
107
|
+
"security": "RELIANCE",
|
|
108
|
+
"exchange": "NSE",
|
|
109
|
+
"price": 1475.3,
|
|
110
|
+
"timestamp": "2026-01-09 16:00:00"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"success": true
|
|
113
114
|
}
|
|
114
115
|
"""
|
|
115
116
|
logger.info(f"In - get_live_prices {securities =}, {exchange =}")
|
|
@@ -300,3 +301,56 @@ class MarketPricer(ApiClient):
|
|
|
300
301
|
data = response.get("data")
|
|
301
302
|
logger.info("Out - get_index_performance_by_dates success")
|
|
302
303
|
return data
|
|
304
|
+
|
|
305
|
+
def get_bulk_option_eod_prices(
|
|
306
|
+
self,
|
|
307
|
+
symbols,
|
|
308
|
+
from_date,
|
|
309
|
+
to_date,
|
|
310
|
+
exchange=None,
|
|
311
|
+
underlying_symbol=None,
|
|
312
|
+
option_type=None,
|
|
313
|
+
expiry_date=None,
|
|
314
|
+
):
|
|
315
|
+
"""
|
|
316
|
+
Fetch bulk EOD OHLCV data for multiple option symbols over a date range.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
symbols (list[str] | str): Option symbols as list or comma-separated string.
|
|
320
|
+
from_date (str): Start date in YYYY-MM-DD format.
|
|
321
|
+
to_date (str): End date in YYYY-MM-DD format.
|
|
322
|
+
exchange (str, optional): Exchange filter.
|
|
323
|
+
underlying_symbol (str, optional): Underlying instrument filter.
|
|
324
|
+
option_type (str, optional): CE or PE.
|
|
325
|
+
expiry_date (str, optional): Option expiry date.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
dict: Dictionary keyed by symbol containing EOD data.
|
|
329
|
+
"""
|
|
330
|
+
logger.info(f"In - get_bulk_option_eod_prices | {symbols=}, {from_date=}, {to_date=}")
|
|
331
|
+
|
|
332
|
+
if not all([symbols, from_date, to_date]):
|
|
333
|
+
raise ValueError("symbols, from_date and to_date are required")
|
|
334
|
+
|
|
335
|
+
symbols = ",".join(symbols) if isinstance(symbols, list) else symbols
|
|
336
|
+
|
|
337
|
+
params = {"symbols": symbols, "from_date": from_date, "to_date": to_date}
|
|
338
|
+
|
|
339
|
+
for key, value in {
|
|
340
|
+
"exchange": exchange,
|
|
341
|
+
"underlying_symbol": underlying_symbol,
|
|
342
|
+
"option_type": option_type,
|
|
343
|
+
"expiry_date": expiry_date,
|
|
344
|
+
}.items():
|
|
345
|
+
if value is not None:
|
|
346
|
+
params[key] = value
|
|
347
|
+
|
|
348
|
+
response = self._get(
|
|
349
|
+
url=self.base_url,
|
|
350
|
+
endpoint="options/bulk/eod",
|
|
351
|
+
params=params,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
logger.info(f"Out - get_bulk_option_eod_prices | symbols_count={len(symbols.split(','))}")
|
|
355
|
+
return response.get("data")
|
|
356
|
+
|
|
@@ -299,3 +299,52 @@ class MasterData(ApiClient):
|
|
|
299
299
|
response = self._get(url=self.base_url, endpoint=self.urls["broker_partner_mapping_details"].format(broker_partner, broker))
|
|
300
300
|
logger.info(f"Broker partner mapping {response =}")
|
|
301
301
|
return response.get('data', {})
|
|
302
|
+
|
|
303
|
+
async def get_broker_data_async(self, symbols, broker, username=None):
|
|
304
|
+
"""
|
|
305
|
+
Asynchronously retrieve broker-specific data for specified symbols.
|
|
306
|
+
|
|
307
|
+
This function fetches broker-specific details such as margin and leverage
|
|
308
|
+
for a list of provided trading symbols from the broker master service.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
username (str) [Optional]: The user_id for whom data is requested.
|
|
312
|
+
symbols (str): A comma-separated string of trading symbols.
|
|
313
|
+
broker (str): The broker identifier for which data is requested.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
list: A list of dictionaries containing broker data for each symbol
|
|
317
|
+
or an empty list if no data is available.
|
|
318
|
+
"""
|
|
319
|
+
logger.info(f"In - get_broker_data_async {broker =}")
|
|
320
|
+
broker_data_response = await self._async_get(
|
|
321
|
+
url=f"{self.base_url}",
|
|
322
|
+
endpoint = self.urls["broker_details"].format(broker),
|
|
323
|
+
params={"symbols": symbols, "user_id": username},
|
|
324
|
+
headers={}
|
|
325
|
+
)
|
|
326
|
+
return broker_data_response.get('data', [])
|
|
327
|
+
|
|
328
|
+
def get_broker_data_all(self, symbols, broker, username=None):
|
|
329
|
+
"""
|
|
330
|
+
Retrieve broker-specific data for specified symbols.
|
|
331
|
+
|
|
332
|
+
This function fetches broker-specific details such as margin and leverage
|
|
333
|
+
for a list of provided trading symbols from the broker master service.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
username (str) [Optional]: The user_id for whom data is requested.
|
|
337
|
+
symbols (str): A comma-separated string of trading symbols.
|
|
338
|
+
broker (str): The broker identifier for which data is requested.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
list: A dictionary containing broker data for the first symbol or
|
|
342
|
+
an empty dictionary if no data is available.
|
|
343
|
+
"""
|
|
344
|
+
logger.info(f"In - get_broker_data {broker =}")
|
|
345
|
+
broker_data_response = self._get(
|
|
346
|
+
url=f"{self.base_url}",
|
|
347
|
+
endpoint=self.urls['broker_details'].format(broker),
|
|
348
|
+
params={"symbols": symbols, "user_id": username},
|
|
349
|
+
)
|
|
350
|
+
return broker_data_response.get('data', [])
|
|
@@ -33,7 +33,8 @@ class ModelPortfolioReporting(ApiClient):
|
|
|
33
33
|
self.object = None
|
|
34
34
|
self.urls = {
|
|
35
35
|
"portfolio": "portfolio",
|
|
36
|
-
"portfolio_performance": "portfolio/%s/performance"
|
|
36
|
+
"portfolio_performance": "portfolio/%s/performance",
|
|
37
|
+
"index_performance": "index/%s/performance/"
|
|
37
38
|
}
|
|
38
39
|
self.name = Services.MODEL_PORTFOLIO.value
|
|
39
40
|
self.base_url = self.get_base_url(Services.MODEL_PORTFOLIO.value)
|
|
@@ -79,3 +80,31 @@ class ModelPortfolioReporting(ApiClient):
|
|
|
79
80
|
)
|
|
80
81
|
logger.info(f"Received response from get_portfolio_performance_by_dates: {performance}")
|
|
81
82
|
return performance.get("data")
|
|
83
|
+
|
|
84
|
+
def get_index_performance_by_dates(
|
|
85
|
+
self,
|
|
86
|
+
index: str,
|
|
87
|
+
start_date: str,
|
|
88
|
+
end_date: str
|
|
89
|
+
) -> Optional[Dict[str, Any]]:
|
|
90
|
+
"""
|
|
91
|
+
Fetch Index performance for a given date range.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
index (str): The index identifier.
|
|
95
|
+
start_date (str): The start date for performance metrics (format: YYYY-MM-DD).
|
|
96
|
+
end_date (str): The end date for performance metrics (format: YYYY-MM-DD).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
dict | None: The performance data within the date range.
|
|
100
|
+
"""
|
|
101
|
+
logger.info(f"In - get_index_performance_by_dates with index={index}, start_date={start_date}, end_date={end_date}")
|
|
102
|
+
endpoint = self.urls['index_performance'] % index
|
|
103
|
+
params = {"start_date": start_date, "end_date": end_date}
|
|
104
|
+
performance = self._get(
|
|
105
|
+
url=self.base_url,
|
|
106
|
+
endpoint=endpoint,
|
|
107
|
+
params=params
|
|
108
|
+
)
|
|
109
|
+
logger.info(f"Received response from get_index_performance_by_dates: {performance}")
|
|
110
|
+
return performance.get("data")
|
|
@@ -32,7 +32,7 @@ class NotificationService(ApiClient):
|
|
|
32
32
|
"email": "email"
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
def _whatsapp(self, title, template, platform, params, to, user_id) ->
|
|
35
|
+
def _whatsapp(self, title, template, platform, params, to, user_id) -> dict:
|
|
36
36
|
"""
|
|
37
37
|
Sends a WhatsApp notification.
|
|
38
38
|
|
|
@@ -46,7 +46,7 @@ class NotificationService(ApiClient):
|
|
|
46
46
|
- params: Parameters for the notification message.
|
|
47
47
|
|
|
48
48
|
Returns:
|
|
49
|
-
|
|
49
|
+
returns the response data from the notification service.
|
|
50
50
|
"""
|
|
51
51
|
logger.info(f"In - whatsapp {user_id =}, {to =}, "
|
|
52
52
|
f"{title =}, {template =}, {platform =}, {params =}")
|
|
@@ -66,8 +66,9 @@ class NotificationService(ApiClient):
|
|
|
66
66
|
self.set_headers(headers)
|
|
67
67
|
resp_data = self._post(url=self.base_url, endpoint=self.urls.get('whatsapp'), data=payload)
|
|
68
68
|
logger.info(f"Whatsapp response {resp_data =}")
|
|
69
|
+
return resp_data
|
|
69
70
|
|
|
70
|
-
def send_whatsapp(self, template, title, params, to, user_id) ->
|
|
71
|
+
def send_whatsapp(self, template, title, params, to, user_id) -> dict:
|
|
71
72
|
"""
|
|
72
73
|
|
|
73
74
|
Args:
|
|
@@ -78,17 +79,18 @@ class NotificationService(ApiClient):
|
|
|
78
79
|
user_id (str): The ID of the user receiving the notification.
|
|
79
80
|
|
|
80
81
|
Returns:
|
|
81
|
-
|
|
82
|
+
Returns the response of calling function
|
|
82
83
|
"""
|
|
83
84
|
logger.info(f"In - send_whatsapp_notification {user_id =} {title = } {params = } {to = }")
|
|
84
|
-
self._whatsapp(title=title,
|
|
85
|
+
response = self._whatsapp(title=title,
|
|
85
86
|
template=template,
|
|
86
87
|
platform=self._get_env_var(NotificationService.PLATFORM),
|
|
87
88
|
params=params,
|
|
88
89
|
to=to,
|
|
89
90
|
user_id=user_id)
|
|
91
|
+
return response
|
|
90
92
|
|
|
91
|
-
def _email(self, title: str, content: str, platform: str, to: str, user_id: str) ->
|
|
93
|
+
def _email(self, title: str, content: str, platform: str, to: str, user_id: str) -> dict:
|
|
92
94
|
"""
|
|
93
95
|
Sends an email notification using the internal notification service.
|
|
94
96
|
|
|
@@ -100,7 +102,7 @@ class NotificationService(ApiClient):
|
|
|
100
102
|
user_id (str): The ID of the user for tracking or logging purposes.
|
|
101
103
|
|
|
102
104
|
Returns:
|
|
103
|
-
|
|
105
|
+
returns the response data from the notification service.
|
|
104
106
|
|
|
105
107
|
"""
|
|
106
108
|
logger.info(f"In - email {user_id =}, {to =}, {title =}, {platform =}, {content =}")
|
|
@@ -119,8 +121,9 @@ class NotificationService(ApiClient):
|
|
|
119
121
|
self.set_headers(headers)
|
|
120
122
|
resp_data = self._post(url=self.base_url, endpoint=self.urls.get('email'), json=payload)
|
|
121
123
|
logger.info(f"Email response {resp_data =}")
|
|
124
|
+
return resp_data
|
|
122
125
|
|
|
123
|
-
def send_email(self, title: str, content: str, to: str, user_id: str) ->
|
|
126
|
+
def send_email(self, title: str, content: str, to: str, user_id: str) -> dict:
|
|
124
127
|
"""
|
|
125
128
|
Sends an email notification to the specified recipient.
|
|
126
129
|
|
|
@@ -131,13 +134,14 @@ class NotificationService(ApiClient):
|
|
|
131
134
|
user_id (str): The ID of the user associated with the notification.
|
|
132
135
|
|
|
133
136
|
Returns:
|
|
134
|
-
|
|
137
|
+
return the response of calling function
|
|
135
138
|
"""
|
|
136
139
|
logger.info(f"In - send_email {user_id =}, {title =}, {to =}")
|
|
137
|
-
self._email(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
response = self._email(
|
|
141
|
+
title=title,
|
|
142
|
+
content=content,
|
|
143
|
+
platform=self._get_env_var(NotificationService.PLATFORM),
|
|
144
|
+
to=to,
|
|
145
|
+
user_id=user_id
|
|
143
146
|
)
|
|
147
|
+
return response
|
|
@@ -11,6 +11,8 @@ class Payment(ApiClient):
|
|
|
11
11
|
super().__init__(service_user)
|
|
12
12
|
self.urls = {
|
|
13
13
|
"get_subscription": "user-subscription",
|
|
14
|
+
"update_otp_facilitator_code": "order",
|
|
15
|
+
"update_subscription_facilitator_code": "user-subscription-update-data"
|
|
14
16
|
}
|
|
15
17
|
self.name = Services.PAYMENT.value
|
|
16
18
|
self.base_url = self.get_base_url(Services.PAYMENT.value)
|
|
@@ -36,4 +38,47 @@ class Payment(ApiClient):
|
|
|
36
38
|
url = f"{self.base_url}"
|
|
37
39
|
subscription = self._get(url=url, endpoint=self.urls.get('get_subscription'), params={'userId': user_id})
|
|
38
40
|
logger.info(f"Successfully fetched subscription data. {subscription =}")
|
|
39
|
-
return subscription.get('data')
|
|
41
|
+
return subscription.get('data')
|
|
42
|
+
|
|
43
|
+
def update_otp_facilitator_code(self, payment_reference_id: str, analyst_id: str, payload: dict):
|
|
44
|
+
"""
|
|
45
|
+
Updates the facilitator code on the Payment Service for a given payment_reference_id.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
payment_reference_id (str): The payment reference ID (order ID)
|
|
49
|
+
analyst_id (str): Analyst UUID
|
|
50
|
+
payload (dict): Body containing facilitatorCode
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
dict: Response data
|
|
54
|
+
"""
|
|
55
|
+
logger.info(f"In - update_facilitator_code payment_reference_id={payment_reference_id}, "
|
|
56
|
+
f""f"analyst_id={analyst_id}, payload={payload}")
|
|
57
|
+
response = self._put(
|
|
58
|
+
url=f"{self.base_url}",
|
|
59
|
+
endpoint=f"{self.urls.get('update_otp_facilitator_code')}/{payment_reference_id}",
|
|
60
|
+
json=payload,
|
|
61
|
+
params={"analyst": analyst_id},
|
|
62
|
+
)
|
|
63
|
+
logger.info(f"Out - update_facilitator_code response={response}")
|
|
64
|
+
return response
|
|
65
|
+
|
|
66
|
+
def update_subscription_facilitator_code(self, payment_reference_id: str, payload: dict):
|
|
67
|
+
"""
|
|
68
|
+
Updates the facilitator code on the Payment Service for a given payment_reference_id.
|
|
69
|
+
Args:
|
|
70
|
+
payment_reference_id (str): The payment reference ID (subscription ID)
|
|
71
|
+
payload (dict): Body containing facilitatorCode and userId
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
dict: Response data
|
|
75
|
+
"""
|
|
76
|
+
logger.info(f"In - update_subscription_facilitator_code payment_reference_id={payment_reference_id}, "
|
|
77
|
+
f"payload={payload}")
|
|
78
|
+
response = self._put(
|
|
79
|
+
url=f"{self.base_url}",
|
|
80
|
+
endpoint=f"{self.urls.get('update_subscription_facilitator_code')}/{payment_reference_id}",
|
|
81
|
+
json=payload
|
|
82
|
+
)
|
|
83
|
+
logger.info(f"Out - update_subscription_facilitator_code response={response}")
|
|
84
|
+
return response
|