diff --git a/TM1py/Services/MetricService.py b/TM1py/Services/MetricService.py new file mode 100644 index 00000000..86e9f02e --- /dev/null +++ b/TM1py/Services/MetricService.py @@ -0,0 +1,71 @@ +try: + import pandas as pd + _has_pandas = True +except ImportError: + _has_pandas = False + +from datetime import datetime + +from TM1py.Services.ObjectService import ObjectService +from TM1py.Services.RestService import RestService +from TM1py.Utils.Utils import format_url, require_pandas, require_version, datetime_to_iso + + +class MetricService(ObjectService): + """Service to handle TM1 cube metrics in v12""" + + def __init__(self, rest: RestService): + super().__init__(rest) + + @require_version(version="12.0.0") + def get( + self, + cube_name: str = None, + metrics: list[str] = None, + timestamp: datetime = None, + **kwargs): + + filters = [] + + if cube_name: + filters.append(f"CubeName eq '{cube_name}'") + + if metrics: + metric_filter = " or ".join( + [f"Name eq '{m}'" for m in metrics] + ) + filters.append(f"({metric_filter})") + + if timestamp: + filters.append( + f"Timestamp gt {datetime_to_iso(timestamp)}" + ) + + filter_part = "" + + if filters: + filter_string = " and ".join(f"({f})" for f in filters) + filter_part = f"?$filter={filter_string}" + + url = format_url(f"/Metrics(){filter_part}") + response = self._rest.GET(url, **kwargs) + + return response.json().get("value", []) + + @require_pandas + @require_version(version="12.0.0") + def get_as_dataframe( + self, + cube_name: str = None, + metrics: list[str] = None, + timestamp: datetime = None, + **kwargs): + + data = self.get( + cube_name=cube_name, + metrics=metrics, + timestamp=timestamp, + **kwargs + ) + + return pd.DataFrame.from_records(data) \ No newline at end of file diff --git a/TM1py/Services/TM1Service.py b/TM1py/Services/TM1Service.py index 6df05634..7b5bad84 100644 --- a/TM1py/Services/TM1Service.py +++ b/TM1py/Services/TM1Service.py @@ -24,6 +24,7 @@ from TM1py.Services.JobService import JobService from TM1py.Services.LoggerService import LoggerService from TM1py.Services.MessageLogService import MessageLogService +from TM1py.Services.MetricService import MetricService from TM1py.Services.MonitoringService import MonitoringService from TM1py.Services.PowerBiService import PowerBiService from TM1py.Services.ServerService import ServerService @@ -119,6 +120,7 @@ def __init__(self, **kwargs): self.message_logs = MessageLogService(self._tm1_rest) self.configuration = ConfigurationService(self._tm1_rest) self.audit_logs = AuditLogService(self._tm1_rest) + self.metrics = MetricService(self._tm1_rest) # higher level modules self.power_bi = PowerBiService(self._tm1_rest) diff --git a/TM1py/Services/__init__.py b/TM1py/Services/__init__.py index 307a45b1..81163cd7 100644 --- a/TM1py/Services/__init__.py +++ b/TM1py/Services/__init__.py @@ -27,6 +27,7 @@ from TM1py.Services.ConfigurationService import ConfigurationService from TM1py.Services.AuditLogService import AuditLogService from TM1py.Services.LoggerService import LoggerService +from TM1py.Services.MetricService import MetricService from TM1py.Services.ServerService import ServerService from TM1py.Services.MonitoringService import MonitoringService diff --git a/TM1py/Utils/Utils.py b/TM1py/Utils/Utils.py index d62f1229..555f310a 100644 --- a/TM1py/Utils/Utils.py +++ b/TM1py/Utils/Utils.py @@ -7,6 +7,7 @@ import re import ssl import urllib.parse as urlparse +from datetime import datetime from enum import Enum, unique from io import StringIO from typing import ( @@ -1818,6 +1819,18 @@ def reorder_with_priority( return result +def datetime_to_iso(dt: datetime) -> str: + """ + Convert datetime → ISO 8601 UTC format used by TM1 Metrics API + """ + + if not isinstance(dt, datetime): + raise TypeError( + f"Expected datetime, got {type(dt)}" + ) + + return dt.strftime("%Y-%m-%dT%H:%M:%S.000Z") + class HTTPAdapterWithSocketOptions(HTTPAdapter): def __init__(self, *args, **kwargs): self.socket_options = kwargs.pop("socket_options", None) diff --git a/TM1py/__init__.py b/TM1py/__init__.py index f5ad044c..2d36d385 100644 --- a/TM1py/__init__.py +++ b/TM1py/__init__.py @@ -57,6 +57,7 @@ from TM1py.Services.JobService import JobService from TM1py.Services.ManageService import ManageService from TM1py.Services.MessageLogService import MessageLogService +from TM1py.Services.MetricService import MetricService from TM1py.Services.MonitoringService import MonitoringService from TM1py.Services.ObjectService import ObjectService from TM1py.Services.PowerBiService import PowerBiService