bw-essentials-core 0.1.5__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.
@@ -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) -> None:
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
- None
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) -> None:
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
- None
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) -> None:
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
- None
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) -> None:
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
- None
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
- title=title,
139
- content=content,
140
- platform=self._get_env_var(NotificationService.PLATFORM),
141
- to=to,
142
- user_id=user_id
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
@@ -0,0 +1,84 @@
1
+ import logging
2
+ from bw_essentials.services.api_client import ApiClient
3
+ from bw_essentials.constants.services import Services
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class Payment(ApiClient):
9
+
10
+ def __init__(self, service_user: str):
11
+ super().__init__(service_user)
12
+ self.urls = {
13
+ "get_subscription": "user-subscription",
14
+ "update_otp_facilitator_code": "order",
15
+ "update_subscription_facilitator_code": "user-subscription-update-data"
16
+ }
17
+ self.name = Services.PAYMENT.value
18
+ self.base_url = self.get_base_url(Services.PAYMENT.value)
19
+
20
+ def get_subscription(self, user_id):
21
+ """
22
+ Fetch subscription details for a given user.
23
+
24
+ This method constructs the request URL using the configured base URL
25
+ and the `get_subscription` endpoint. It sends a GET request with
26
+ the provided `user_id` as a query parameter, logs the process,
27
+ and returns the subscription data payload.
28
+
29
+ Args:
30
+ user_id (str | int): Unique identifier of the user whose
31
+ subscription details need to be fetched.
32
+
33
+ Returns:
34
+ dict | None: A dictionary containing the subscription data if
35
+ available, otherwise `None`.
36
+ """
37
+ logger.info(f"Received request to get subscription with {user_id =}")
38
+ url = f"{self.base_url}"
39
+ subscription = self._get(url=url, endpoint=self.urls.get('get_subscription'), params={'userId': user_id})
40
+ logger.info(f"Successfully fetched subscription data. {subscription =}")
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
@@ -48,7 +48,7 @@ Example
48
48
  import logging
49
49
  from typing import Optional, Dict, Any
50
50
 
51
- from bw_essentials.constants.services import Services
51
+ from bw_essentials.constants.services import Services, PortfolioStatus
52
52
  from bw_essentials.services.api_client import ApiClient
53
53
 
54
54
  logger = logging.getLogger(__name__)
@@ -78,6 +78,10 @@ class PortfolioCatalogue(ApiClient):
78
78
  self.name = Services.PORTFOLIO_CATALOGUE.value
79
79
  self.urls = {
80
80
  "rebalance": "rebalance",
81
+ "get_rebalance": "rebalance",
82
+ "subscriptions_by_ids": "subscription-plan/list",
83
+ "otp_subscriptions_by_ids": "one-time-plans/list",
84
+ "option_recommendations": "options/recommendations"
81
85
  }
82
86
 
83
87
  def create_rebalance(self, json: Dict[str, Any]) -> Optional[Dict[str, Any]]:
@@ -102,3 +106,167 @@ class PortfolioCatalogue(ApiClient):
102
106
  )
103
107
  logger.info("%s", data)
104
108
  return data
