Skip to content

Commit

Permalink
Tm1 v12 support service url (#2)
Browse files Browse the repository at this point in the history
* Moved api path to base url to continue to support v11

* Refactored all services to continue v11 support and expand support to v12 using the base_url property.

* Update config.ini

Fixed test fixture config

* Corrected test_get_threads to include api/v1 in the path

* Support for v12 Auth with backwards compatibility.

* Updates to cell service to support v12 using blobs. Updates include tests, new utils for skipping invalid tests, and updates to the FileService.py

* Updates to services and tests to support v12

* Updates to services and tests to support v12

* Small change to CubeService_test.py to use a real rule statement in the get all names test. Test used to use comments which seem unreliable across versions.

* Updates to the ProcessService and HierarchyService and related tests.

* Removed test that requires control process, not all models have control processes.

* New functions request the server metadata and update system logger levels

* Updates to process tests

* Remove ODATA tags that are not needed

* Updates to the application service to support v12. Updated tests to resolve dependency issues with chores and processes.

* New manage service that allows you to control the PAe control plane

* Remove support for Impersonation and ErrorLogs entity

* Manage Service Updates

* Manage Service Updates and small fixes to security tests

* Simplification of the auth and root creation methods
  • Loading branch information
rclapp authored and MariusWirtz committed Jan 11, 2024
1 parent c11f9e3 commit 68e6f82
Show file tree
Hide file tree
Showing 41 changed files with 885 additions and 417 deletions.
9 changes: 9 additions & 0 deletions TM1py/Exceptions/Exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ def __str__(self):
return f"Function '{self.function}' requires TM1 server version >= '{self.required_version}'"


class TM1pyVersionDeprecationException(Exception):
def __init__(self, function: str, deprecated_in_version):
self.function = function
self.deprecated_in_version = deprecated_in_version

def __str__(self):
return f"Function '{self.function}' has been deprecated in TM1 server version >= '{self.deprecated_in_version}'"


class TM1pyNotAdminException(Exception):
def __init__(self, function: str):
self.function = function
Expand Down
2 changes: 1 addition & 1 deletion TM1py/Objects/Chore.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self, name: str, start_time: ChoreStartTime, dst_sensitivity: bool,
def from_json(cls, chore_as_json: str) -> 'Chore':
""" Alternative constructor
:param chore_as_json: string, JSON. Response of /api/v1/Chores('x')/Tasks?$expand=*
:param chore_as_json: string, JSON. Response of /Chores('x')/Tasks?$expand=*
:return: Chore, an instance of this class
"""
chore_as_dict = json.loads(chore_as_json)
Expand Down
2 changes: 1 addition & 1 deletion TM1py/Objects/GitProject.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def remove_deployment(self, deployment_name: str):
@classmethod
def from_json(cls, tm1project_as_json: str) -> 'TM1Project':
"""
:param tm1project_as_json: response of /api/v1/!tm1project
:param tm1project_as_json: response of /!tm1project
:return: an instance of this class
"""
tm1project_as_dict = json.loads(tm1project_as_json)
Expand Down
2 changes: 1 addition & 1 deletion TM1py/Objects/Process.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def __init__(self,
@classmethod
def from_json(cls, process_as_json: str) -> 'Process':
"""
:param process_as_json: response of /api/v1/Processes('x')?$expand=*
:param process_as_json: response of /Processes('x')?$expand=*
:return: an instance of this class
"""
process_as_dict = json.loads(process_as_json)
Expand Down
2 changes: 1 addition & 1 deletion TM1py/Objects/Server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Server:
""" Abstraction of the TM1 Server
:Notes:
contains the information you get from http://localhost:5895/api/v1/Servers
contains the information you get from http://localhost:5895/Servers
no methods so far
"""
def __init__(self, server_as_dict: Dict):
Expand Down
2 changes: 1 addition & 1 deletion TM1py/Objects/User.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def from_dict(cls, user_as_dict: Dict) -> 'User':
"""
return cls(name=user_as_dict['Name'],
friendly_name=user_as_dict['FriendlyName'],
enabled=user_as_dict["Enabled"],
enabled=user_as_dict.get('Enabled', None),
user_type=user_as_dict["Type"],
groups=[group["Name"] for group in user_as_dict['Groups']])

Expand Down
12 changes: 6 additions & 6 deletions TM1py/Services/AnnotationService.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def get_all(self, cube_name: str, **kwargs) -> List[Annotation]:
:param cube_name:
"""
url = format_url("/api/v1/Cubes('{}')/Annotations?$expand=DimensionalContext($select=Name)", cube_name)
url = format_url("/Cubes('{}')/Annotations?$expand=DimensionalContext($select=Name)", cube_name)
response = self._rest.GET(url, **kwargs)

annotations_as_dict = response.json()['value']
Expand All @@ -36,7 +36,7 @@ def create(self, annotation: Annotation, **kwargs) -> Response:
:param annotation: instance of TM1py.Annotation
"""
url = "/api/v1/Annotations"
url = "/Annotations"

from TM1py import CubeService
cube_dimensions = CubeService(self._rest).get_dimension_names(
Expand Down Expand Up @@ -65,7 +65,7 @@ def create_many(self, annotations: Iterable[Annotation], **kwargs) -> Response:
cube_dimensions[annotation.object_name] = dimension_names
payload.append(annotation.construct_body_for_post(dimension_names))

response = self._rest.POST("/api/v1/Annotations", json.dumps(payload), **kwargs)
response = self._rest.POST("/Annotations", json.dumps(payload), **kwargs)

return response

Expand All @@ -74,7 +74,7 @@ def get(self, annotation_id: str, **kwargs) -> Annotation:
:param annotation_id: String, the id of the annotation
"""
request = format_url("/api/v1/Annotations('{}')?$expand=DimensionalContext($select=Name)", annotation_id)
request = format_url("/Annotations('{}')?$expand=DimensionalContext($select=Name)", annotation_id)
response = self._rest.GET(url=request, **kwargs)
return Annotation.from_json(response.text)

Expand All @@ -84,13 +84,13 @@ def update(self, annotation: Annotation, **kwargs) -> Response:
:param annotation: instance of TM1py.Annotation
"""
url = format_url("/api/v1/Annotations('{}')", annotation.id)
url = format_url("/Annotations('{}')", annotation.id)
return self._rest.PATCH(url=url, data=annotation.body, **kwargs)

def delete(self, annotation_id: str, **kwargs) -> Response:
""" delete Annotation
:param annotation_id: string, the id of the annotation
"""
url = format_url("/api/v1/Annotations('{}')", annotation_id)
url = format_url("/Annotations('{}')", annotation_id)
return self._rest.DELETE(url=url, **kwargs)
50 changes: 33 additions & 17 deletions TM1py/Services/ApplicationService.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
Application
from TM1py.Services import RestService
from TM1py.Services.ObjectService import ObjectService
from TM1py.Utils import format_url
from TM1py.Utils import format_url, verify_version


class ApplicationService(ObjectService):
Expand All @@ -24,6 +24,21 @@ def __init__(self, tm1_rest: RestService):
super().__init__(tm1_rest)
self._rest = tm1_rest

def get_all_public_root_names(self, **kwargs):

url = "/Contents('Applications')/Contents"
response = self._rest.GET(url, **kwargs)
applications = list(application['Name'] for application in response.json()['value'])
return applications

def get_all_private_root_names(self, **kwargs):

url = "/Contents('Applications')/PrivateContents"
response = self._rest.GET(url, **kwargs)
applications = list(application['Name'] for application in response.json()['value'])
return applications


def get(self, path: str, application_type: Union[str, ApplicationTypes], name: str, private: bool = False,
**kwargs) -> Application:
""" Retrieve Planning Analytics Application
Expand All @@ -41,7 +56,7 @@ def get(self, path: str, application_type: Union[str, ApplicationTypes], name: s
if application_type == ApplicationTypes.DOCUMENT:
return self.get_document(path=path, name=name, private=private, **kwargs)

if not application_type == ApplicationTypes.FOLDER:
if not application_type == ApplicationTypes.FOLDER and not verify_version(required_version='12', version=self.version):
name += application_type.suffix

contents = 'PrivateContents' if private else 'Contents'
Expand All @@ -50,7 +65,7 @@ def get(self, path: str, application_type: Union[str, ApplicationTypes], name: s
mid = "".join([format_url("/Contents('{}')", element) for element in path.split('/')])

base_url = format_url(
"/api/v1/Contents('Applications')" + mid + "/" + contents + "('{application_name}')",
"/Contents('Applications')" + mid + "/" + contents + "('{application_name}')",
application_name=name)

if application_type == ApplicationTypes.CUBE:
Expand Down Expand Up @@ -112,19 +127,19 @@ def get_document(self, path: str, name: str, private: bool = False, **kwargs) ->
:param private: boolean
:return: Return DocumentApplication
"""
if not name.endswith(ApplicationTypes.DOCUMENT.suffix):
if not name.endswith(ApplicationTypes.DOCUMENT.suffix) and not verify_version(required_version='12', version=self.version):
name += ApplicationTypes.DOCUMENT.suffix

contents = 'PrivateContents' if private else 'Contents'
mid = "".join([format_url("/Contents('{}')", element) for element in path.split('/')])
url = format_url(
"/api/v1/Contents('Applications')" + mid + "/" + contents + "('{name}')/Document/Content",
"/Contents('Applications')" + mid + "/" + contents + "('{name}')/Document/Content",
name=name)

content = self._rest.GET(url, **kwargs).content

url = format_url(
"/api/v1/Contents('Applications')" + mid + "/" + contents + "('{name}')/Document",
"/Contents('Applications')" + mid + "/" + contents + "('{name}')/Document",
name=name)
document_fields = self._rest.GET(url, **kwargs).json()

Expand All @@ -150,7 +165,7 @@ def delete(self, path: str, application_type: Union[str, ApplicationTypes], appl
# raise ValueError if not a valid ApplicationType
application_type = ApplicationTypes(application_type)

if not application_type == ApplicationTypes.FOLDER:
if not application_type == ApplicationTypes.FOLDER and not verify_version(required_version='12', version=self.version):
application_name += application_type.suffix

contents = 'PrivateContents' if private else 'Contents'
Expand All @@ -159,7 +174,7 @@ def delete(self, path: str, application_type: Union[str, ApplicationTypes], appl
mid = "".join([format_url("/Contents('{}')", element) for element in path.split('/')])

url = format_url(
"/api/v1/Contents('Applications')" + mid + "/" + contents + "('{application_name}')",
"/Contents('Applications')" + mid + "/" + contents + "('{application_name}')",
application_name=application_name)
return self._rest.DELETE(url, **kwargs)

Expand All @@ -168,7 +183,7 @@ def rename(self, path: str, application_type: Union[str, ApplicationTypes], appl
# raise ValueError if not a valid ApplicationType
application_type = ApplicationTypes(application_type)

if not application_type == ApplicationTypes.FOLDER:
if not application_type == ApplicationTypes.FOLDER and not verify_version(required_version='12', version=self.version):
application_name += application_type.suffix

contents = 'PrivateContents' if private else 'Contents'
Expand All @@ -177,7 +192,7 @@ def rename(self, path: str, application_type: Union[str, ApplicationTypes], appl
mid = "".join([format_url("/Contents('{}')", element) for element in path.split('/')])

url = format_url(
"/api/v1/Contents('Applications')" + mid + "/" + contents + "('{application_name}')/tm1.Move",
"/Contents('Applications')" + mid + "/" + contents + "('{application_name}')/tm1.Move",
application_name=application_name)
data = {"Name": new_application_name}

Expand All @@ -196,13 +211,14 @@ def create(self, application: Union[Application, DocumentApplication], private:
mid = ""
if application.path.strip() != '':
mid = "".join([format_url("/Contents('{}')", element) for element in application.path.split('/')])
url = "/api/v1/Contents('Applications')" + mid + "/" + contents
url = "/Contents('Applications')" + mid + "/" + contents
response = self._rest.POST(url, application.body, **kwargs)

if application.application_type == ApplicationTypes.DOCUMENT:
url = format_url(
"/api/v1/Contents('Applications')" + mid + "/" + contents + "('{name}.blob')/Document/Content",
name=application.name)
"/Contents('Applications')" + mid + "/" + contents + "('{name}{suffix}')/Document/Content",
name=application.name,
suffix='.blob' if not verify_version(required_version='12', version=self.version) else '')
response = self._rest.PUT(url, application.content, headers=self.BINARY_HTTP_HEADER, **kwargs)

return response
Expand All @@ -223,11 +239,11 @@ def update(self, application: Union[Application, DocumentApplication], private:

if application.application_type == ApplicationTypes.DOCUMENT:
url = format_url(
"/api/v1/Contents('Applications')" + mid + "/" + contents + "('{name}.blob')/Document/Content",
"/Contents('Applications')" + mid + "/" + contents + "('{name}.blob')/Document/Content",
name=application.name)
response = self._rest.PATCH(url, application.content, headers=self.BINARY_HTTP_HEADER, **kwargs)
else:
url = "/api/v1/Contents('Applications')" + mid + "/" + contents
url = "/Contents('Applications')" + mid + "/" + contents
response = self._rest.POST(url, application.body, **kwargs)

return response
Expand Down Expand Up @@ -265,7 +281,7 @@ def exists(self, path: str, application_type: Union[str, ApplicationTypes], name
# raise ValueError if not a valid ApplicationType
application_type = ApplicationTypes(application_type)

if not application_type == ApplicationTypes.FOLDER:
if not application_type == ApplicationTypes.FOLDER and not verify_version(required_version='12', version=self.version):
name += application_type.suffix

contents = 'PrivateContents' if private else 'Contents'
Expand All @@ -274,7 +290,7 @@ def exists(self, path: str, application_type: Union[str, ApplicationTypes], name
mid = "".join(["/Contents('{}')".format(element) for element in path.split('/')])

url = format_url(
"/api/v1/Contents('Applications')" + mid + "/" + contents + "('{application_name}')",
"/Contents('Applications')" + mid + "/" + contents + "('{application_name}')",
application_name=name)
return self._exists(url, **kwargs)

Expand Down
Loading

0 comments on commit 68e6f82

Please sign in to comment.