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.
- otcfinutils-0.0.24.15/OTCFinUtils/__init__.py +69 -0
- otcfinutils-0.0.24.15/OTCFinUtils/buffer.py +103 -0
- otcfinutils-0.0.24.15/OTCFinUtils/data_structs.py +242 -0
- otcfinutils-0.0.24.15/OTCFinUtils/dataverse_handler.py +691 -0
- otcfinutils-0.0.24.15/OTCFinUtils/mapper.py +706 -0
- otcfinutils-0.0.24.15/OTCFinUtils/mapper_loading.py +185 -0
- otcfinutils-0.0.24.15/OTCFinUtils/security.py +127 -0
- otcfinutils-0.0.24.15/OTCFinUtils/sharepoint.py +194 -0
- otcfinutils-0.0.24.15/OTCFinUtils/sharepoint_client.py +136 -0
- otcfinutils-0.0.24.15/OTCFinUtils/utils.py +503 -0
- otcfinutils-0.0.24.15/PKG-INFO +12 -0
- otcfinutils-0.0.24.15/README.md +81 -0
- otcfinutils-0.0.24.15/otcfinutils.egg-info/PKG-INFO +12 -0
- otcfinutils-0.0.24.15/otcfinutils.egg-info/SOURCES.txt +22 -0
- otcfinutils-0.0.24.15/otcfinutils.egg-info/dependency_links.txt +1 -0
- otcfinutils-0.0.24.15/otcfinutils.egg-info/not-zip-safe +1 -0
- otcfinutils-0.0.24.15/otcfinutils.egg-info/requires.txt +6 -0
- otcfinutils-0.0.24.15/otcfinutils.egg-info/top_level.txt +1 -0
- otcfinutils-0.0.24.15/setup.cfg +4 -0
- otcfinutils-0.0.24.15/setup.py +19 -0
- otcfinutils-0.0.24.15/tests/test_dataverse_handler.py +2 -0
- otcfinutils-0.0.24.15/tests/test_mapper.py +211 -0
- otcfinutils-0.0.24.15/tests/test_security.py +7 -0
- otcfinutils-0.0.24.15/tests/test_sharepoint.py +2 -0
|
@@ -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"
|