109
+
110
+ def rebalance(self, portfolio_id, status):
111
+ """Fetch the latest rebalance record for a portfolio.
112
+
113
+ Parameters
114
+ ----------
115
+ portfolio_id : str
116
+ Portfolio identifier.
117
+ status : str
118
+ Portfolio status filter.
119
+
120
+ Returns
121
+ -------
122
+ dict
123
+ The first record from the service response's 'data' list.
124
+ """
125
+ logger.info("In - rebalance portfolio_id=%s status=%s", portfolio_id, status)
126
+ data = self._get(
127
+ url=self.base_url,
128
+ endpoint=self.urls["get_rebalance"],
129
+ params={'portfolioId': portfolio_id, 'status': status}
130
+ )
131
+ logger.info("rebalance response: %s", data)
132
+ return data.get('data')[0]
133
+
134
+ def get_rebalance_due_date(self, portfolio_id, status):
135
+ """Return the next rebalance date for an active portfolio, else None.
136
+
137
+ Parameters
138
+ ----------
139
+ portfolio_id : str
140
+ Portfolio identifier.
141
+ status : str
142
+ Portfolio status.
143
+
144
+ Returns
145
+ -------
146
+ str | None
147
+ The next rebalance date string if available; otherwise None.
148
+ """
149
+ logger.info(
150
+ "In - get_rebalance_due_date portfolio_id=%s status=%s",
151
+ portfolio_id,
152
+ status,
153
+ )
154
+ if status in PortfolioStatus.ACTIVE_FOR_SUBSCRIBED_USER.value:
155
+ rebalance_history = self.rebalance(portfolio_id, status)
156
+ next_rebalance_date = (
157
+ rebalance_history.get('nextRebalanceDate') if rebalance_history else None
158
+ )
159
+ logger.info("nextRebalanceDate=%s", next_rebalance_date)
160
+ return next_rebalance_date
161
+ logger.info("get_rebalance_due_date not applicable for status=%s", status)
162
+ return None
163
+
164
+ def get_subscriptions_by_ids(self, plans_ids: list):
165
+ """
166
+ Fetch multiple subscriptions by their plan IDs.
167
+
168
+ This method constructs the request URL using the configured base URL
169
+ and the `subscriptions_by_ids` endpoint. It sends a POST request with
170
+ the given list of `plans_ids` in the request body, logs the process,
171
+ and returns the subscription data.
172
+
173
+ Args:
174
+ plans_ids (list[str] | list[int]): A list of subscription plan IDs
175
+ to fetch details for.
176
+
177
+ Returns:
178
+ list[dict]: A list of subscription data dictionaries if available,
179
+ otherwise an empty list.
180
+ """
181
+ logger.info(f"In get_subscriptions_by_ids {plans_ids =}")
182
+ url = f"{self.base_url}"
183
+ payload = {
184
+ "planId": plans_ids
185
+ }
186
+ response = self._post(url=url, endpoint=self.urls.get('subscriptions_by_ids'), json=payload)
187
+ logger.info(f"Successfully fetched subscriptions by ids data. {response =}")
188
+ return response.get('data', [])
189
+
190
+ def get_otp_subscriptions_by_ids(self, plans_ids: list):
191
+ """
192
+ Fetch OTP subscription details for multiple plan IDs.
193
+
194
+ This method sends a POST request to the `otp_subscriptions_by_ids`
195
+ endpoint with the given list of plan IDs in the request body.
196
+ It logs the request and response flow and returns the extracted
197
+ subscription data.
198
+
199
+ Args:
200
+ plans_ids (list[str] | list[int]): A list of OTP subscription plan IDs
201
+ to retrieve details for.
202
+
203
+ Returns:
204
+ list[dict]: A list of OTP subscription data dictionaries if available,
205
+ otherwise an empty list.
206
+ """
207
+ logger.info(f"In get_otp_subscriptions_by_ids {plans_ids =}")
208
+ url = f"{self.base_url}"
209
+ payload = {
210
+ "id": plans_ids
211
+ }
212
+ response = self._post(url=url, endpoint=self.urls.get('otp_subscriptions_by_ids'), json=payload)
213
+ logger.info(f"Successfully fetched otp subscriptions by ids data. {response =}")
214
+ return response.get('data', [])
215
+
216
+ def get_options_recommendations(self,
217
+ strike_price_gt: float = None,
218
+ segment_in: str = None,
219
+ date_gte: str = None,
220
+ limit: int = None,
221
+ broker: str = None,
222
+ underlying_symbol: str = None,
223
+ status: str = None,
224
+ isin_code: str = None):
225
+ """
226
+ Fetch options trading recommendations based on filtering parameters.
227
+
228
+ This method sends a GET request to the Portfolio-Catalogue service's
229
+ `/options/recommendations` endpoint with optional query parameters
230
+ for filtering by strike price, trading segment, date, broker, and limit.
231
+
232
+ Endpoint:
233
+ GET /options/recommendations
234
+
235
+ Args:
236
+ strike_price_gt (float, optional): Fetch recommendations where strike price > given value.
237
+ segment_in (str, optional): Trading segment to filter by (e.g., 'fno').
238
+ date_gte (str, optional): Minimum date (ISO format, e.g., '2025-10-28').
239
+ limit (int, optional): Limit the number of records returned.
240
+ broker (str, optional): Broker name to filter recommendations (e.g., 'zerodha').
241
+ underlying_symbol (str, optional): Trading symbol to filter recommendations (e.g., 'RELIANCE').
242
+ status (str, optional): Status of the recommendation. (e.g., 'active', 'inactive')
243
+ isin_code (str, optional): ISIN code to filter recommendations).
244
+
245
+ Returns:
246
+ list[dict]: A list of recommendation records if available, otherwise an empty list.
247
+ """
248
+ logger.info(
249
+ "In - get_options_recommendations strike_price_gt=%s, segment_in=%s, date_gte=%s, limit=%s, "
250
+ "broker=%s, underlying_symbol=%s, status=%s, isin_code=%s",
251
+ strike_price_gt, segment_in, date_gte, limit, broker, underlying_symbol, status, isin_code
252
+ )
253
+
254
+ params = {k: v for k, v in {
255
+ "strike_price__gt": strike_price_gt,
256
+ "segment__in": segment_in,
257
+ "date__gte": date_gte,
258
+ "limit": limit,
259
+ "broker": broker,
260
+ "underlying_symbol": underlying_symbol,
261
+ "status": status,
262
+ "isin_code": isin_code
263
+ }.items() if v is not None}
264
+
265
+ response = self._get(
266
+ url=self.base_url,
267
+ endpoint=self.urls.get('option_recommendations'),
268
+ params=params
269
+ )
270
+
271
+ logger.info(f"Successfully fetched options recommendations. {response =}")
272
+ return response.get("data", [])
@@ -43,6 +43,7 @@ class PortfolioContent(ApiClient):
43
43
  self.name = Services.PORTFOLIO_CONTENT.value
44
44
  self.urls = {
45
45
  "portfolio": "portfolio",
46
+ "portfolio-details": "portfolio-details"
46
47
  }
47
48
 
48
49
  def get_portfolio_details(self, portfolio_id: str) -> Optional[Dict[str, Any]]:
@@ -67,3 +68,26 @@ class PortfolioContent(ApiClient):
67
68
  )
68
69
  logger.info("%s", data)
69
70
  return data
71
+
72
+ def get_portfolio_details_by_ids(self, portfolio_ids: list):
73
+ """
74
+ Fetch portfolio details from the external service for the given portfolio IDs.
75
+
76
+ Args:
77
+ portfolio_ids (list): A list of portfolio IDs to fetch details for.
78
+
79
+ Returns:
80
+ dict: The response data containing portfolio details from the external service.
81
+ """
82
+ logger.info("Fetching portfolio details for %d portfolio IDs: %s", len(portfolio_ids), portfolio_ids)
83
+
84
+ payload = {
85
+ "portfolioIds": portfolio_ids
86
+ }
87
+ response = self._post(
88
+ url=self.base_url,
89
+ endpoint=self.urls['portfolio-details'],
90
+ json=payload
91
+ )
92
+ logger.info(f"Successfully fetched portfolio details by ids. {response=}")
93
+ return response.get('data', [])