TM1py 2.2.1__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.
- TM1py/Exceptions/Exceptions.py +201 -0
- TM1py/Exceptions/__init__.py +10 -0
- TM1py/Objects/Annotation.py +247 -0
- TM1py/Objects/Application.py +188 -0
- TM1py/Objects/Axis.py +119 -0
- TM1py/Objects/Chore.py +165 -0
- TM1py/Objects/ChoreFrequency.py +68 -0
- TM1py/Objects/ChoreStartTime.py +93 -0
- TM1py/Objects/ChoreTask.py +80 -0
- TM1py/Objects/Cube.py +116 -0
- TM1py/Objects/Dimension.py +128 -0
- TM1py/Objects/Element.py +110 -0
- TM1py/Objects/ElementAttribute.py +75 -0
- TM1py/Objects/Git.py +76 -0
- TM1py/Objects/GitCommit.py +27 -0
- TM1py/Objects/GitPlan.py +101 -0
- TM1py/Objects/GitProject.py +525 -0
- TM1py/Objects/GitRemote.py +28 -0
- TM1py/Objects/Hierarchy.py +343 -0
- TM1py/Objects/MDXView.py +84 -0
- TM1py/Objects/NativeView.py +331 -0
- TM1py/Objects/Process.py +512 -0
- TM1py/Objects/ProcessDebugBreakpoint.py +236 -0
- TM1py/Objects/Rules.py +100 -0
- TM1py/Objects/Sandbox.py +87 -0
- TM1py/Objects/Server.py +30 -0
- TM1py/Objects/Subset.py +295 -0
- TM1py/Objects/TM1Object.py +27 -0
- TM1py/Objects/User.py +179 -0
- TM1py/Objects/View.py +39 -0
- TM1py/Objects/__init__.py +28 -0
- TM1py/Services/AnnotationService.py +96 -0
- TM1py/Services/ApplicationService.py +898 -0
- TM1py/Services/AuditLogService.py +100 -0
- TM1py/Services/CellService.py +5465 -0
- TM1py/Services/ChoreService.py +299 -0
- TM1py/Services/ConfigurationService.py +80 -0
- TM1py/Services/CubeService.py +426 -0
- TM1py/Services/DimensionService.py +213 -0
- TM1py/Services/ElementService.py +1489 -0
- TM1py/Services/FileService.py +419 -0
- TM1py/Services/GitService.py +292 -0
- TM1py/Services/HierarchyService.py +895 -0
- TM1py/Services/JobService.py +56 -0
- TM1py/Services/LoggerService.py +97 -0
- TM1py/Services/ManageService.py +219 -0
- TM1py/Services/MessageLogService.py +161 -0
- TM1py/Services/MonitoringService.py +94 -0
- TM1py/Services/ObjectService.py +85 -0
- TM1py/Services/PowerBiService.py +81 -0
- TM1py/Services/ProcessService.py +753 -0
- TM1py/Services/RestService.py +1395 -0
- TM1py/Services/SandboxService.py +135 -0
- TM1py/Services/SecurityService.py +255 -0
- TM1py/Services/ServerService.py +292 -0
- TM1py/Services/SessionService.py +65 -0
- TM1py/Services/SubsetService.py +305 -0
- TM1py/Services/TM1Service.py +183 -0
- TM1py/Services/ThreadService.py +73 -0
- TM1py/Services/TransactionLogService.py +107 -0
- TM1py/Services/UserService.py +71 -0
- TM1py/Services/ViewService.py +308 -0
- TM1py/Services/__init__.py +33 -0
- TM1py/Utils/MDXUtils.py +248 -0
- TM1py/Utils/Utils.py +1833 -0
- TM1py/Utils/__init__.py +2 -0
- TM1py/__init__.py +84 -0
- tm1py-2.2.1.dist-info/METADATA +181 -0
- tm1py-2.2.1.dist-info/RECORD +72 -0
- tm1py-2.2.1.dist-info/WHEEL +5 -0
- tm1py-2.2.1.dist-info/licenses/LICENSE +22 -0
- tm1py-2.2.1.dist-info/top_level.txt +1 -0
TM1py/Objects/Axis.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import json
|
|
5
|
+
from typing import Dict, Union
|
|
6
|
+
|
|
7
|
+
from TM1py.Objects.Subset import AnonymousSubset, Subset
|
|
8
|
+
from TM1py.Objects.TM1Object import TM1Object
|
|
9
|
+
from TM1py.Utils import format_url
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ViewAxisSelection(TM1Object):
|
|
13
|
+
"""Describes what is selected in a dimension on an axis. Can be a Registered Subset or an Anonymous Subset"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, dimension_name: str, subset: Union[Subset, AnonymousSubset]):
|
|
16
|
+
"""
|
|
17
|
+
:Parameters:
|
|
18
|
+
`dimension_name` : String
|
|
19
|
+
`subset` : Subset or AnonymousSubset
|
|
20
|
+
"""
|
|
21
|
+
self._subset = subset
|
|
22
|
+
self._dimension_name = dimension_name
|
|
23
|
+
self._hierarchy_name = dimension_name
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def subset(self) -> Union[Subset, AnonymousSubset]:
|
|
27
|
+
return self._subset
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def dimension_name(self) -> str:
|
|
31
|
+
return self._dimension_name
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def hierarchy_name(self) -> str:
|
|
35
|
+
return self._hierarchy_name
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def body(self) -> str:
|
|
39
|
+
return json.dumps(self._construct_body(), ensure_ascii=False)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def body_as_dict(self) -> Dict:
|
|
43
|
+
return self._construct_body()
|
|
44
|
+
|
|
45
|
+
def _construct_body(self) -> Dict:
|
|
46
|
+
"""construct the ODATA conform JSON represenation for the ViewAxisSelection entity.
|
|
47
|
+
|
|
48
|
+
:return: dictionary
|
|
49
|
+
"""
|
|
50
|
+
body_as_dict = collections.OrderedDict()
|
|
51
|
+
if isinstance(self._subset, AnonymousSubset):
|
|
52
|
+
body_as_dict["Subset"] = json.loads(self._subset.body)
|
|
53
|
+
elif isinstance(self._subset, Subset):
|
|
54
|
+
subset_path = format_url(
|
|
55
|
+
"Dimensions('{}')/Hierarchies('{}')/Subsets('{}')",
|
|
56
|
+
self._dimension_name,
|
|
57
|
+
self._hierarchy_name,
|
|
58
|
+
self._subset.name,
|
|
59
|
+
)
|
|
60
|
+
body_as_dict["Subset@odata.bind"] = subset_path
|
|
61
|
+
return body_as_dict
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ViewTitleSelection:
|
|
65
|
+
"""Describes what is selected in a dimension on the view title.
|
|
66
|
+
Can be a Registered Subset or an Anonymous Subset
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, dimension_name: str, subset: Union[AnonymousSubset, Subset], selected: str):
|
|
71
|
+
self._dimension_name = dimension_name
|
|
72
|
+
self._hierarchy_name = dimension_name
|
|
73
|
+
self._subset = subset
|
|
74
|
+
self._selected = selected
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def subset(self) -> Union[Subset, AnonymousSubset]:
|
|
78
|
+
return self._subset
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def dimension_name(self) -> str:
|
|
82
|
+
return self._dimension_name
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def hierarchy_name(self) -> str:
|
|
86
|
+
return self._hierarchy_name
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def selected(self) -> str:
|
|
90
|
+
return self._selected
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def body(self) -> str:
|
|
94
|
+
return json.dumps(self._construct_body(), ensure_ascii=False)
|
|
95
|
+
|
|
96
|
+
def _construct_body(self) -> Dict:
|
|
97
|
+
"""construct the ODATA conform JSON represenation for the ViewTitleSelection entity.
|
|
98
|
+
|
|
99
|
+
:return: string, the valid JSON
|
|
100
|
+
"""
|
|
101
|
+
body_as_dict = collections.OrderedDict()
|
|
102
|
+
if isinstance(self._subset, AnonymousSubset):
|
|
103
|
+
body_as_dict["Subset"] = json.loads(self._subset.body)
|
|
104
|
+
elif isinstance(self._subset, Subset):
|
|
105
|
+
subset_path = format_url(
|
|
106
|
+
"Dimensions('{}')/Hierarchies('{}')/Subsets('{}')",
|
|
107
|
+
self._dimension_name,
|
|
108
|
+
self._hierarchy_name,
|
|
109
|
+
self._subset.name,
|
|
110
|
+
)
|
|
111
|
+
body_as_dict["Subset@odata.bind"] = subset_path
|
|
112
|
+
element_path = format_url(
|
|
113
|
+
"Dimensions('{}')/Hierarchies('{}')/Elements('{}')",
|
|
114
|
+
self._dimension_name,
|
|
115
|
+
self._hierarchy_name,
|
|
116
|
+
self._selected,
|
|
117
|
+
)
|
|
118
|
+
body_as_dict["Selected@odata.bind"] = element_path
|
|
119
|
+
return body_as_dict
|
TM1py/Objects/Chore.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import json
|
|
5
|
+
from typing import Dict, Iterable, List
|
|
6
|
+
|
|
7
|
+
from TM1py.Objects.ChoreFrequency import ChoreFrequency
|
|
8
|
+
from TM1py.Objects.ChoreStartTime import ChoreStartTime
|
|
9
|
+
from TM1py.Objects.ChoreTask import ChoreTask
|
|
10
|
+
from TM1py.Objects.TM1Object import TM1Object
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Chore(TM1Object):
|
|
14
|
+
"""Abstraction of TM1 Chore"""
|
|
15
|
+
|
|
16
|
+
SINGLE_COMMIT = "SingleCommit"
|
|
17
|
+
MULTIPLE_COMMIT = "MultipleCommit"
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
name: str,
|
|
22
|
+
start_time: ChoreStartTime,
|
|
23
|
+
dst_sensitivity: bool,
|
|
24
|
+
active: bool,
|
|
25
|
+
execution_mode: str,
|
|
26
|
+
frequency: ChoreFrequency,
|
|
27
|
+
tasks: Iterable[ChoreTask],
|
|
28
|
+
):
|
|
29
|
+
self._name = name
|
|
30
|
+
self._start_time = start_time
|
|
31
|
+
self._dst_sensitivity = dst_sensitivity
|
|
32
|
+
self._active = active
|
|
33
|
+
self._execution_mode = execution_mode
|
|
34
|
+
self._frequency = frequency
|
|
35
|
+
self._tasks = list(tasks)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_json(cls, chore_as_json: str) -> "Chore":
|
|
39
|
+
"""Alternative constructor
|
|
40
|
+
|
|
41
|
+
:param chore_as_json: string, JSON. Response of /Chores('x')/Tasks?$expand=*
|
|
42
|
+
:return: Chore, an instance of this class
|
|
43
|
+
"""
|
|
44
|
+
chore_as_dict = json.loads(chore_as_json)
|
|
45
|
+
return cls.from_dict(chore_as_dict)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_dict(cls, chore_as_dict: Dict) -> "Chore":
|
|
49
|
+
"""Alternative constructor
|
|
50
|
+
|
|
51
|
+
:param chore_as_dict: Chore as dict
|
|
52
|
+
:return: Chore, an instance of this class
|
|
53
|
+
"""
|
|
54
|
+
return cls(
|
|
55
|
+
name=chore_as_dict["Name"],
|
|
56
|
+
start_time=ChoreStartTime.from_string(chore_as_dict["StartTime"]),
|
|
57
|
+
dst_sensitivity=chore_as_dict["DSTSensitive"],
|
|
58
|
+
active=chore_as_dict["Active"],
|
|
59
|
+
execution_mode=chore_as_dict["ExecutionMode"],
|
|
60
|
+
frequency=ChoreFrequency.from_string(chore_as_dict["Frequency"]),
|
|
61
|
+
tasks=[ChoreTask.from_dict(task, step) for step, task in enumerate(chore_as_dict["Tasks"])],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def name(self) -> str:
|
|
66
|
+
return self._name
|
|
67
|
+
|
|
68
|
+
@name.setter
|
|
69
|
+
def name(self, name: str):
|
|
70
|
+
self._name = name
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def start_time(self) -> ChoreStartTime:
|
|
74
|
+
return self._start_time
|
|
75
|
+
|
|
76
|
+
@start_time.setter
|
|
77
|
+
def start_time(self, start_time: ChoreStartTime):
|
|
78
|
+
self._start_time = start_time
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def dst_sensitivity(self) -> bool:
|
|
82
|
+
return self._dst_sensitivity
|
|
83
|
+
|
|
84
|
+
@dst_sensitivity.setter
|
|
85
|
+
def dst_sensitivity(self, dst_sensitivity: bool):
|
|
86
|
+
self._dst_sensitivity = dst_sensitivity
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def active(self) -> bool:
|
|
90
|
+
return self._active
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def execution_mode(self) -> str:
|
|
94
|
+
return self._execution_mode
|
|
95
|
+
|
|
96
|
+
@execution_mode.setter
|
|
97
|
+
def execution_mode(self, execution_mode):
|
|
98
|
+
self._execution_mode = execution_mode
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def frequency(self) -> ChoreFrequency:
|
|
102
|
+
return self._frequency
|
|
103
|
+
|
|
104
|
+
@frequency.setter
|
|
105
|
+
def frequency(self, frequency: ChoreFrequency):
|
|
106
|
+
self._frequency = frequency
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def tasks(self) -> List[ChoreTask]:
|
|
110
|
+
return self._tasks
|
|
111
|
+
|
|
112
|
+
@tasks.setter
|
|
113
|
+
def tasks(self, tasks: List[ChoreTask]):
|
|
114
|
+
self._tasks = tasks
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def body(self) -> str:
|
|
118
|
+
return self.construct_body()
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def body_as_dict(self) -> Dict:
|
|
122
|
+
return json.loads(self.body)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def execution_path(self) -> Dict:
|
|
126
|
+
"""
|
|
127
|
+
1 chore together with its executed processes
|
|
128
|
+
Use case: building out a tree of chores and their processes (and again the processes that are called by the latter (if any)).
|
|
129
|
+
:return: dictionary containing chore name as the key and a list of process names as the value
|
|
130
|
+
"""
|
|
131
|
+
return {self.name: [task.process_name for task in self.tasks]}
|
|
132
|
+
|
|
133
|
+
def add_task(self, task: ChoreTask):
|
|
134
|
+
self._tasks.append(task)
|
|
135
|
+
|
|
136
|
+
def insert_task(self, new_task: ChoreTask):
|
|
137
|
+
task_list = self.tasks
|
|
138
|
+
for task in task_list[new_task._step :]:
|
|
139
|
+
task._step = task._step + 1
|
|
140
|
+
task_list.insert(new_task._step, new_task)
|
|
141
|
+
self.tasks = task_list
|
|
142
|
+
|
|
143
|
+
def activate(self):
|
|
144
|
+
self._active = True
|
|
145
|
+
|
|
146
|
+
def deactivate(self):
|
|
147
|
+
self._active = False
|
|
148
|
+
|
|
149
|
+
def reschedule(self, days: int = 0, hours: int = 0, minutes: int = 0, seconds: int = 0):
|
|
150
|
+
self._start_time.add(days=days, hours=hours, minutes=minutes, seconds=seconds)
|
|
151
|
+
|
|
152
|
+
def construct_body(self) -> str:
|
|
153
|
+
"""
|
|
154
|
+
construct self.body (json) from the class attributes
|
|
155
|
+
:return: String, TM1 JSON representation of a chore
|
|
156
|
+
"""
|
|
157
|
+
body_as_dict = collections.OrderedDict()
|
|
158
|
+
body_as_dict["Name"] = self._name
|
|
159
|
+
body_as_dict["StartTime"] = self._start_time.start_time_string
|
|
160
|
+
body_as_dict["DSTSensitive"] = self._dst_sensitivity
|
|
161
|
+
body_as_dict["Active"] = self._active
|
|
162
|
+
body_as_dict["ExecutionMode"] = self._execution_mode
|
|
163
|
+
body_as_dict["Frequency"] = self._frequency.frequency_string
|
|
164
|
+
body_as_dict["Tasks"] = [task.body_as_dict for task in self._tasks]
|
|
165
|
+
return json.dumps(body_as_dict, ensure_ascii=False)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from TM1py.Objects.TM1Object import TM1Object
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChoreFrequency(TM1Object):
|
|
8
|
+
"""Utility class to handle time representation fore Chore Frequency"""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self, days: Union[str, int], hours: Union[str, int], minutes: Union[str, int], seconds: Union[str, int]
|
|
12
|
+
):
|
|
13
|
+
self._days = str(days).zfill(2)
|
|
14
|
+
self._hours = str(hours).zfill(2)
|
|
15
|
+
self._minutes = str(minutes).zfill(2)
|
|
16
|
+
self._seconds = str(seconds).zfill(2)
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def days(self) -> str:
|
|
20
|
+
return self._days
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def hours(self) -> str:
|
|
24
|
+
return self._hours
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def minutes(self) -> str:
|
|
28
|
+
return self._minutes
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def seconds(self) -> str:
|
|
32
|
+
return self._seconds
|
|
33
|
+
|
|
34
|
+
@days.setter
|
|
35
|
+
def days(self, value: Union[str, int]):
|
|
36
|
+
self._days = str(value).zfill(2)
|
|
37
|
+
|
|
38
|
+
@hours.setter
|
|
39
|
+
def hours(self, value: Union[str, int]):
|
|
40
|
+
self._hours = str(value).zfill(2)
|
|
41
|
+
|
|
42
|
+
@minutes.setter
|
|
43
|
+
def minutes(self, value: Union[str, int]):
|
|
44
|
+
self._minutes = str(value).zfill(2)
|
|
45
|
+
|
|
46
|
+
@seconds.setter
|
|
47
|
+
def seconds(self, value: Union[str, int]):
|
|
48
|
+
self._seconds = str(value).zfill(2)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_string(cls, frequency_string: str) -> "ChoreFrequency":
|
|
52
|
+
pos_dt = frequency_string.find("DT", 1)
|
|
53
|
+
pos_h = frequency_string.find("H", pos_dt)
|
|
54
|
+
pos_m = frequency_string.find("M", pos_h)
|
|
55
|
+
pos_s = len(frequency_string) - 1
|
|
56
|
+
return cls(
|
|
57
|
+
days=frequency_string[1:pos_dt],
|
|
58
|
+
hours=frequency_string[pos_dt + 2 : pos_h],
|
|
59
|
+
minutes=frequency_string[pos_h + 1 : pos_m],
|
|
60
|
+
seconds=frequency_string[pos_m + 1 : pos_s],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def frequency_string(self) -> str:
|
|
65
|
+
return "P{}DT{}H{}M{}S".format(self._days, self._hours, self._minutes, self._seconds)
|
|
66
|
+
|
|
67
|
+
def __str__(self) -> str:
|
|
68
|
+
return self.frequency_string
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChoreStartTime:
|
|
7
|
+
"""Utility class to handle time representation for Chore Start Time"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, year: int, month: int, day: int, hour: int, minute: int, second: int, tz: str = None):
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
:param year: year
|
|
13
|
+
:param month: month
|
|
14
|
+
:param day: day
|
|
15
|
+
:param hour: hour or None
|
|
16
|
+
:param minute: minute or None
|
|
17
|
+
:param second: second or None
|
|
18
|
+
"""
|
|
19
|
+
self._datetime = datetime.datetime.combine(datetime.date(year, month, day), datetime.time(hour, minute, second))
|
|
20
|
+
self.tz = tz
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_string(cls, start_time_string: str) -> "ChoreStartTime":
|
|
24
|
+
# extract optional tz info (e.g., +01:00) from string end
|
|
25
|
+
if "+" in start_time_string:
|
|
26
|
+
# case "2020-11-05T08:00:01+01:00",
|
|
27
|
+
tz = "+" + start_time_string.split("+")[1]
|
|
28
|
+
elif start_time_string.count("-") == 3:
|
|
29
|
+
# case: "2020-11-05T08:00:01-01:00",
|
|
30
|
+
tz = "-" + start_time_string.split("-")[-1]
|
|
31
|
+
else:
|
|
32
|
+
tz = None
|
|
33
|
+
|
|
34
|
+
# f to handle strange timestamp 2016-09-25T20:25Z instead of common 2016-09-25T20:25:00Z
|
|
35
|
+
# second is defaulted to 0 if not specified in the chore schedule
|
|
36
|
+
def format_time(value: int) -> str:
|
|
37
|
+
return int(value or 0)
|
|
38
|
+
|
|
39
|
+
return cls(
|
|
40
|
+
year=format_time(start_time_string[0:4]),
|
|
41
|
+
month=format_time(start_time_string[5:7]),
|
|
42
|
+
day=format_time(start_time_string[8:10]),
|
|
43
|
+
hour=format_time(start_time_string[11:13]),
|
|
44
|
+
minute=format_time(start_time_string[14:16]),
|
|
45
|
+
second=format_time(0 if start_time_string[16] != ":" else start_time_string[17:19]),
|
|
46
|
+
tz=tz,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def start_time_string(self) -> str:
|
|
51
|
+
# produce timestamp 2016-09-25T20:25:00Z instead of common 2016-09-25T20:25Z where no seconds are specified
|
|
52
|
+
start_time = self._datetime.strftime("%Y-%m-%dT%H:%M:%S")
|
|
53
|
+
|
|
54
|
+
if self.tz:
|
|
55
|
+
start_time += self.tz
|
|
56
|
+
else:
|
|
57
|
+
start_time += "Z"
|
|
58
|
+
|
|
59
|
+
return start_time
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def datetime(self) -> datetime:
|
|
63
|
+
return self._datetime
|
|
64
|
+
|
|
65
|
+
def __str__(self):
|
|
66
|
+
return self.start_time_string
|
|
67
|
+
|
|
68
|
+
def set_time(
|
|
69
|
+
self,
|
|
70
|
+
year: int = None,
|
|
71
|
+
month: int = None,
|
|
72
|
+
day: int = None,
|
|
73
|
+
hour: int = None,
|
|
74
|
+
minute: int = None,
|
|
75
|
+
second: int = None,
|
|
76
|
+
):
|
|
77
|
+
|
|
78
|
+
_year = year if year is not None else self._datetime.year
|
|
79
|
+
_month = month if month is not None else self._datetime.month
|
|
80
|
+
_day = day if day is not None else self._datetime.day
|
|
81
|
+
_hour = hour if hour is not None else self._datetime.hour
|
|
82
|
+
_minute = minute if minute is not None else self._datetime.minute
|
|
83
|
+
_second = second if second is not None else self._datetime.second
|
|
84
|
+
|
|
85
|
+
self._datetime = self._datetime.replace(
|
|
86
|
+
year=_year, month=_month, day=_day, hour=_hour, minute=_minute, second=_second
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def add(self, days: int = 0, hours: int = 0, minutes: int = 0, seconds: int = 0):
|
|
90
|
+
self._datetime = self._datetime + datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
|
|
91
|
+
|
|
92
|
+
def subtract(self, days: int = 0, hours: int = 0, minutes: int = 0, seconds: int = 0):
|
|
93
|
+
self._datetime = self._datetime - datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import json
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from TM1py.Objects.TM1Object import TM1Object
|
|
8
|
+
from TM1py.Utils import format_url
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ChoreTask(TM1Object):
|
|
12
|
+
"""Abstraction of a Chore Task
|
|
13
|
+
|
|
14
|
+
A Chore task always conistst of
|
|
15
|
+
- The step integer ID: it's order in the execution plan.
|
|
16
|
+
1 to n, where n is the last Process in the Chore
|
|
17
|
+
- The name of the process to execute
|
|
18
|
+
- The parameters for the process
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, step: int, process_name: str, parameters: List[Dict[str, str]]):
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
:param step: step in the execution order of the Chores' processes. 1 to n, where n the number of processes
|
|
26
|
+
:param process_name: name of the process
|
|
27
|
+
:param parameters: list of dictionaries with 'Name' and 'Value' property:
|
|
28
|
+
[{
|
|
29
|
+
'Name': '..',
|
|
30
|
+
'Value': '..'
|
|
31
|
+
},
|
|
32
|
+
...
|
|
33
|
+
]
|
|
34
|
+
"""
|
|
35
|
+
self._step = step
|
|
36
|
+
self._process_name = process_name
|
|
37
|
+
self._parameters = parameters
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_dict(cls, chore_task_as_dict: Dict, step: int = None):
|
|
41
|
+
if "Process" in chore_task_as_dict:
|
|
42
|
+
process_name = chore_task_as_dict["Process"]["Name"]
|
|
43
|
+
else:
|
|
44
|
+
# Extract "ProcessName" from "Processes('ProcessName')"
|
|
45
|
+
process_name = chore_task_as_dict["Process@odata.bind"][11:-2]
|
|
46
|
+
|
|
47
|
+
return cls(
|
|
48
|
+
step=step if step is not None else int(chore_task_as_dict["Step"]),
|
|
49
|
+
process_name=process_name,
|
|
50
|
+
parameters=[{"Name": p["Name"], "Value": p["Value"]} for p in chore_task_as_dict["Parameters"]],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def body_as_dict(self) -> Dict:
|
|
55
|
+
body_as_dict = collections.OrderedDict()
|
|
56
|
+
body_as_dict["Process@odata.bind"] = format_url("Processes('{}')", self._process_name)
|
|
57
|
+
body_as_dict["Parameters"] = self._parameters
|
|
58
|
+
return body_as_dict
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def step(self) -> int:
|
|
62
|
+
return self._step
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def process_name(self) -> str:
|
|
66
|
+
return self._process_name
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def parameters(self) -> List[Dict[str, str]]:
|
|
70
|
+
return self._parameters
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def body(self) -> str:
|
|
74
|
+
return json.dumps(self.body_as_dict, ensure_ascii=False)
|
|
75
|
+
|
|
76
|
+
def __eq__(self, other: "ChoreTask") -> bool:
|
|
77
|
+
return self.process_name == other.process_name and self.parameters == other.parameters
|
|
78
|
+
|
|
79
|
+
def __ne__(self, other: "ChoreTask") -> bool:
|
|
80
|
+
return self.process_name != other.process_name or self._parameters != other.parameters
|
TM1py/Objects/Cube.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import json
|
|
5
|
+
from typing import Dict, Iterable, List, Optional, Union
|
|
6
|
+
|
|
7
|
+
from TM1py.Objects.Rules import Rules
|
|
8
|
+
from TM1py.Objects.TM1Object import TM1Object
|
|
9
|
+
from TM1py.Utils import format_url
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Cube(TM1Object):
|
|
13
|
+
"""Abstraction of a TM1 Cube"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, name: str, dimensions: Iterable[str], rules: Optional[Union[str, Rules]] = None):
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
:param name: name of the Cube
|
|
19
|
+
:param dimensions: list of (existing) dimension names
|
|
20
|
+
:param rules: instance of TM1py.Objects.Rules
|
|
21
|
+
"""
|
|
22
|
+
self._name = name
|
|
23
|
+
self.dimensions = list(dimensions)
|
|
24
|
+
self.rules = rules
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
return self._name
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def dimensions(self) -> List[str]:
|
|
32
|
+
return self._dimensions
|
|
33
|
+
|
|
34
|
+
@dimensions.setter
|
|
35
|
+
def dimensions(self, value: List[str]):
|
|
36
|
+
self._dimensions = value
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def has_rules(self) -> bool:
|
|
40
|
+
if self._rules:
|
|
41
|
+
return True
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def rules(self) -> Rules:
|
|
46
|
+
return self._rules
|
|
47
|
+
|
|
48
|
+
@rules.setter
|
|
49
|
+
def rules(self, value: Union[str, Rules]):
|
|
50
|
+
if value is None:
|
|
51
|
+
self._rules = None
|
|
52
|
+
elif isinstance(value, str):
|
|
53
|
+
self._rules = Rules(rules=value)
|
|
54
|
+
elif isinstance(value, Rules):
|
|
55
|
+
self._rules = value
|
|
56
|
+
else:
|
|
57
|
+
raise ValueError("value must None or of type str or Rules")
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def skipcheck(self) -> bool:
|
|
61
|
+
if self.has_rules:
|
|
62
|
+
return self.rules.skipcheck
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def undefvals(self) -> bool:
|
|
67
|
+
if self.has_rules:
|
|
68
|
+
return self.rules.undefvals
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def feedstrings(self) -> bool:
|
|
73
|
+
if self.has_rules:
|
|
74
|
+
return self.rules.feedstrings
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_json(cls, cube_as_json: str) -> "Cube":
|
|
79
|
+
"""Alternative constructor
|
|
80
|
+
|
|
81
|
+
:param cube_as_json: user as JSON string
|
|
82
|
+
:return: cube, an instance of this class
|
|
83
|
+
"""
|
|
84
|
+
cube_as_dict = json.loads(cube_as_json)
|
|
85
|
+
return cls.from_dict(cube_as_dict)
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_dict(cls, cube_as_dict: Dict) -> "Cube":
|
|
89
|
+
"""Alternative constructor
|
|
90
|
+
|
|
91
|
+
:param cube_as_dict: user as dict
|
|
92
|
+
:return: user, an instance of this class
|
|
93
|
+
"""
|
|
94
|
+
return cls(
|
|
95
|
+
name=cube_as_dict["Name"],
|
|
96
|
+
dimensions=[dimension["Name"] for dimension in cube_as_dict["Dimensions"]],
|
|
97
|
+
rules=Rules(cube_as_dict["Rules"]) if cube_as_dict["Rules"] else None,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def body(self) -> str:
|
|
102
|
+
return self._construct_body()
|
|
103
|
+
|
|
104
|
+
def _construct_body(self) -> str:
|
|
105
|
+
"""
|
|
106
|
+
construct body (json) from the class attributes
|
|
107
|
+
:return: String, TM1 JSON representation of a cube
|
|
108
|
+
"""
|
|
109
|
+
body_as_dict = collections.OrderedDict()
|
|
110
|
+
body_as_dict["Name"] = self.name
|
|
111
|
+
body_as_dict["Dimensions@odata.bind"] = [
|
|
112
|
+
format_url("Dimensions('{}')", dimension) for dimension in self.dimensions
|
|
113
|
+
]
|
|
114
|
+
if self.has_rules:
|
|
115
|
+
body_as_dict["Rules"] = str(self.rules)
|
|
116
|
+
return json.dumps(body_as_dict, ensure_ascii=False)
|