otcfinutils 0.0.24.15__tar.gz

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.
@@ -0,0 +1,69 @@
1
+ from OTCFinUtils.dataverse_handler import (
2
+ DVHandler,
3
+ get_dv_handler,
4
+ set_up_dv_handler,
5
+ )
6
+ from OTCFinUtils.buffer import Buffer
7
+ from OTCFinUtils.data_structs import (
8
+ ResponseIds,
9
+ APIParams,
10
+ HTTPMethod,
11
+ KeyLookupParams,
12
+ EntityKeyLookupParams,
13
+ IDParams,
14
+ EntityLookupParams,
15
+ Category,
16
+ Lookup,
17
+ EntityLookups,
18
+ MappingFields,
19
+ Operator,
20
+ Condition,
21
+ DEFAULT_SHEET_CHUNK_SIZE,
22
+ SHEETS_RULE_TYPE_CHOICE,
23
+ FileSource,
24
+ )
25
+ from OTCFinUtils.mapper_loading import (
26
+ _extract_key_api_params,
27
+ _extract_batch_response_objects,
28
+ _extract_object_ids,
29
+ _extract_object_lookups,
30
+ _extract_curr_object_lookups,
31
+ get_buffer_lookups,
32
+ get_buffer_ids,
33
+ )
34
+ from OTCFinUtils.mapper import DataMapper
35
+ from OTCFinUtils.security import (
36
+ get_client_credential,
37
+ get_dataverse_token,
38
+ get_graph_token,
39
+ )
40
+ from OTCFinUtils.sharepoint import (
41
+ load_document,
42
+ get_connection_details,
43
+ create_excel_document,
44
+ update_excel,
45
+ )
46
+ from OTCFinUtils.utils import (
47
+ extract_segment_object_line,
48
+ extract_segment_response_id,
49
+ extract_segment_status_code,
50
+ extract_segment_table_name,
51
+ process_data_type,
52
+ extract_lookup_column,
53
+ extract_file_data,
54
+ data_equals_exclude_value,
55
+ trim_string_values,
56
+ is_date,
57
+ extract_date_string,
58
+ choice_value,
59
+ normalize_choice_map,
60
+ default_value,
61
+ cast_data,
62
+ get_skip_flag,
63
+ data_is_empty,
64
+ evaluate_condition,
65
+ is_valid,
66
+ group_mappings,
67
+ extract_sorted_categories,
68
+ extract_sheets,
69
+ )
@@ -0,0 +1,103 @@
1
+ from OTCFinUtils.data_structs import (
2
+ EntityKeyLookupParams,
3
+ EntityLookupParams,
4
+ IDParams,
5
+ )
6
+
7
+
8
+ class Buffer:
9
+ def __init__(self) -> None:
10
+ """
11
+ Buffers for temporarily storing entity information.
12
+ Need to use them for the batch reading / writing operations.
13
+ """
14
+ self.entity_data: dict = dict()
15
+ self.entity_id_params: IDParams = IDParams()
16
+ self.entity_lookup_params: dict[str, IDParams] = dict()
17
+ self.entity_key_lookup_params: EntityKeyLookupParams = dict()
18
+
19
+ """
20
+ Buffers for temporarily storing information for all entities in a category.
21
+ Need to use them for the batch reading / writing operations.
22
+ """
23
+ self.category_data: list[dict] = []
24
+ self.category_id_params: list[IDParams] = []
25
+ self.category_lookup_params: list[EntityLookupParams] = []
26
+ self.category_key_lookup_params: list[EntityKeyLookupParams] = []
27
+
28
+
29
+ def _load_id_params_buffer(self) -> None:
30
+ """
31
+ Loads the buffer for the entity to the buffer for the category.
32
+ This buffer is used for a batch query for the ids of the entities.
33
+ Resets the entity buffer.
34
+ """
35
+ self.category_id_params.append(self.entity_id_params)
36
+ self.entity_id_params = IDParams()
37
+
38
+
39
+ def _load_data_buffer(self) -> None:
40
+ """
41
+ Loads the buffer for the entity to the buffer for the category.
42
+ This is the buffer that will be bulk-upserted in the end.
43
+ Resets the entity buffer.
44
+ """
45
+ self.category_data.append(self.entity_data)
46
+ self.entity_data = dict()
47
+
48
+
49
+ def _load_lookup_params_buffer(self) -> None:
50
+ """
51
+ Loads the lookup buffer for the entity, which is a dictionary,
52
+ into the lookup buffer for the category. Transforms the dictionary
53
+ to a list, because that's how the the DV Handler requires it.
54
+ Resets the entity buffer.
55
+ """
56
+ entity_lookup_params_list = list(self.entity_lookup_params.values())
57
+ self.category_lookup_params.append(entity_lookup_params_list)
58
+ self.entity_lookup_params = dict()
59
+
60
+
61
+ def _load_key_lookup_params_buffer(self) -> None:
62
+ """
63
+ Loads the key-lookup params for the entity into
64
+ the key-lookup buffer for the category of data.
65
+ """
66
+ self.category_key_lookup_params.append(self.entity_key_lookup_params)
67
+ self.entity_key_lookup_params = dict()
68
+
69
+
70
+ def reset_entity_buffers(self) -> None:
71
+ """
72
+ Resets all buffers for the current entity.
73
+ Used in the entity cycle.
74
+ """
75
+ self.entity_data = dict()
76
+ self.entity_id_params = IDParams()
77
+ self.entity_lookup_params = dict()
78
+ self.entity_key_lookup_params = dict()
79
+
80
+
81
+ def reset_category_buffers(self):
82
+ """
83
+ Reset all category-level buffers.
84
+ """
85
+ self.category_id_params = []
86
+ self.category_lookup_params = []
87
+ self.category_key_lookup_params = []
88
+ self.category_data = []
89
+
90
+
91
+ def load_buffers(self) -> None:
92
+ """
93
+ Because we will query and upsert the data in batches,
94
+ we need to store everything in our buffers.
95
+ """
96
+ try:
97
+ self._load_lookup_params_buffer()
98
+ self._load_id_params_buffer()
99
+ self._load_data_buffer()
100
+ self._load_key_lookup_params_buffer()
101
+
102
+ except Exception as e:
103
+ raise RuntimeError(f"Error while loading buffers: {e}")
@@ -0,0 +1,242 @@
1
+ from http import HTTPStatus as status
2
+ from enum import Enum
3
+ from dataclasses import dataclass, field
4
+ from typing import Optional, Any
5
+ from collections import namedtuple
6
+
7
+
8
+ @dataclass
9
+ class ResponseIds:
10
+ """
11
+ Used as a return value from the upsert operation on the DV.
12
+ Contains the table names and ids of the entities that were
13
+ updated and created, stored separately.
14
+ """
15
+ def __init__(self) -> None:
16
+ self.updated_entity_ids: dict[str, set[str]] = dict()
17
+ self.created_entity_ids: dict[str, set[str]] = dict()
18
+ self.failed_operations: list[str] = list()
19
+
20
+
21
+ def append(self, status_code: int, id: str, table: str) -> None:
22
+ """
23
+ Depending on the status code appends the id and
24
+ table name to the create / update category.
25
+ """
26
+ if status_code == status.OK.value and table in self.updated_entity_ids:
27
+ self.updated_entity_ids[table].add(id)
28
+
29
+ elif status_code == status.OK.value and table not in self.updated_entity_ids:
30
+ self.updated_entity_ids[table] = set()
31
+ self.updated_entity_ids[table].add(id)
32
+
33
+ elif status_code == status.CREATED.value and table in self.created_entity_ids:
34
+ self.created_entity_ids[table].add(id)
35
+
36
+ elif status_code == status.CREATED.value and table not in self.created_entity_ids:
37
+ self.created_entity_ids[table] = set()
38
+ self.created_entity_ids[table].add(id)
39
+
40
+ return
41
+
42
+
43
+ def merge(self, delta_ids: "ResponseIds") -> None:
44
+ """
45
+ Merges the values from the provided object onto the
46
+ the values of the calling object.
47
+ """
48
+ for table, ids in delta_ids.created_entity_ids.items():
49
+ if table not in self.created_entity_ids:
50
+ self.created_entity_ids[table] = set()
51
+ self.created_entity_ids[table].update(ids)
52
+
53
+ for table, ids in delta_ids.updated_entity_ids.items():
54
+ if table not in self.updated_entity_ids:
55
+ self.updated_entity_ids[table] = set()
56
+ self.updated_entity_ids[table].update(ids)
57
+
58
+ self.failed_operations.extend(delta_ids.failed_operations)
59
+
60
+ return
61
+
62
+
63
+ def json_updated_entities(self) -> dict[str, list]:
64
+ result = dict()
65
+ for table, ids in self.updated_entity_ids.items():
66
+ result[table] = list(ids)
67
+ return result
68
+
69
+
70
+ def json_created_entities(self) -> dict[str, list]:
71
+ result = dict()
72
+ for table, ids in self.created_entity_ids.items():
73
+ result[table] = list(ids)
74
+ return result
75
+
76
+
77
+ def num_updated_entities(self) -> int:
78
+ total = 0
79
+ for ids in self.updated_entity_ids.values():
80
+ total += len(ids)
81
+ return total
82
+
83
+ def num_created_entities(self) -> int:
84
+ total = 0
85
+ for ids in self.created_entity_ids.values():
86
+ total += len(ids)
87
+ return total
88
+
89
+ def num_failed_operations(self) -> int:
90
+ return len(self.failed_operations)
91
+
92
+
93
+ class FileSource(Enum):
94
+ DV = "DV"
95
+ SP = "SP"
96
+
97
+
98
+ @dataclass
99
+ class APIParams:
100
+ """
101
+ Basic helper data structure for making API calls.
102
+ """
103
+ url: str
104
+ method: str
105
+ data: dict | None = None
106
+
107
+
108
+ class QueryOperator(Enum):
109
+ AND = "and"
110
+ OR = "or"
111
+
112
+
113
+ @dataclass
114
+ class QueryParams:
115
+ table: str
116
+ columns: Optional[list[str]] = None
117
+ conditions: Optional[list[str]] = None
118
+ operator: QueryOperator = QueryOperator.AND
119
+ top_num: Optional[int] = None
120
+
121
+
122
+ class HTTPMethod(Enum):
123
+ GET = "GET"
124
+ POST = "POST"
125
+ PATCH = "PATCH"
126
+ DELETE = "DELETE"
127
+
128
+
129
+ @dataclass
130
+ class KeyLookupParams:
131
+ lookup_id_column: str
132
+ lookup_id: Optional[str]
133
+
134
+
135
+ EntityKeyLookupParams = dict[str, KeyLookupParams]
136
+
137
+
138
+ @dataclass
139
+ class IDParams:
140
+ """
141
+ A group of parameters used for querying id info of entities.
142
+ """
143
+ table: Optional[str] = None
144
+ id_column: Optional[str] = None
145
+ conditions: list[str] = field(default_factory=list)
146
+
147
+ # If we have a lookup
148
+ bind_column: Optional[str] = None
149
+
150
+
151
+ EntityLookupParams = list[IDParams]
152
+
153
+ """
154
+ Used to divide and sort the file loading mappings
155
+ """
156
+ Category = namedtuple("Category", ["group_num", "table", "sheet"])
157
+
158
+ """
159
+ Used for storing the id info of the lookup entity
160
+ """
161
+ Lookup = namedtuple("Lookup", ["bind_column", "table", "id"])
162
+
163
+ """
164
+ Needed because each entity can have several lookups to other entities
165
+ """
166
+ EntityLookups = list[Lookup]
167
+
168
+
169
+ class MappingFields:
170
+ LOOKUP_TABLE = "new_lookuptable"
171
+ LOOKUP_RELATIONSHIP = "new_lookuprelationship"
172
+ LOOKUP_COLUMN = "new_lookupcolumn"
173
+ DV_COLUMN = "new_dataversecolumnname"
174
+ DV_TABLE = "new_dataversetablenametext"
175
+ FILE_COLUMN = "new_filecolumnname"
176
+ DEFAULT_VALUE = "new_defaultvalue"
177
+ DATA_TYPE = "new_dataversedatatype"
178
+ IS_LOOKUP = "new_islookup"
179
+ IS_KEY = "new_iskey"
180
+ IS_CHOICE = "new_ischoice"
181
+ CHOICE_DICTIONARY = "new_choicedictionary"
182
+ CONDITION = "new_condition"
183
+ ALTERNATIVE_FILE_COLUMN = "new_alternativefilecolumnname"
184
+ EXCLUDE_VALUE = "new_excludevalue"
185
+ MAP_GROUP_ORDER = "new_mapgrouporder"
186
+ SHEET = "new_sheetname"
187
+
188
+
189
+ @dataclass
190
+ class Mapping:
191
+ dv_column: str
192
+ dv_table: str
193
+ file_column: str
194
+ map_group_order: int
195
+ sheet: str
196
+ default_value: Optional[str] = None
197
+ data_type: Optional[str] = None
198
+ lookup_table: Optional[str] = None
199
+ lookup_relationship: Optional[str] = None
200
+ lookup_column: Optional[str] = None
201
+ is_lookup: bool = False
202
+ is_key: bool = False
203
+ is_choice: bool = False
204
+ choice_dictionary: Optional[str] = None
205
+ condition: Optional[str] = None
206
+ alternative_file_column: Optional[str] = None
207
+ exclude_value: Optional[str] = None
208
+
209
+
210
+ class Operator(Enum):
211
+ EQUALS = "EQUALS"
212
+ NOT_EQUALS = "NOT_EQUALS"
213
+ IS_NULL = "IS_NULL"
214
+ IS_NOT_NULL = "IS_NOT_NULL"
215
+ SMALLER = "SMALLER"
216
+ GREATER = "GREATER"
217
+ GREATER_OR_EQUAL = "GREATER_OR_EQUAL"
218
+ SMALLER_OR_EQUAL = "SMALLER_OR_EQUAL"
219
+
220
+
221
+ @dataclass
222
+ class Condition:
223
+ column: str
224
+ operator: Operator
225
+ is_static_value: bool
226
+ compare_value: Any
227
+ result_value: Any
228
+
229
+ @classmethod
230
+ def create(cls, obj: dict) -> "Condition":
231
+ return cls(
232
+ column=obj["column"],
233
+ operator=Operator(obj["operator"]),
234
+ is_static_value=obj["is_static_value"],
235
+ compare_value=obj["compare_value"],
236
+ result_value=obj["result_value"],
237
+ )
238
+
239
+
240
+ DEFAULT_SHEET_CHUNK_SIZE = 1000
241
+ SHEETS_RULE_TYPE_CHOICE = 12
242
+ NOT_DEFINED = "NOT DEFINED"