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,493 @@
1
+ import yaml
2
+ from typing import Callable
3
+ from lusid import models
4
+
5
+ try:
6
+ from StringIO import StringIO
7
+ except ImportError:
8
+ from io import StringIO
9
+
10
+ # Common abbreviations
11
+ rev_abbrev = {
12
+ "FIFO": "TransactionConfiguration/default/TaxLotSelectionMethod=FirstInFirstOut",
13
+ "LIFO": "TransactionConfiguration/default/TaxLotSelectionMethod=LastInFirstOut",
14
+ "HCF": "TransactionConfiguration/default/TaxLotSelectionMethod=HighestCostFirst",
15
+ "LCF": "TransactionConfiguration/default/TaxLotSelectionMethod=LowestCostFirst",
16
+ "AC": "TransactionConfiguration/default/TaxLotSelectionMethod=AverageCost",
17
+ "LL": "LongLonger",
18
+ "SL": "ShortLonger",
19
+ "LS": "LongShorter",
20
+ "SS": "ShortShorter",
21
+ "S1": "Side1",
22
+ "S2": "Side2",
23
+ "STOCK": "Settlement, Traded",
24
+ "CASH": "Commitment, CashSettlement",
25
+ "FWD": "ForwardFx, CashSettlement",
26
+ "RCV": "Receivable, CashSettlement",
27
+ }
28
+
29
+ # Reverse key map for lookups
30
+ fwd_abbrev = {v: k for k, v in rev_abbrev.items()}
31
+
32
+
33
+ # Methods to map between abbreviations
34
+ def abbrev(v):
35
+ return fwd_abbrev.get(v, v)
36
+
37
+
38
+ def unabbrev(v):
39
+ return rev_abbrev.get(v, v)
40
+
41
+
42
+ # Dumper class with the custom constructor/resolvers
43
+ class CustomDumper(yaml.Dumper):
44
+ pass
45
+
46
+
47
+ # Loader class with the custom constructor for querying
48
+ # This is required because the input message uses different classes
49
+ class QueryLoader(yaml.Loader):
50
+ pass
51
+
52
+
53
+ # Loader class with the custom constructor for updates
54
+ # This is required because the input message uses different classes
55
+ class UpdateLoader(yaml.Loader):
56
+ pass
57
+
58
+
59
+ class TxnConfigYaml:
60
+ """
61
+ This class holds all the configuration and methods for working with the TransactionTypeConfiguration
62
+
63
+ Each object in the TransactionTypeConfiguration has a representation method which converts a LUSID model to YAML
64
+ with the suffix "_rep" and a constructor method which converts YAML to a LUSID model with the suffix "_con".
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ models,
70
+ UpdateLoader: yaml.Loader = UpdateLoader,
71
+ QueryLoader: yaml.Loader = QueryLoader,
72
+ CustomDumper: yaml.Dumper = CustomDumper,
73
+ ):
74
+ """
75
+ The initialisation here will set up the custom constructors and representers. It will add these to the the
76
+ CustomDumper, QueryLoader and UpdateLoader.
77
+
78
+ :param models: The lusid.models
79
+ :param yaml.Loader UpdateLoader: The loader to use for updating (writing to LUSID)
80
+ :param yaml.Loader QueryLoader: The loader to use for querying (reading from LUSID)
81
+ :param yaml.Dumper CustomDumper: The dumper used for converting LUSID models to YAML
82
+ """
83
+ self.YAMLConverter(
84
+ "!Txn",
85
+ self.config_rep,
86
+ self.TransactionSetConfigurationDataNoLinks,
87
+ self.config_con,
88
+ self.TransactionSetConfigurationDataNoLinks,
89
+ models.TransactionSetConfigurationDataRequest,
90
+ )
91
+
92
+ self.YAMLConverter(
93
+ "!TxnConfig",
94
+ self.trans_config_rep,
95
+ models.TransactionConfigurationData,
96
+ self.trans_config_con,
97
+ models.TransactionConfigurationData,
98
+ models.TransactionConfigurationDataRequest,
99
+ )
100
+
101
+ self.YAMLConverter(
102
+ "!Mvmt",
103
+ self.mvmt_rep,
104
+ models.TransactionConfigurationMovementData,
105
+ self.mvmt_con,
106
+ models.TransactionConfigurationMovementData,
107
+ models.TransactionConfigurationMovementDataRequest,
108
+ )
109
+
110
+ self.YAMLConverter(
111
+ "!Map",
112
+ self.pm_rep,
113
+ models.TransactionPropertyMapping,
114
+ self.pm_con,
115
+ models.TransactionPropertyMapping,
116
+ models.TransactionPropertyMappingRequest,
117
+ )
118
+
119
+ self.YAMLConverter(
120
+ "!Alias",
121
+ self.alias_rep,
122
+ models.TransactionConfigurationTypeAlias,
123
+ self.alias_con,
124
+ models.TransactionConfigurationTypeAlias,
125
+ models.TransactionConfigurationTypeAlias,
126
+ )
127
+
128
+ self.YAMLConverter(
129
+ "!SideConfig",
130
+ self.side_config_rep,
131
+ models.SideConfigurationData,
132
+ self.side_config_con,
133
+ models.SideConfigurationData,
134
+ models.SideConfigurationDataRequest,
135
+ )
136
+
137
+ self.YAMLConverter(
138
+ "!Prop",
139
+ self.pp_rep,
140
+ models.PerpetualProperty,
141
+ self.pp_con,
142
+ models.PerpetualProperty,
143
+ models.PerpetualProperty,
144
+ )
145
+
146
+ class YAMLConverter:
147
+ """
148
+ This class handles the configuration for a single element of the model e.g. aliases on a
149
+ TransactionTypeConfiguration.
150
+ """
151
+
152
+ def __init__(
153
+ self,
154
+ tag: str,
155
+ representation_function: Callable,
156
+ representation_model,
157
+ constructor_function: Callable,
158
+ constructor_read_model,
159
+ constructor_update_model,
160
+ representation_dumper: yaml.Dumper = CustomDumper,
161
+ constructor_read_loader: yaml.Loader = QueryLoader,
162
+ constructor_update_loader: yaml.Loader = UpdateLoader,
163
+ ):
164
+ """
165
+ The initialisation function creates the representation and constructor functions from the provided
166
+ base callable functions and registers them with the dumper and loaders.
167
+
168
+ :param str tag: The tag to use for this node
169
+ :param Callable representation_function: The base representation function to use when converting to YAML
170
+ :param lusid.models representation_model: The model to convert from when representing as YAML
171
+ :param constructor_function: The base constructor function to use when converting from YAML
172
+ :param lusid.models constructor_read_model: The model to use for reading from LUSID
173
+ :param lusid.models constructor_update_model: The model to use for sending to LUSID
174
+ (usually has "Request" added to the name)
175
+ :param yaml.Dumper representation_dumper: The dumper to use for converting to YAML
176
+ :param yaml.Loader constructor_read_loader: The loader to use for converting from YAML when reading from LUSID
177
+ :param yaml.Loader constructor_update_loader: The loader to use for converting from YAML when writing to LUSID
178
+ """
179
+
180
+ # Maps the types of the element to the appropriate dumper method
181
+ type_mapping = {
182
+ "<class 'dict'>": "represent_mapping",
183
+ "<class 'str'>": "represent_scalar",
184
+ "<class 'list'>": "represent_sequence",
185
+ }
186
+
187
+ def representation_function_dumper(
188
+ dumper: yaml.Dumper, data, repr: Callable = representation_function
189
+ ) -> Callable:
190
+ """
191
+ This function is used by the dumper to turn a LUSID model object into YAML
192
+
193
+ :param yaml.Dumper dumper: The dumper to use
194
+ :param data: The data
195
+ :param Callable repr: The base representation function
196
+
197
+ :return: Callable
198
+ """
199
+ # Get the payload from the representation function
200
+ payload = repr(data)
201
+ # Get the appropriate dumper function to use
202
+ representation_type = type_mapping[str((type(payload)))]
203
+
204
+ # Construct and return the appropriate dumper representation function
205
+ if representation_type == "represent_sequence":
206
+ return getattr(dumper, representation_type)(
207
+ tag, payload, flow_style=True
208
+ )
209
+ else:
210
+ return getattr(dumper, representation_type)(tag, payload)
211
+
212
+ # Add the dumper function
213
+ yaml.add_representer(
214
+ data_type=representation_model,
215
+ representer=representation_function_dumper,
216
+ Dumper=representation_dumper,
217
+ )
218
+
219
+ def constructor_function_read(
220
+ loader: yaml.Loader, node, con: Callable = constructor_function
221
+ ):
222
+ """
223
+ This function is used to construct a LUSID model from YAML when reading from LUSID
224
+
225
+ :param yaml.Loader loader: The YAML loader to use
226
+ :param node: The YAML node to construct the model from
227
+ :param con: The base constructor function
228
+
229
+ :return: lusid.models: The populated LUSID model
230
+ """
231
+ return constructor_read_model(*con(loader, node))
232
+
233
+ def constructor_function_update(loader, node, con=constructor_function):
234
+ """
235
+ This function is used to construct a LUSID model from YAML when writing to LUSID. The only difference
236
+ from the "constructor_function_read" is that it has a different model.
237
+
238
+ :param yaml.Loader loader: The YAML loader to use
239
+ :param node: The YAML node to construct the model from
240
+ :param con: The base constructor function
241
+
242
+ :return: lusid.models: The populated LUSID model
243
+ """
244
+ return constructor_update_model(*con(loader, node))
245
+
246
+ # Add the YAML to LUSID model constructor for reading from LUSID
247
+ yaml.add_constructor(
248
+ tag=tag,
249
+ constructor=constructor_function_read,
250
+ Loader=constructor_read_loader,
251
+ )
252
+
253
+ # Add the YAML to LUSID model constructor for writing to LUSID
254
+ yaml.add_constructor(
255
+ tag=tag,
256
+ constructor=constructor_function_update,
257
+ Loader=constructor_update_loader,
258
+ )
259
+
260
+ @staticmethod
261
+ def find_by_tag(node: yaml.Node, tag: str):
262
+ """
263
+ Utility function which searches the node until the specified tag is first found returning the discovered node
264
+
265
+ :param yaml.Node node: The node to search
266
+ :param str tag: The tag to search for in the node
267
+
268
+ :return: yaml.Node: The first discovered node which matches the tag
269
+ """
270
+ for i in node.value:
271
+ if i[0].value == tag:
272
+ return i[1]
273
+
274
+ @staticmethod
275
+ def prepare_properties(properties_node_list: list) -> dict:
276
+ """
277
+ Utility which prepares a single property from a properties node so that it can be sent to LUSID as a dictionary
278
+
279
+ :param list properties_node_list: The properties node to prepare
280
+
281
+ :return dict(str, models.PerpetualProperty) properties: The property in a dictionary
282
+ """
283
+ if len(properties_node_list) > 0:
284
+ single_property = {n.key: n for n in properties_node_list}
285
+ else:
286
+ single_property = {}
287
+
288
+ return single_property
289
+
290
+ # Set up the YAML converter for the TransactionSetConfigurationData (top level object)
291
+ @staticmethod
292
+ def config_rep(data):
293
+ return {
294
+ "transactionConfigRequests": data.transaction_configs,
295
+ "sideConfigRequests": data.side_definitions,
296
+ }
297
+
298
+ @classmethod
299
+ def config_con(cls, loader, node):
300
+ txn = loader.construct_sequence(
301
+ cls.find_by_tag(node, "transactionConfigRequests")
302
+ )
303
+ side = loader.construct_sequence(cls.find_by_tag(node, "sideConfigRequests"))
304
+ return txn, side
305
+
306
+ # Set up the YAML converter for the TransactionConfigurationData (transaction_configs)
307
+ @staticmethod
308
+ def trans_config_rep(data):
309
+ return {
310
+ "aliases": data.aliases,
311
+ "movements": data.movements,
312
+ "properties": list(data.properties.values()),
313
+ }
314
+
315
+ @classmethod
316
+ def trans_config_con(cls, loader, node):
317
+ a = loader.construct_sequence(cls.find_by_tag(node, "aliases"))
318
+ m = loader.construct_sequence(cls.find_by_tag(node, "movements"))
319
+ p = cls.prepare_properties(
320
+ loader.construct_sequence(cls.find_by_tag(node, "properties"))
321
+ )
322
+ return a, m, p
323
+
324
+ # Set up the YAML converter for the TransactionConfigurationMovementData (movements)
325
+ @staticmethod
326
+ def mvmt_rep(data):
327
+ return [
328
+ [abbrev(data.side), data.direction, abbrev(data.movement_types)],
329
+ list(data.properties.values()),
330
+ data.mappings,
331
+ ] + ([data.name] if data.name else [])
332
+
333
+ @classmethod
334
+ def mvmt_con(cls, loader, node):
335
+ s = loader.construct_sequence(node.value[0])
336
+ properties = cls.prepare_properties(loader.construct_sequence(node.value[1]))
337
+ side = unabbrev(s[0])
338
+ direction = s[1]
339
+ movement_types = unabbrev(s[2])
340
+ mappings = loader.construct_sequence(node.value[2])
341
+
342
+ name = None
343
+ if len(node.value) > 3:
344
+ name = node.value[3].value
345
+ if name == "null":
346
+ name = None
347
+
348
+ return movement_types, side, direction, properties, mappings, name
349
+
350
+ # Set up the YAML converter for the TransactionPropertyMapping (mappings)
351
+ @staticmethod
352
+ def pm_rep(data):
353
+ if data.map_from is not None:
354
+ return "{}=F:{}".format(data.property_key, data.map_from)
355
+ else:
356
+ return "{}=S:{}".format(data.property_key, data.set_to)
357
+
358
+ @staticmethod
359
+ def pm_con(loader, node):
360
+ s = node.value.split("=")
361
+
362
+ property_key = s[0]
363
+
364
+ if s[1].startswith("F:"):
365
+ map_from = s[1][2:]
366
+ set_to = None
367
+ else:
368
+ set_to = s[1][2:]
369
+ map_from = None
370
+
371
+ return property_key, map_from, set_to
372
+
373
+ # Set up the YAML converter for the TransactionConfigurationTypeAlias (aliases)
374
+ @staticmethod
375
+ def alias_rep(data):
376
+ return [
377
+ data.type,
378
+ data.description,
379
+ data.transaction_group,
380
+ data.source,
381
+ data.transaction_class,
382
+ abbrev(data.transaction_roles),
383
+ ] + (["default"] if data.is_default else [])
384
+
385
+ @staticmethod
386
+ def alias_con(loader, node):
387
+ s = loader.construct_sequence(node)
388
+ # If old yaml files are used with only 5 params set for txn type alias (SENG-41)
389
+ if len(s) == 5:
390
+ return (
391
+ s[0],
392
+ s[1],
393
+ s[3],
394
+ s[3],
395
+ s[2],
396
+ unabbrev(s[4]),
397
+ (len(s) > 5 and s[5] == "default"),
398
+ )
399
+ # If newer yaml files are used with additional params set for txn type alias (SENG-41)
400
+ else:
401
+ return (
402
+ s[0],
403
+ s[1],
404
+ s[4],
405
+ s[3],
406
+ s[2],
407
+ unabbrev(s[5]),
408
+ (len(s) > 6 and s[6] == "default"),
409
+ )
410
+
411
+ # Set up the YAML converter for the SideConfigurationDataRequest (side)
412
+ @staticmethod
413
+ def side_config_rep(data):
414
+ return [
415
+ data.side,
416
+ data.security,
417
+ data.currency,
418
+ data.rate,
419
+ data.units,
420
+ data.amount,
421
+ ]
422
+
423
+ @staticmethod
424
+ def side_config_con(loader, node):
425
+ s = loader.construct_sequence(node)
426
+ side = s[0]
427
+ security = s[1]
428
+ currency = s[2]
429
+ rate = s[3]
430
+ units = s[4]
431
+ amount = s[5]
432
+ return side, security, currency, rate, units, amount
433
+
434
+ # Set up the YAML converter for PerpetualProperties (properties)
435
+ @staticmethod
436
+ def pp_rep(data):
437
+ return abbrev("{}={}".format(data.key, data.value.label_value))
438
+
439
+ @staticmethod
440
+ def pp_con(loader, node):
441
+ s = unabbrev(node.value).split("=")
442
+ return (s[0], models.property_value.PropertyValue(s[1]))
443
+
444
+ # END OF THE INITIALISER ############################################
445
+
446
+ # Write the yaml file with validation that the custom view doesn't drop any
447
+ # important data
448
+ class TransactionSetConfigurationDataNoLinks:
449
+ """
450
+ This class is used to replace TransactionSetConfigurationData so that it has no extra information e.g. links
451
+ which get converted to YAML.
452
+ """
453
+
454
+ def __init__(self, transaction_configs, side_definitions):
455
+ self.transaction_configs = transaction_configs
456
+ self.side_definitions = side_definitions
457
+
458
+ def dump(self, obj, filename, raw=False):
459
+
460
+ if raw:
461
+ with open(filename, "w") as stream:
462
+ yaml.dump(obj, stream)
463
+ else:
464
+ orig = yaml.dump(obj, width=500)
465
+ cust = yaml.dump(obj, Dumper=CustomDumper, width=500)
466
+ copy = yaml.dump(yaml.load(cust, Loader=QueryLoader), width=500)
467
+
468
+ if orig != copy:
469
+ print("DIFFS - writing to orig/copy")
470
+ with open("orig", "w") as stream:
471
+ stream.write(orig)
472
+ with open("copy", "w") as stream:
473
+ stream.write(copy)
474
+ with open(filename, "w") as stream:
475
+ stream.write(cust)
476
+
477
+ # Get the YAML using the custom view
478
+ def get_yaml(self, obj):
479
+ return yaml.dump(obj, Dumper=CustomDumper)
480
+
481
+ # Read a YAML file
482
+ def load(self, filename):
483
+ with open(filename, "r") as stream:
484
+ return yaml.load(stream, Loader=QueryLoader)
485
+
486
+ # Read a YAML file and convert to the UPDATE request message format
487
+ def load_update(self, filename):
488
+ with open(filename, "r") as stream:
489
+ return yaml.load(stream, Loader=UpdateLoader)
490
+
491
+ # Read a YAML string and convert to the UPDATE request message format
492
+ def load_update_str(self, s):
493
+ return yaml.load(s, Loader=UpdateLoader)
File without changes
@@ -0,0 +1,128 @@
1
+ import pandas as pd
2
+ from flatten_json import flatten
3
+ import logging
4
+
5
+ logger = logging.getLogger()
6
+
7
+
8
+ def lusid_response_to_data_frame(
9
+ lusid_response, rename_properties: bool = False, column_name_mapping: dict = None
10
+ ):
11
+ """
12
+ This function takes a LUSID API response and attempts to convert the response into a Pandas DataFrame or Series.
13
+ Not all LUSID API responses are the same datatype. Therefore we need to implement some if/else conditional logic to
14
+ see how the response should be converted.
15
+ In terms of an implementation, the function checks for two attributes on the response:
16
+ (1) A "values" attribute - these are lists of LUSID objects. Example: the "Get Holdings" response has a values
17
+ attribute which contains a list of individual holding objects.
18
+ (2) A "to_dict" attribute - this is one dictionary of values.
19
+
20
+ Parameters
21
+ ----------
22
+ lusid_response
23
+ a response from the LUSID APIs (e.g. VersionedResourceListOfPortfolioHolding)
24
+ rename_properties : bool
25
+ this parameter formats the returned DataFrame.
26
+ Specifically, the formatter does two things; (1) removes any metadata columns for properties and SHKs, and (2)
27
+ simplifies the naming of column headers for properties and SHKs.
28
+ rename_mapping : dict
29
+ a dictionary which is used to map old column headers to new ones.
30
+ The dictionary key is old header while the dictionary value in new header. If key does not exist, the function
31
+ will ignore the non-existant mapping.
32
+
33
+ Returns
34
+ -------
35
+
36
+ pandas.DataFrame
37
+ """
38
+
39
+ # Check if lusid_response is a list of the same objects which all have to_dict() method
40
+
41
+ if type(lusid_response) == list and len(lusid_response) == 0:
42
+ return pd.DataFrame()
43
+
44
+ elif type(lusid_response) == list and len(lusid_response) > 0:
45
+
46
+ first_item_type = type(lusid_response[0])
47
+
48
+ if not all(isinstance(x, first_item_type) for x in lusid_response):
49
+ raise TypeError("All items in list must be of the same data type")
50
+
51
+ if not hasattr(first_item_type, "to_dict"):
52
+ raise TypeError("All object items in list must have a to_dict() method")
53
+
54
+ response_df = pd.DataFrame(
55
+ flatten(value.to_dict(), ".") for value in lusid_response
56
+ )
57
+
58
+ # Check if lusid_response has a values attribute with data type of list
59
+
60
+ elif hasattr(lusid_response, "values") and type(lusid_response.values) == list:
61
+
62
+ response_df = pd.DataFrame(
63
+ flatten(value.to_dict(), ".") for value in lusid_response.values
64
+ )
65
+
66
+ # Check if response object has to_dict() method
67
+
68
+ elif hasattr(lusid_response, "to_dict"):
69
+
70
+ response_df = pd.DataFrame.from_dict(
71
+ (flatten(lusid_response.to_dict(), ".")),
72
+ orient="index",
73
+ columns=["response_values"],
74
+ )
75
+ else:
76
+
77
+ raise TypeError(
78
+ """Cannot map response object to pandas DataFrame or Series. The LUSID response object must have
79
+ either the values attribute or the to_dict() method, or be a list of objects with
80
+ the to_dict() method"""
81
+ )
82
+
83
+ if rename_properties:
84
+
85
+ # Collect columns to drop - these are meta-data columns for properties and sub-holding keys
86
+ columns_to_drop = list(
87
+ response_df.filter(
88
+ regex="(^sub_holding_keys|^properties).*(key$|metric_value$|effective_from$)"
89
+ ).columns
90
+ )
91
+
92
+ response_df.drop(columns_to_drop, axis=1, inplace=True)
93
+
94
+ # Rename the properties and sub-holding keys column to show the property "code" only
95
+ # Recall the format is "domain/scope/code"
96
+
97
+ rename_dict = {}
98
+
99
+ columns_to_rename = response_df.filter(
100
+ regex="(^sub_holding_keys|^properties).*(label_value$|value.metric_value.value$)"
101
+ ).columns
102
+
103
+ for column in columns_to_rename:
104
+
105
+ # find the property code substring
106
+ # The property code should always be displayed after the second "/"
107
+ # Example: sub_holding_keys.Transaction/MultiAssetScope/PortionSubClass.value.label_value
108
+ property_code = str(column[(column.rfind("/") + 1) :])[
109
+ : column[(column.rfind("/") + 1) :].find(".")
110
+ ]
111
+
112
+ # find the property scope substring
113
+ scope_string = column[(column.find("/") + 1) : (column.rfind("/"))]
114
+
115
+ # find the property or SHK suffix
116
+ p_or_shk = column[: column.find(".")].title().replace("_", "")
117
+
118
+ rename_dict[column] = (
119
+ property_code + "(" + scope_string + "-" + p_or_shk + ")"
120
+ )
121
+
122
+ response_df.rename(columns=rename_dict, inplace=True)
123
+
124
+ if column_name_mapping:
125
+
126
+ response_df.rename(columns=column_name_mapping, inplace=True)
127
+
128
+ return response_df.dropna(axis=1, how="all")
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 FINBOURNE
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.1
2
+ Name: finbourne-sdk-utils
3
+ Version: 0.0.24
4
+ Summary: Python utilitiesfor LUSID V2 SDK
5
+ Home-page: https://github.com/finbourne/finbourne-sdk-utils
6
+ Author: FINBOURNE Technology
7
+ Author-email: engineering@finbourne.com
8
+ License: MIT
9
+ Keywords: FINBOURNE,LUSID,LUSID SDK,python
10
+ Requires-Python: >=3.7
11
+ License-File: LICENSE
12
+ Requires-Dist: urllib3>=1.26.9
13
+ Requires-Dist: requests>=2.27.1
14
+ Requires-Dist: coloredlogs>=14.0
15
+ Requires-Dist: detect-delimiter>=0.1
16
+ Requires-Dist: flatten-json>=0.1.7
17
+ Requires-Dist: pandas>=1.1.4
18
+ Requires-Dist: PyYAML>=5.4
19
+ Requires-Dist: tqdm>=4.52.0
20
+ Requires-Dist: openpyxl>=3.0.7
21
+ Requires-Dist: xlrd~=1.2
22
+ Requires-Dist: pytz>=2019.3
23
+ Requires-Dist: IPython>=7.31.1
24
+ Requires-Dist: lusid-sdk>=2
25
+