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.
- features/__init__.py +0 -0
- features/main.py +11 -0
- finbourne_sdk_utils/__init__.py +8 -0
- finbourne_sdk_utils/cocoon/__init__.py +34 -0
- finbourne_sdk_utils/cocoon/async_tools.py +94 -0
- finbourne_sdk_utils/cocoon/cocoon.py +1862 -0
- finbourne_sdk_utils/cocoon/cocoon_printer.py +455 -0
- finbourne_sdk_utils/cocoon/config/domain_settings.json +125 -0
- finbourne_sdk_utils/cocoon/config/seed_sample_data.json +36 -0
- finbourne_sdk_utils/cocoon/dateorcutlabel.py +198 -0
- finbourne_sdk_utils/cocoon/instruments.py +482 -0
- finbourne_sdk_utils/cocoon/properties.py +442 -0
- finbourne_sdk_utils/cocoon/seed_sample_data.py +137 -0
- finbourne_sdk_utils/cocoon/systemConfiguration.py +92 -0
- finbourne_sdk_utils/cocoon/transaction_type_upload.py +136 -0
- finbourne_sdk_utils/cocoon/utilities.py +1877 -0
- finbourne_sdk_utils/cocoon/validator.py +243 -0
- finbourne_sdk_utils/extract/__init__.py +1 -0
- finbourne_sdk_utils/extract/group_holdings.py +400 -0
- finbourne_sdk_utils/iam/__init__.py +1 -0
- finbourne_sdk_utils/iam/roles.py +74 -0
- finbourne_sdk_utils/jupyter_tools/__init__.py +2 -0
- finbourne_sdk_utils/jupyter_tools/hide_code_button.py +23 -0
- finbourne_sdk_utils/jupyter_tools/stop_execution.py +14 -0
- finbourne_sdk_utils/logger/LusidLogger.py +41 -0
- finbourne_sdk_utils/logger/__init__.py +1 -0
- finbourne_sdk_utils/lpt/__init__.py +0 -0
- finbourne_sdk_utils/lpt/back_compat.py +20 -0
- finbourne_sdk_utils/lpt/cash_ladder.py +191 -0
- finbourne_sdk_utils/lpt/connect_lusid.py +64 -0
- finbourne_sdk_utils/lpt/connect_none.py +5 -0
- finbourne_sdk_utils/lpt/connect_token.py +9 -0
- finbourne_sdk_utils/lpt/dfq.py +321 -0
- finbourne_sdk_utils/lpt/either.py +65 -0
- finbourne_sdk_utils/lpt/get_instruments.py +101 -0
- finbourne_sdk_utils/lpt/lpt.py +374 -0
- finbourne_sdk_utils/lpt/lse.py +188 -0
- finbourne_sdk_utils/lpt/map_instruments.py +164 -0
- finbourne_sdk_utils/lpt/pager.py +32 -0
- finbourne_sdk_utils/lpt/record.py +13 -0
- finbourne_sdk_utils/lpt/refreshing_token.py +43 -0
- finbourne_sdk_utils/lpt/search_instruments.py +48 -0
- finbourne_sdk_utils/lpt/stdargs.py +154 -0
- finbourne_sdk_utils/lpt/txn_config.py +128 -0
- finbourne_sdk_utils/lpt/txn_config_yaml.py +493 -0
- finbourne_sdk_utils/pandas_utils/__init__.py +0 -0
- finbourne_sdk_utils/pandas_utils/lusid_pandas.py +128 -0
- finbourne_sdk_utils-0.0.24.dist-info/LICENSE +21 -0
- finbourne_sdk_utils-0.0.24.dist-info/METADATA +25 -0
- finbourne_sdk_utils-0.0.24.dist-info/RECORD +52 -0
- finbourne_sdk_utils-0.0.24.dist-info/WHEEL +5 -0
- 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
|
+
|