-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathProcess.py
More file actions
589 lines (506 loc) · 24.6 KB
/
Copy pathProcess.py
File metadata and controls
589 lines (506 loc) · 24.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
# -*- coding: utf-8 -*-
import json
import re
from typing import Dict, Iterable, List, Optional, Union
from TM1py.Objects.TM1Object import TM1Object
from TM1py.Utils import verify_version
class Process(TM1Object):
"""Abstraction of a TM1 Process.
IMPORTANT. doesn't work with Processes that were generated through the Wizard
"""
""" the auto_generated_string code is required to be in all code-tabs. """
BEGIN_GENERATED_STATEMENTS = "#****Begin: Generated Statements***"
END_GENERATED_STATEMENTS = "#****End: Generated Statements****"
AUTO_GENERATED_STATEMENTS = "{}\r\n{}\r\n".format(BEGIN_GENERATED_STATEMENTS, END_GENERATED_STATEMENTS)
MAX_STATEMENTS = 16_380
MAX_STATEMENTS_POST_11_8_015 = 100_000
@staticmethod
def max_statements(version: str):
if verify_version(required_version="11.8.015", version=version):
return Process.MAX_STATEMENTS_POST_11_8_015
return Process.MAX_STATEMENTS
@staticmethod
def add_generated_string_to_code(code: str) -> str:
pattern = r"(?s)#\*\*\*\*Begin: Generated Statements(.*)#\*\*\*\*End: Generated Statements\*\*\*\*"
if re.search(pattern=pattern, string=code):
return code
else:
return Process.AUTO_GENERATED_STATEMENTS + code
def __init__(
self,
name: str,
has_security_access: Optional[bool] = False,
ui_data: str = "CubeAction=1511\fDataAction=1503\fCubeLogChanges=0\f",
parameters: Iterable = None,
variables: Iterable = None,
variables_ui_data: Iterable = None,
prolog_procedure: str = "",
metadata_procedure: str = "",
data_procedure: str = "",
epilog_procedure: str = "",
datasource_type: str = "None",
datasource_ascii_decimal_separator: str = ".",
datasource_ascii_delimiter_char: str = ";",
datasource_ascii_delimiter_type: str = "Character",
datasource_ascii_header_records: int = 1,
datasource_ascii_quote_character: str = "",
datasource_ascii_thousand_separator: str = ",",
datasource_data_source_name_for_client: str = "",
datasource_data_source_name_for_server: str = "",
datasource_password: str = "",
datasource_user_name: str = "",
datasource_query: str = "",
datasource_uses_unicode: bool = True,
datasource_view: str = "",
datasource_subset: str = "",
datasource_json_root_pointer: str = "",
datasource_json_variable_mapping: str = "",
# --- NEW: Apache Arrow Flight (TM1 12.6.1+) ---
datasource_flight_location: str = "",
datasource_flight_descriptor_type: str = "",
datasource_flight_descriptor: str = "",
datasource_flight_auth: str = "",
):
"""Default construcor
:param name: name of the process - mandatory
:param has_security_access:
:param ui_data:
:param parameters:
:param variables:
:param variables_ui_data:
:param prolog_procedure:
:param metadata_procedure:
:param data_procedure:
:param epilog_procedure:
:param datasource_type:
:param datasource_ascii_decimal_separator:
:param datasource_ascii_delimiter_char:
:param datasource_ascii_delimiter_type:
:param datasource_ascii_header_records:
:param datasource_ascii_quote_character:
:param datasource_ascii_thousand_separator:
:param datasource_data_source_name_for_client:
:param datasource_data_source_name_for_server:
:param datasource_password:
:param datasource_user_name:
:param datasource_query:
:param datasource_uses_unicode:
:param datasource_view:
:param datasource_subset:
:param datasource_json_root_pointer:
:param datasource_json_variable_mapping:
:param datasource_flight_location: Arrow Flight gRPC URI, e.g. "grpc://host:8443" (TM1 12.6.1+)
:param datasource_flight_descriptor_type: Arrow Flight descriptor type: "PATH" or "COMMAND" (TM1 12.6.1+)
:param datasource_flight_descriptor: Arrow Flight descriptor, e.g. "dataset/path" or "SELECT * FROM t" (TM1 12.6.1+)
:param datasource_flight_auth: Arrow Flight auth header, e.g. "Bearer <token>" or "Basic user:pass" (TM1 12.6.1+)
"""
self._name = name
self._has_security_access = has_security_access
self._ui_data = ui_data
self._parameters = list(parameters) if parameters else []
self._variables = list(variables) if variables else []
if variables_ui_data:
# Handle encoding issue in variable_ui_data for async requests
self._variables_ui_data = [entry.replace("€", "\f") for entry in variables_ui_data]
else:
self._variables_ui_data = []
self._prolog_procedure = Process.add_generated_string_to_code(prolog_procedure)
self._metadata_procedure = Process.add_generated_string_to_code(metadata_procedure)
self._data_procedure = Process.add_generated_string_to_code(data_procedure)
self._epilog_procedure = Process.add_generated_string_to_code(epilog_procedure)
self._datasource_type = datasource_type
self._datasource_ascii_decimal_separator = datasource_ascii_decimal_separator
self._datasource_ascii_delimiter_char = datasource_ascii_delimiter_char
self._datasource_ascii_delimiter_type = datasource_ascii_delimiter_type
self._datasource_ascii_header_records = datasource_ascii_header_records
self._datasource_ascii_quote_character = datasource_ascii_quote_character
self._datasource_ascii_thousand_separator = datasource_ascii_thousand_separator
self._datasource_data_source_name_for_client = datasource_data_source_name_for_client
self._datasource_data_source_name_for_server = datasource_data_source_name_for_server
self._datasource_password = datasource_password
self._datasource_user_name = datasource_user_name
self._datasource_query = datasource_query
self._datasource_uses_unicode = datasource_uses_unicode
self._datasource_view = datasource_view
self._datasource_subset = datasource_subset
self._datasource_json_root_pointer = datasource_json_root_pointer
self._datasource_json_variable_mapping = datasource_json_variable_mapping
self._datasource_flight_location = datasource_flight_location
self._datasource_flight_descriptor_type = datasource_flight_descriptor_type
self._datasource_flight_descriptor = datasource_flight_descriptor
self._datasource_flight_auth = datasource_flight_auth
@classmethod
def from_json(cls, process_as_json: str) -> "Process":
"""
:param process_as_json: response of /Processes('x')?$expand=*
:return: an instance of this class
"""
process_as_dict = json.loads(process_as_json)
return cls.from_dict(process_as_dict)
@classmethod
def from_dict(cls, process_as_dict: Dict) -> "Process":
"""
:param process_as_dict: Dictionary, process as dictionary
:return: an instance of this class
"""
return cls(
name=process_as_dict["Name"],
has_security_access=process_as_dict["HasSecurityAccess"],
ui_data=process_as_dict.get("UIData", ""),
parameters=process_as_dict["Parameters"],
variables=process_as_dict["Variables"],
variables_ui_data=process_as_dict.get("VariablesUIData", ""),
prolog_procedure=process_as_dict["PrologProcedure"],
metadata_procedure=process_as_dict["MetadataProcedure"],
data_procedure=process_as_dict["DataProcedure"],
epilog_procedure=process_as_dict["EpilogProcedure"],
datasource_type=process_as_dict["DataSource"].get("Type", ""),
datasource_ascii_decimal_separator=process_as_dict["DataSource"].get("asciiDecimalSeparator", ""),
datasource_ascii_delimiter_char=process_as_dict["DataSource"].get("asciiDelimiterChar", ""),
datasource_ascii_delimiter_type=process_as_dict["DataSource"].get("asciiDelimiterType", ""),
datasource_ascii_header_records=process_as_dict["DataSource"].get("asciiHeaderRecords", ""),
datasource_ascii_quote_character=process_as_dict["DataSource"].get("asciiQuoteCharacter", ""),
datasource_ascii_thousand_separator=process_as_dict["DataSource"].get("asciiThousandSeparator", ""),
datasource_data_source_name_for_client=process_as_dict["DataSource"].get("dataSourceNameForClient", ""),
datasource_data_source_name_for_server=process_as_dict["DataSource"].get("dataSourceNameForServer", ""),
datasource_password=process_as_dict["DataSource"].get("password", ""),
datasource_user_name=process_as_dict["DataSource"].get("userName", ""),
datasource_query=process_as_dict["DataSource"].get("query", ""),
datasource_uses_unicode=process_as_dict["DataSource"].get("usesUnicode", ""),
datasource_view=process_as_dict["DataSource"].get("view", ""),
datasource_subset=process_as_dict["DataSource"].get("subset", ""),
datasource_json_root_pointer=process_as_dict["DataSource"].get("jsonRootPointer", ""),
datasource_json_variable_mapping=process_as_dict["DataSource"].get("jsonVariableMapping", ""),
datasource_flight_location=process_as_dict["DataSource"].get("flightLocation", ""),
datasource_flight_descriptor_type=process_as_dict["DataSource"].get("flightDescriptorType", ""),
datasource_flight_descriptor=process_as_dict["DataSource"].get("flightDescriptor", ""),
datasource_flight_auth=process_as_dict["DataSource"].get("flightAuth", ""),
)
@property
def body(self) -> str:
return self._construct_body()
@property
def body_as_dict(self) -> Dict:
return self._construct_body_as_dict()
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, value: str):
self._name = value
@property
def has_security_access(self) -> bool:
return self._has_security_access
@has_security_access.setter
def has_security_access(self, value: bool):
self._has_security_access = value
@property
def variables(self) -> List:
return self._variables
@property
def parameters(self) -> List:
return self._parameters
@property
def prolog_procedure(self) -> str:
return self._prolog_procedure
@prolog_procedure.setter
def prolog_procedure(self, value: str):
self._prolog_procedure = Process.add_generated_string_to_code(value)
@property
def metadata_procedure(self) -> str:
return self._metadata_procedure
@metadata_procedure.setter
def metadata_procedure(self, value: str):
self._metadata_procedure = Process.add_generated_string_to_code(value)
@property
def data_procedure(self) -> str:
return self._data_procedure
@data_procedure.setter
def data_procedure(self, value: str):
self._data_procedure = Process.add_generated_string_to_code(value)
@property
def epilog_procedure(self) -> str:
return self._epilog_procedure
@epilog_procedure.setter
def epilog_procedure(self, value: str):
self._epilog_procedure = Process.add_generated_string_to_code(value)
@property
def all_procedures(self) -> str:
return self._prolog_procedure + self._metadata_procedure + self._data_procedure + self._epilog_procedure
@property
def datasource_type(self) -> str:
return self._datasource_type
@datasource_type.setter
def datasource_type(self, value: str):
self._datasource_type = value
@property
def datasource_ascii_decimal_separator(self) -> str:
return self._datasource_ascii_decimal_separator
@datasource_ascii_decimal_separator.setter
def datasource_ascii_decimal_separator(self, value: str):
self._datasource_ascii_decimal_separator = value
@property
def datasource_ascii_delimiter_char(self) -> str:
return self._datasource_ascii_delimiter_char
@datasource_ascii_delimiter_char.setter
def datasource_ascii_delimiter_char(self, value: str):
self._datasource_ascii_delimiter_char = value
@property
def datasource_ascii_delimiter_type(self) -> str:
return self._datasource_ascii_delimiter_type
@datasource_ascii_delimiter_type.setter
def datasource_ascii_delimiter_type(self, value: str):
self._datasource_ascii_delimiter_type = value
@property
def datasource_ascii_header_records(self) -> int:
return self._datasource_ascii_header_records
@datasource_ascii_header_records.setter
def datasource_ascii_header_records(self, value: int):
self._datasource_ascii_header_records = value
@property
def datasource_ascii_quote_character(self) -> str:
return self._datasource_ascii_quote_character
@datasource_ascii_quote_character.setter
def datasource_ascii_quote_character(self, value: str):
self._datasource_ascii_quote_character = value
@property
def datasource_ascii_thousand_separator(self) -> str:
return self._datasource_ascii_thousand_separator
@datasource_ascii_thousand_separator.setter
def datasource_ascii_thousand_separator(self, value: str):
self._datasource_ascii_thousand_separator = value
@property
def datasource_data_source_name_for_client(self) -> str:
return self._datasource_data_source_name_for_client
@datasource_data_source_name_for_client.setter
def datasource_data_source_name_for_client(self, value: str):
self._datasource_data_source_name_for_client = value
@property
def datasource_data_source_name_for_server(self) -> str:
return self._datasource_data_source_name_for_server
@datasource_data_source_name_for_server.setter
def datasource_data_source_name_for_server(self, value: str):
self._datasource_data_source_name_for_server = value
@property
def datasource_password(self) -> str:
return self._datasource_password
@datasource_password.setter
def datasource_password(self, value: str):
self._datasource_password = value
@property
def datasource_user_name(self) -> str:
return self._datasource_user_name
@datasource_user_name.setter
def datasource_user_name(self, value: str):
self._datasource_user_name = value
@property
def datasource_query(self) -> str:
return self._datasource_query
@datasource_query.setter
def datasource_query(self, value: str):
self._datasource_query = value
@property
def datasource_uses_unicode(self) -> bool:
return self._datasource_uses_unicode
@datasource_uses_unicode.setter
def datasource_uses_unicode(self, value: bool):
self._datasource_uses_unicode = value
@property
def datasource_view(self) -> str:
return self._datasource_view
@datasource_view.setter
def datasource_view(self, value: str):
self._datasource_view = value
@property
def datasource_subset(self) -> str:
return self._datasource_subset
@datasource_subset.setter
def datasource_subset(self, value: str):
self._datasource_subset = value
@property
def datasource_json_root_pointer(self) -> str:
return self._datasource_json_root_pointer
@datasource_json_root_pointer.setter
def datasource_json_root_pointer(self, value: str):
self._datasource_json_root_pointer = value
@property
def datasource_json_variable_mapping(self) -> str:
return self._datasource_json_variable_mapping
@datasource_json_variable_mapping.setter
def datasource_json_variable_mapping(self, value: str):
self._datasource_json_variable_mapping = value
@property
def datasource_flight_location(self) -> str:
return self._datasource_flight_location
@datasource_flight_location.setter
def datasource_flight_location(self, value: str):
self._datasource_flight_location = value
@property
def datasource_flight_descriptor_type(self) -> str:
return self._datasource_flight_descriptor_type
@datasource_flight_descriptor_type.setter
def datasource_flight_descriptor_type(self, value: str):
self._datasource_flight_descriptor_type = value
@property
def datasource_flight_descriptor(self) -> str:
return self._datasource_flight_descriptor
@datasource_flight_descriptor.setter
def datasource_flight_descriptor(self, value: str):
self._datasource_flight_descriptor = value
@property
def datasource_flight_auth(self) -> str:
return self._datasource_flight_auth
@datasource_flight_auth.setter
def datasource_flight_auth(self, value: str):
self._datasource_flight_auth = value
def add_variable(self, name: str, variable_type: str):
"""add variable to the process
:param name: -
:param variable_type: 'String' or 'Numeric'
:return:
"""
# variable consists of actual variable and UI-Information ('ignore','other', etc.)
# 1. handle Variable info
variable = {
"Name": name,
"Type": variable_type,
"Position": len(self._variables) + 1,
"StartByte": 0,
"EndByte": 0,
}
self._variables.append(variable)
# 2. handle UI info
var_type = 33 if variable_type == "Numeric" else 32
# '\f' !
variable_ui_data = "VarType=" + str(var_type) + "\f" + "ColType=" + str(827) + "\f"
"""
mapping VariableUIData:
VarType 33 -> Numeric
VarType 32 -> String
ColType 827 -> Other
"""
self._variables_ui_data.append(variable_ui_data)
def remove_variable(self, name: str):
for variable in self.variables[:]:
if variable["Name"] == name:
vuid = self._variables_ui_data[self._variables.index(variable)]
self._variables_ui_data.remove(vuid)
self._variables.remove(variable)
def add_parameter(
self, name: str, prompt: str, value: Union[str, int, float], parameter_type: Optional[str] = None
):
"""
:param name:
:param prompt:
:param value:
:param parameter_type: introduced in TM1 11 REST API, therefor optional. if Not given type is derived from value
:return:
"""
if not parameter_type:
parameter_type = "String" if isinstance(value, str) else "Numeric"
parameter = {"Name": name, "Prompt": prompt, "Value": value, "Type": parameter_type}
self._parameters.append(parameter)
def remove_parameter(self, name: str):
for parameter in self.parameters[:]:
if parameter["Name"] == name:
self._parameters.remove(parameter)
def drop_parameter_types(self):
for p in range(len(self.parameters)):
if "Type" in self.parameters[p]:
del self.parameters[p]["Type"]
# construct self.body (json) from the class-attributes
def _construct_body(self) -> str:
return json.dumps(self._construct_body_as_dict(), ensure_ascii=False)
def _construct_body_as_dict(self) -> Dict:
# general parameters
body_as_dict = {
"Name": self._name,
"PrologProcedure": self._prolog_procedure,
"MetadataProcedure": self._metadata_procedure,
"DataProcedure": self._data_procedure,
"EpilogProcedure": self._epilog_procedure,
"HasSecurityAccess": self._has_security_access,
"UIData": self._ui_data,
"DataSource": {},
"Parameters": self._parameters,
"Variables": self._variables,
"VariablesUIData": self._variables_ui_data,
}
# specific parameters (depending on datasource type)
if self._datasource_type == "ASCII":
body_as_dict["DataSource"] = {
"Type": self._datasource_type,
"asciiDecimalSeparator": self._datasource_ascii_decimal_separator,
"asciiDelimiterChar": self._datasource_ascii_delimiter_char,
"asciiDelimiterType": self._datasource_ascii_delimiter_type,
"asciiHeaderRecords": self._datasource_ascii_header_records,
"asciiQuoteCharacter": self._datasource_ascii_quote_character,
"asciiThousandSeparator": self._datasource_ascii_thousand_separator,
"dataSourceNameForClient": self._datasource_data_source_name_for_client,
"dataSourceNameForServer": self._datasource_data_source_name_for_server,
}
if self._datasource_ascii_delimiter_type == "FixedWidth":
del body_as_dict["DataSource"]["asciiDelimiterChar"]
elif self._datasource_type == "None":
body_as_dict["DataSource"] = {"Type": "None"}
elif self._datasource_type == "ODBC":
body_as_dict["DataSource"] = {
"Type": self._datasource_type,
"dataSourceNameForClient": self._datasource_data_source_name_for_client,
"dataSourceNameForServer": self._datasource_data_source_name_for_server,
"userName": self._datasource_user_name,
"password": self._datasource_password,
"query": self._datasource_query,
"usesUnicode": self._datasource_uses_unicode,
}
elif self._datasource_type == "TM1CubeView":
body_as_dict["DataSource"] = {
"Type": self._datasource_type,
"dataSourceNameForClient": self._datasource_data_source_name_for_server,
"dataSourceNameForServer": self._datasource_data_source_name_for_server,
"view": self._datasource_view,
}
elif self._datasource_type == "TM1DimensionSubset":
body_as_dict["DataSource"] = {
"Type": self._datasource_type,
"dataSourceNameForClient": self._datasource_data_source_name_for_server,
"dataSourceNameForServer": self._datasource_data_source_name_for_server,
"subset": self._datasource_subset,
}
elif self._datasource_type == "JSON":
body_as_dict["DataSource"] = {
"Type": self._datasource_type,
"dataSourceNameForClient": self._datasource_data_source_name_for_server,
"dataSourceNameForServer": self._datasource_data_source_name_for_server,
"jsonRootPointer": self._datasource_json_root_pointer,
"jsonVariableMapping": self._datasource_json_variable_mapping,
}
elif self._datasource_type.upper() in ("ARROW", "PARQUET"):
# Apache Arrow IPC/Feather or Parquet file datasource (TM1 12.6.1+).
# File datasources: just the (file name OR http/https URL) — no ascii*
# delimiter/quote/header keys. Optional JSON-treatment for nested columns
# reuses the JSON datasource's jsonRootPointer / jsonVariableMapping.
# NB: the server canonicalizes Type to title-case ("Arrow"/"Parquet") on
# read-back, so match case-insensitively to keep from_dict round-trips intact.
body_as_dict["DataSource"] = {
"Type": self._datasource_type,
"dataSourceNameForClient": self._datasource_data_source_name_for_client,
"dataSourceNameForServer": self._datasource_data_source_name_for_server,
}
if self._datasource_json_root_pointer:
body_as_dict["DataSource"]["jsonRootPointer"] = self._datasource_json_root_pointer
if self._datasource_json_variable_mapping:
body_as_dict["DataSource"]["jsonVariableMapping"] = self._datasource_json_variable_mapping
elif self._datasource_type.upper() == "FLIGHT":
# Apache Arrow Flight datasource (TM1 12.6.1+): TM1 is a Flight CLIENT that
# streams record batches from a remote Flight server into TI variables.
# Wire field names verified against a live 12.6.1 server: flightLocation /
# flightDescriptorType / flightDescriptor / flightAuth (NOT dataSourceFlight*).
body_as_dict["DataSource"] = {
"Type": self._datasource_type,
"flightLocation": self._datasource_flight_location,
"flightDescriptorType": self._datasource_flight_descriptor_type,
"flightDescriptor": self._datasource_flight_descriptor,
"flightAuth": self._datasource_flight_auth,
}
return body_as_dict