finbourne-sdk-utils 0.0.24__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.
Files changed (52) hide show
  1. features/__init__.py +0 -0
  2. features/main.py +11 -0
  3. finbourne_sdk_utils/__init__.py +8 -0
  4. finbourne_sdk_utils/cocoon/__init__.py +34 -0
  5. finbourne_sdk_utils/cocoon/async_tools.py +94 -0
  6. finbourne_sdk_utils/cocoon/cocoon.py +1862 -0
  7. finbourne_sdk_utils/cocoon/cocoon_printer.py +455 -0
  8. finbourne_sdk_utils/cocoon/config/domain_settings.json +125 -0
  9. finbourne_sdk_utils/cocoon/config/seed_sample_data.json +36 -0
  10. finbourne_sdk_utils/cocoon/dateorcutlabel.py +198 -0
  11. finbourne_sdk_utils/cocoon/instruments.py +482 -0
  12. finbourne_sdk_utils/cocoon/properties.py +442 -0
  13. finbourne_sdk_utils/cocoon/seed_sample_data.py +137 -0
  14. finbourne_sdk_utils/cocoon/systemConfiguration.py +92 -0
  15. finbourne_sdk_utils/cocoon/transaction_type_upload.py +136 -0
  16. finbourne_sdk_utils/cocoon/utilities.py +1877 -0
  17. finbourne_sdk_utils/cocoon/validator.py +243 -0
  18. finbourne_sdk_utils/extract/__init__.py +1 -0
  19. finbourne_sdk_utils/extract/group_holdings.py +400 -0
  20. finbourne_sdk_utils/iam/__init__.py +1 -0
  21. finbourne_sdk_utils/iam/roles.py +74 -0
  22. finbourne_sdk_utils/jupyter_tools/__init__.py +2 -0
  23. finbourne_sdk_utils/jupyter_tools/hide_code_button.py +23 -0
  24. finbourne_sdk_utils/jupyter_tools/stop_execution.py +14 -0
  25. finbourne_sdk_utils/logger/LusidLogger.py +41 -0
  26. finbourne_sdk_utils/logger/__init__.py +1 -0
  27. finbourne_sdk_utils/lpt/__init__.py +0 -0
  28. finbourne_sdk_utils/lpt/back_compat.py +20 -0
  29. finbourne_sdk_utils/lpt/cash_ladder.py +191 -0
  30. finbourne_sdk_utils/lpt/connect_lusid.py +64 -0
  31. finbourne_sdk_utils/lpt/connect_none.py +5 -0
  32. finbourne_sdk_utils/lpt/connect_token.py +9 -0
  33. finbourne_sdk_utils/lpt/dfq.py +321 -0
  34. finbourne_sdk_utils/lpt/either.py +65 -0
  35. finbourne_sdk_utils/lpt/get_instruments.py +101 -0
  36. finbourne_sdk_utils/lpt/lpt.py +374 -0
  37. finbourne_sdk_utils/lpt/lse.py +188 -0
  38. finbourne_sdk_utils/lpt/map_instruments.py +164 -0
  39. finbourne_sdk_utils/lpt/pager.py +32 -0
  40. finbourne_sdk_utils/lpt/record.py +13 -0
  41. finbourne_sdk_utils/lpt/refreshing_token.py +43 -0
  42. finbourne_sdk_utils/lpt/search_instruments.py +48 -0
  43. finbourne_sdk_utils/lpt/stdargs.py +154 -0
  44. finbourne_sdk_utils/lpt/txn_config.py +128 -0
  45. finbourne_sdk_utils/lpt/txn_config_yaml.py +493 -0
  46. finbourne_sdk_utils/pandas_utils/__init__.py +0 -0
  47. finbourne_sdk_utils/pandas_utils/lusid_pandas.py +128 -0
  48. finbourne_sdk_utils-0.0.24.dist-info/LICENSE +21 -0
  49. finbourne_sdk_utils-0.0.24.dist-info/METADATA +25 -0
  50. finbourne_sdk_utils-0.0.24.dist-info/RECORD +52 -0
  51. finbourne_sdk_utils-0.0.24.dist-info/WHEEL +5 -0
  52. finbourne_sdk_utils-0.0.24.dist-info/top_level.txt +2 -0
@@ -0,0 +1,455 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from finbourne_sdk_utils.cocoon import checkargs
4
+ from lusid.exceptions import ApiException
5
+ from flatten_json import flatten
6
+
7
+
8
+ class CocoonPrinter:
9
+ VALID_KEYS = [
10
+ "portfolios",
11
+ "instruments",
12
+ "transactions",
13
+ "quotes",
14
+ "holdings",
15
+ "reference_portfolios",
16
+ ]
17
+
18
+ def __init__(
19
+ self, response, extended_error_details=False, data_entity_details=False
20
+ ):
21
+ self.validate(response)
22
+
23
+ self.response = response
24
+ self.file_type = list(response.keys())[0]
25
+ self.extended_error_details = extended_error_details
26
+ self.data_entity_details = data_entity_details
27
+
28
+ def validate(self, response):
29
+ if len(response.keys()) == 0:
30
+ message = (
31
+ f"Response doesn't contain any keys. Valid keys are {self.VALID_KEYS}"
32
+ )
33
+ raise ValueError(message)
34
+
35
+ if len(response.keys()) > 1:
36
+ message = f"Response contains too many keys - only one is allowed, but received {list(response.keys())}"
37
+ raise ValueError(message)
38
+
39
+ key = list(response.keys())[0]
40
+
41
+ if key not in self.VALID_KEYS:
42
+ message = f"Response contains invalid key. Valid keys are {self.VALID_KEYS}, but received {key}"
43
+ raise ValueError(message)
44
+
45
+ def format_instruments_response(self):
46
+ return format_instruments_response(
47
+ self.response,
48
+ extended_error_details=self.extended_error_details,
49
+ data_entity_details=self.data_entity_details,
50
+ )
51
+
52
+ def format_portfolios_response(self):
53
+ return format_portfolios_response(
54
+ self.response, extended_error_details=self.extended_error_details
55
+ )
56
+
57
+ def format_transactions_response(self):
58
+ return format_transactions_response(
59
+ self.response, extended_error_details=self.extended_error_details
60
+ )
61
+
62
+ def format_quotes_response(self):
63
+ return format_quotes_response(
64
+ self.response, extended_error_details=self.extended_error_details,
65
+ )
66
+
67
+ def format_holdings_response(self):
68
+ return format_holdings_response(
69
+ self.response, extended_error_details=self.extended_error_details,
70
+ )
71
+
72
+ def format_reference_portfolios_response(self):
73
+ return format_reference_portfolios_response(
74
+ self.response, extended_error_details=self.extended_error_details,
75
+ )
76
+
77
+ def format_response(self):
78
+ return getattr(CocoonPrinter, f"format_{self.file_type}_response")(self)
79
+
80
+
81
+ @checkargs
82
+ def check_dict_for_required_keys(
83
+ target_dict: dict, target_name: str, required_keys: list
84
+ ):
85
+ """
86
+ This function checks that a named dictionary contains a list of required key
87
+
88
+ Parameters
89
+ ----------
90
+ target_dict : dict
91
+ Dictionary to check for keys in
92
+ target_name : str
93
+ Variable name of dictionary
94
+ required_keys : list
95
+ List of required keys
96
+
97
+ Returns
98
+ -------
99
+ None
100
+ """
101
+
102
+ for key in required_keys:
103
+ if key not in target_dict.keys():
104
+ raise ValueError(
105
+ f"'{key}' missing from {target_name}. {target_name} must include {required_keys}"
106
+ f"as keys"
107
+ )
108
+
109
+
110
+ def get_errors_from_response(
111
+ list_of_API_exceptions: list, extended_error_details: bool = False
112
+ ):
113
+ """
114
+ This function gets the status code and reason from API exception
115
+
116
+ Parameters
117
+ ----------
118
+ list_of_API_exceptions : list
119
+ A list of ApiExceptions
120
+ extended_error_details : bool
121
+ A flag that determines whether the errors returned will have extended details (request id, error body)
122
+ Returns
123
+ -------
124
+ pd.DataFrame
125
+ A Pandas DataFrame containing the reasons and status codes for ApiExceptions from a request.
126
+ If the extended_error_details bool is True, the RequestID and error body will also be returned.
127
+ """
128
+
129
+ # check all items are ApiExceptions
130
+ for i in list_of_API_exceptions:
131
+ if not isinstance(i, ApiException):
132
+ raise TypeError(
133
+ f" Unexpected Error in response. Expected instance of ApiException, but got: {repr(i)}"
134
+ )
135
+
136
+ # get status and reason for each batch
137
+ status = [item.status for item in list_of_API_exceptions]
138
+ reason = [item.reason for item in list_of_API_exceptions]
139
+
140
+ items_list = [reason, status]
141
+ column_names = ["error_items", "status"]
142
+
143
+ if extended_error_details:
144
+ request_id = [
145
+ item.headers.get("lusid-meta-requestId") for item in list_of_API_exceptions
146
+ ]
147
+ error_body = [item.body for item in list_of_API_exceptions]
148
+ items_list.extend([request_id, error_body])
149
+ column_names.extend(["request_id", "error_body"])
150
+
151
+ # transpose list of lists for insertion to dataframe
152
+ items_error = np.array(items_list).T.tolist()
153
+
154
+ return pd.DataFrame(items_error, columns=column_names)
155
+
156
+
157
+ def get_portfolio_from_href(href: list, file_type: str):
158
+ """
159
+ This function finds the protfolio code contained within a href for a given file_type
160
+
161
+ Parameters
162
+ ----------
163
+ href : list
164
+ list of hrefs from LUSID response
165
+ file_type : str
166
+
167
+ Returns
168
+ -------
169
+ list
170
+ portfolio codes taken from hrefs
171
+ """
172
+
173
+ # get portfolio codes from each href
174
+ codes = [j[-2] for j in [i.split("/") for i in href]]
175
+
176
+ return codes
177
+
178
+
179
+ @checkargs
180
+ def get_non_href_response(response: dict, file_type: str, data_entity_details=False):
181
+ dict_items_success = [
182
+ (key, value)
183
+ for batch in response[file_type]["success"]
184
+ for (key, value) in batch.values.items()
185
+ ]
186
+ dict_items_failed = [
187
+ (key, value)
188
+ for batch in response[file_type]["success"]
189
+ for (key, value) in batch.failed.items()
190
+ ]
191
+
192
+ def extract_value_details_from_success_request(data_entity_dict):
193
+ return pd.DataFrame(
194
+ flatten(value[1].to_dict(), ".") for value in data_entity_dict
195
+ )
196
+
197
+ def extract_key_details_from_success_request(data_entity_dict):
198
+ return pd.DataFrame(value[0] for value in data_entity_dict)
199
+
200
+ if data_entity_details:
201
+
202
+ return (
203
+ extract_value_details_from_success_request(dict_items_success),
204
+ extract_value_details_from_success_request(dict_items_failed),
205
+ )
206
+
207
+ else:
208
+
209
+ return (
210
+ extract_key_details_from_success_request(dict_items_success),
211
+ extract_key_details_from_success_request(dict_items_failed),
212
+ )
213
+
214
+
215
+ def format_instruments_response(
216
+ response: dict,
217
+ extended_error_details: bool = False,
218
+ data_entity_details: bool = False,
219
+ ) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
220
+ """
221
+ This function unpacks a response from instrument requests and returns successful, failed and errored statuses for
222
+ request constituents.
223
+ Parameters
224
+ ----------
225
+ response : dict
226
+ response from Lusid-python-tools
227
+ extended_error_details : bool
228
+ A flag that determines whether the errors returned will have extended details (request id, error body)
229
+ data_entity_details : bool
230
+ A flag that determines whether the fields from the response will all be extracted
231
+ Returns
232
+ -------
233
+ success : pd.DataFrame
234
+ successful calls from request
235
+ error : pd.DataFrame
236
+ Error responses from request that fail (APIExceptions: 400 errors)
237
+ fail : pd.Dataframe
238
+ Failed responses that LUSID rejected
239
+ """
240
+
241
+ file_type = "instruments"
242
+ check_dict_for_required_keys(
243
+ response[file_type], f"Response from {file_type} request", ["errors", "success"]
244
+ )
245
+
246
+ # get success and failures
247
+ items_success, items_failed = get_non_href_response(
248
+ response, file_type, data_entity_details
249
+ )
250
+
251
+ return (
252
+ items_success,
253
+ get_errors_from_response(response[file_type]["errors"], extended_error_details),
254
+ items_failed,
255
+ )
256
+
257
+
258
+ def format_portfolios_response(
259
+ response: dict, extended_error_details: bool = False,
260
+ ) -> tuple[pd.DataFrame, pd.DataFrame]:
261
+ """
262
+ This function unpacks a response from portfolio requests and returns successful and errored statuses for
263
+ request constituents.
264
+
265
+ Parameters
266
+ ----------
267
+ response : dict
268
+ response from Lusid-python-tools
269
+ extended_error_details : bool
270
+ A flag that determines whether the errors returned will have extended details (request id, error body)
271
+
272
+ Returns
273
+ -------
274
+ success : pd.DataFrame
275
+ successful calls from request
276
+ error : pd.DataFrame
277
+ Error responses from request that fail (APIExceptions: 400 errors)
278
+ """
279
+ file_type = "portfolios"
280
+ check_dict_for_required_keys(
281
+ response[file_type], f"Response from {file_type} request", ["errors", "success"]
282
+ )
283
+
284
+ # get success
285
+ items_success = [batch.id.code for batch in response[file_type]["success"]]
286
+
287
+ errors = get_errors_from_response(
288
+ response[file_type]["errors"], extended_error_details
289
+ )
290
+
291
+ return (pd.DataFrame(items_success, columns=["successful items"]), errors)
292
+
293
+
294
+ def format_transactions_response(
295
+ response: dict, extended_error_details: bool = False,
296
+ ) -> tuple[pd.DataFrame, pd.DataFrame]:
297
+ """
298
+ This function unpacks a response from transaction requests and returns successful and errored statuses for
299
+ request constituents.
300
+
301
+ Parameters
302
+ ----------
303
+ response : dict
304
+ response from Lusid-python-tools
305
+ extended_error_details : bool
306
+ A flag that determines whether the errors returned will have extended details (request id, error body)
307
+
308
+ Returns
309
+ -------
310
+ success : pd.DataFrame
311
+ successful calls from request
312
+ error : pd.DataFrame
313
+ Error responses from request that fail (APIExceptions: 400 errors)
314
+ """
315
+
316
+ file_type = "transactions"
317
+
318
+ check_dict_for_required_keys(
319
+ response[file_type], f"Response from {file_type} request", ["errors", "success"]
320
+ )
321
+
322
+ # get success
323
+ items_success = [batch.href for batch in response[file_type]["success"]]
324
+
325
+ errors = get_errors_from_response(
326
+ response[file_type]["errors"], extended_error_details
327
+ )
328
+
329
+ return (
330
+ pd.DataFrame(
331
+ get_portfolio_from_href(items_success, file_type),
332
+ columns=["successful items"],
333
+ ),
334
+ errors,
335
+ )
336
+
337
+
338
+ def format_holdings_response(
339
+ response: dict, extended_error_details: bool = False,
340
+ ) -> tuple[pd.DataFrame, pd.DataFrame]:
341
+ """
342
+ This function unpacks a response from holding requests and returns successful and errored statuses for
343
+ request constituents.
344
+
345
+ Parameters
346
+ ----------
347
+ response : dict
348
+ response from Lusid-python-tools
349
+ extended_error_details : bool
350
+ A flag that determines whether the errors returned will have extended details (request id, error body)
351
+
352
+ Returns
353
+ -------
354
+ success : pd.DataFrame
355
+ successful calls from request
356
+ error : pd.DataFrame
357
+ Error responses from request that fail (APIExceptions: 400 errors)
358
+
359
+ """
360
+
361
+ file_type = "holdings"
362
+ check_dict_for_required_keys(
363
+ response[file_type], f"Response from {file_type} request", ["errors", "success"]
364
+ )
365
+
366
+ # get success
367
+ items_success = [batch.href for batch in response[file_type]["success"]]
368
+
369
+ errors = get_errors_from_response(
370
+ response[file_type]["errors"], extended_error_details
371
+ )
372
+
373
+ return (
374
+ pd.DataFrame(
375
+ get_portfolio_from_href(items_success, file_type),
376
+ columns=["successful items"],
377
+ ),
378
+ errors,
379
+ )
380
+
381
+
382
+ def format_quotes_response(
383
+ response: dict, extended_error_details: bool = False,
384
+ ) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
385
+ """
386
+ This function unpacks a response from quotes requests and returns successful, failed and errored statuses for
387
+ request constituents.
388
+
389
+ Parameters
390
+ ----------
391
+ response : dict
392
+ response from Lusid-python-tools
393
+ extended_error_details : bool
394
+ A flag that determines whether the errors returned will have extended details (request id, error body)
395
+
396
+ Returns
397
+ -------
398
+ success : pd.DataFrame
399
+ successful calls from request
400
+ error : pd.DataFrame
401
+ Error responses from request that fail (APIExceptions: 400 errors)
402
+ fail : pd.Dataframe
403
+ Failed responses that LUSID rejected
404
+ """
405
+
406
+ file_type = "quotes"
407
+
408
+ check_dict_for_required_keys(
409
+ response[file_type], f"Response from {file_type} request", ["errors", "success"]
410
+ )
411
+ items_success, items_failed = get_non_href_response(
412
+ response, file_type, data_entity_details=True
413
+ )
414
+
415
+ return (
416
+ items_success,
417
+ get_errors_from_response(response[file_type]["errors"], extended_error_details),
418
+ items_failed,
419
+ )
420
+
421
+
422
+ def format_reference_portfolios_response(
423
+ response: dict, extended_error_details: bool = False,
424
+ ) -> tuple[pd.DataFrame, pd.DataFrame]:
425
+ """
426
+ This function unpacks a response from reference portfolio requests and returns successful and errored statuses for request constituents.
427
+
428
+ Parameters
429
+ ----------
430
+ response : dict
431
+ response from Lusid-python-tools
432
+ extended_error_details : bool
433
+ A flag that determines whether the errors returned will have extended details (request id, error body)
434
+
435
+ Returns
436
+ -------
437
+ success : pd.DataFrame
438
+ successful calls from request
439
+ error : pd.DataFrame
440
+ Error responses from rquest that fail (APIExceptions: 400 errors)
441
+ """
442
+
443
+ file_type = "reference_portfolios"
444
+ check_dict_for_required_keys(
445
+ response[file_type], f"Response from {file_type} request", ["errors", "success"]
446
+ )
447
+
448
+ # get success
449
+ items_success = [batch.id.code for batch in response[file_type]["success"]]
450
+
451
+ errors = get_errors_from_response(
452
+ response[file_type]["errors"], extended_error_details
453
+ )
454
+
455
+ return (pd.DataFrame(items_success, columns=["successful items"]), errors)
@@ -0,0 +1,125 @@
1
+ {
2
+ "transaction": {
3
+ "domain": "Transaction",
4
+ "batch_allowed": true,
5
+ "default_batch_size": 10000,
6
+ "top_level_model": "TransactionRequest",
7
+ "portfolio_specific": true,
8
+ "full_key_format": true,
9
+ "required_call_attributes": [
10
+ "scope",
11
+ "code"
12
+ ],
13
+ "unique_attributes": [
14
+ "transaction_id"
15
+ ]
16
+ },
17
+ "transactions_with_commit_mode": {
18
+ "domain": "Transaction",
19
+ "batch_allowed": true,
20
+ "default_batch_size": 10000,
21
+ "top_level_model": "TransactionRequest",
22
+ "portfolio_specific": true,
23
+ "full_key_format": true,
24
+ "required_call_attributes": [
25
+ "scope",
26
+ "code"
27
+ ],
28
+ "unique_attributes": [
29
+ "transaction_id"
30
+ ]
31
+ },
32
+ "holding": {
33
+ "domain": "Holding",
34
+ "default_batch_size": 100000000000,
35
+ "batch_allowed": false,
36
+ "top_level_model": "AdjustHoldingRequest",
37
+ "portfolio_specific": true,
38
+ "full_key_format": true,
39
+ "required_call_attributes": [
40
+ "scope",
41
+ "code",
42
+ "effective_at"
43
+ ],
44
+ "unique_attributes": [
45
+ "effective_at"
46
+ ]
47
+ },
48
+ "instrument": {
49
+ "domain": "Instrument",
50
+ "batch_allowed": true,
51
+ "default_batch_size": 2000,
52
+ "top_level_model": "InstrumentDefinition",
53
+ "portfolio_specific": false,
54
+ "full_key_format": false,
55
+ "required_call_attributes": [],
56
+ "unique_attributes": []
57
+ },
58
+ "portfolio": {
59
+ "domain": "Portfolio",
60
+ "batch_allowed": false,
61
+ "default_batch_size": 100000000000,
62
+ "top_level_model": "CreateTransactionPortfolioRequest",
63
+ "portfolio_specific": true,
64
+ "full_key_format": false,
65
+ "required_call_attributes": [
66
+ "scope",
67
+ "code"
68
+ ],
69
+ "unique_attributes": [
70
+ "code"
71
+ ]
72
+ },
73
+ "quote": {
74
+ "domain": null,
75
+ "batch_allowed": true,
76
+ "default_batch_size": 2000,
77
+ "top_level_model": "UpsertQuoteRequest",
78
+ "portfolio_specific": false,
79
+ "full_key_format": false,
80
+ "required_call_attributes": [
81
+ "scope"
82
+ ],
83
+ "unique_attributes": []
84
+ },
85
+ "instrument_property": {
86
+ "domain": "Instrument",
87
+ "batch_allowed": true,
88
+ "default_batch_size": 2000,
89
+ "top_level_model": "UpsertInstrumentPropertyRequest",
90
+ "portfolio_specific": false,
91
+ "full_key_format": false,
92
+ "required_call_attributes": [],
93
+ "unique_attributes": []
94
+ },
95
+ "portfolio_group": {
96
+ "domain": "PortfolioGroup",
97
+ "batch_allowed": true,
98
+ "default_batch_size": 2000,
99
+ "top_level_model": "CreatePortfolioGroupRequest",
100
+ "portfolio_specific": true,
101
+ "full_key_format": false,
102
+ "required_call_attributes": [
103
+ "scope",
104
+ "code"
105
+ ],
106
+ "unique_attributes": [
107
+ "code"
108
+ ]
109
+ },
110
+ "reference_portfolio": {
111
+ "domain": "Portfolio",
112
+ "batch_allowed": false,
113
+ "default_batch_size": 2000,
114
+ "top_level_model": "CreateReferencePortfolioRequest",
115
+ "portfolio_specific": true,
116
+ "full_key_format": false,
117
+ "required_call_attributes": [
118
+ "scope",
119
+ "code"
120
+ ],
121
+ "unique_attributes": [
122
+ "code"
123
+ ]
124
+ }
125
+ }
@@ -0,0 +1,36 @@
1
+ {"instruments": {
2
+ "identifier_mapping": {
3
+ "ClientInternal": "instrument_id",
4
+ "Ticker": "ticker",
5
+ "Sedol": "sedol"},
6
+ "required": {"name": "name"},
7
+ "properties": ["instrument_type"],
8
+ "optional": {}
9
+ },"portfolios": {
10
+ "required": {
11
+ "code": "portfolio_code",
12
+ "display_name": "portfolio_name",
13
+ "base_currency": "portfolio_base_currency"
14
+ },
15
+ "optional": {"created": "$2000-01-01T00:00:00+00:00"},
16
+ "identifier_mapping" : {},
17
+ "properties": []
18
+ },"transactions":
19
+ {"identifier_mapping": {
20
+ "ClientInternal": "instrument_id",
21
+ "Currency": "cash_transactions"
22
+ },
23
+ "required": {
24
+ "code": "portfolio_code",
25
+ "transaction_id": "txn_id",
26
+ "type": "txn_type",
27
+ "transaction_price.price": "txn_price",
28
+ "transaction_price.type": "$Price",
29
+ "total_consideration.amount": "txn_consideration",
30
+ "units": "txn_units",
31
+ "transaction_date": "txn_trade_date",
32
+ "total_consideration.currency": "currency",
33
+ "settlement_date": "txn_settle_date"
34
+ },
35
+ "optional": {},
36
+ "properties": ["strategy"]}}