cvec 0.1.0__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.
cvec-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CVector Energy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cvec-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.3
2
+ Name: cvec
3
+ Version: 0.1.0
4
+ Summary: SDK for CVector Energy
5
+ Author: CVector
6
+ Author-email: support@cvector.energy
7
+ Requires-Python: >=3.10
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: pandas (>=2.2.3,<3.0.0)
14
+ Requires-Dist: psycopg (>=3.1.0,<4.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ # CVec Client Library
18
+
19
+ The "cvec" package is the Python SDK for CVector Energy.
20
+
21
+ # Getting Started
22
+
23
+ ## Installation
24
+
25
+ Assuming that you have a supported version of Python installed, you can first create a venv with:
26
+
27
+ ```
28
+ python -m venv .venv
29
+ ```
30
+
31
+ Then, activate the venv:
32
+
33
+ ```
34
+ . .venv/bin/activate
35
+ ```
36
+
37
+ Then, you can install cvec from PyPI with:
38
+
39
+ ```
40
+ pip install cvec
41
+ ```
42
+
43
+ ## Using cvec
44
+
45
+ Import the cvec package. We will also use the datetime module.
46
+
47
+ ```
48
+ import cvec
49
+ from datetime import datetime
50
+ ```
51
+
52
+ Construct the CVec client. The host, tenant, and api_key can be given through parameters to the constructor or from the environment variables CVEC_HOST, CVEC_TENANT, and CVEC_API_KEY:
53
+
54
+ ```
55
+ cvec = cvec.CVec()
56
+ ```
57
+
58
+ ### Spans
59
+
60
+ A span is a period of interest, such as an experiment, a baseline recording session, or an alarm. The initial state of a Span is implicitly defined by a period where a given metric has a constant value.
61
+
62
+ The newest span for a metric does not have an end time, since it has not ended yet (or has not ended by the finish of the queried period).
63
+
64
+ To get the spans on `my_tag_name` since 2025-05-14 10am, run:
65
+
66
+ ```
67
+ for span in cvec.get_spans("mygroup/myedge/mode", start_at=datetime(2025, 5, 14, 10, 0, 0)):
68
+ print("%s\t%s" % (span.value, span.raw_start_at))
69
+ ```
70
+
71
+ The output will be like:
72
+
73
+ ```
74
+ offline 2025-05-19 16:28:02.130000+00:00
75
+ starting 2025-05-19 16:28:01.107000+00:00
76
+ running 2025-05-19 15:29:28.795000+00:00
77
+ stopping 2025-05-19 15:29:27.788000+00:00
78
+ offline 2025-05-19 14:14:43.752000+00:00
79
+ ```
80
+
81
+ ### Metrics
82
+
83
+ A metric is a named set of time-series data points pertaining to a particular resource (for example, the value reported by a sensor). Metrics can have numeric or string values. Boolean values are mapped to 0 and 1. The get_metrics function returns a list of metric metadata.
84
+
85
+ To get all of the metrics that changed value at 10am on 2025-05-14, run:
86
+
87
+ ```
88
+ for item in cvec.get_metrics(start_at=datetime(2025, 5, 14, 10, 0, 0), end_at=datetime(2025, 5, 14, 11, 0, 0)):
89
+ print(item.name)
90
+ ```
91
+
92
+ Example output:
93
+
94
+ ```
95
+ mygroup/myedge/compressor01/status
96
+ mygroup/myedge/compressor01/interlocks/emergency_stop
97
+ mygroup/myedge/compressor01/stage1/pressure_out/psig
98
+ mygroup/myedge/compressor01/stage1/temp_out/c
99
+ mygroup/myedge/compressor01/stage2/pressure_out/psig
100
+ mygroup/myedge/compressor01/stage2/temp_out/c
101
+ mygroup/myedge/compressor01/motor/current/a
102
+ mygroup/myedge/compressor01/motor/power_kw
103
+ ```
104
+
105
+ ### Metric Data
106
+
107
+ The main content for a metric is a set of points where the metric value changed. These are returned as a Pandas Dataframe with columns for name, time, value_double, value_string.
108
+
109
+ To get all of the value changes for all metrics at 10am on 2025-05-14, run:
110
+
111
+ ```
112
+ cvec.get_metric_data(start_at=datetime(2025, 5, 14, 10, 0, 0), end_at=datetime(2025, 5, 14, 11, 0, 0))
113
+ ```
114
+
115
+ Example output:
116
+
117
+ ```
118
+ name time value_double value_string
119
+ 0 mygroup/myedge/mode 2025-05-14 10:10:41.949000+00:00 24.900000 starting
120
+ 1 mygroup/myedge/compressor01/interlocks/emergency_stop 2025-05-14 10:27:24.899000+00:00 0.0000000 None
121
+ 2 mygroup/myedge/compressor01/stage1/pressure_out/psig 2025-05-14 10:43:38.282000+00:00 123.50000 None
122
+ 3 mygroup/myedge/compressor01/stage1/temp_out/c 2025-05-14 10:10:41.948000+00:00 24.900000 None
123
+ 4 mygroup/myedge/compressor01/motor/current/a 2025-05-14 10:27:24.897000+00:00 12.000000 None
124
+ ... ... ... ... ...
125
+ 46253 mygroup/myedge/compressor01/stage1/temp_out/c 2025-05-14 10:59:55.725000+00:00 25.300000 None
126
+ 46254 mygroup/myedge/compressor01/stage2/pressure_out/psig 2025-05-14 10:59:56.736000+00:00 250.00000 None
127
+ 46255 mygroup/myedge/compressor01/stage2/temp_out/c 2025-05-14 10:59:57.746000+00:00 12.700000 None
128
+ 46256 mygroup/myedge/compressor01/motor/current/a 2025-05-14 10:59:58.752000+00:00 11.300000 None
129
+ 46257 mygroup/myedge/compressor01/motor/power_kw 2025-05-14 10:59:59.760000+00:00 523.40000 None
130
+
131
+ [46257 rows x 4 columns]
132
+ ```
133
+
134
+ # CVec Class
135
+
136
+ The SDK provides an API client class named `CVec` with the following functions.
137
+
138
+ ## `__init__(?host, ?tenant, ?api_key, ?default_start_at, ?default_end_at)`
139
+
140
+ Setup the SDK with the given host and API Key. The host and API key are loaded from environment variables CVEC_HOST, CVEC_TENANT, CVEC_API_KEY, if they are not given as arguments to the constructor. The `default_start_at` and `default_end_at` can provide a default query time interval for API methods.
141
+
142
+ ## `get_spans(name, ?start_at, ?end_at, ?limit)`
143
+
144
+ Return time spans for a metric. Spans are generated from value changes that occur after `start_at` (if specified) and before `end_at` (if specified).
145
+ If `start_at` is `None` (e.g., not provided as an argument and no class default `default_start_at` is set), the query for value changes is unbounded at the start. Similarly, if `end_at` is `None`, the query is unbounded at the end.
146
+
147
+ Each `Span` object in the returned list represents a period where the metric's value is constant and has the following attributes:
148
+ - `value`: The metric's value during the span.
149
+ - `name`: The name of the metric.
150
+ - `raw_start_at`: The timestamp of the value change that initiated this span's value. This will be greater than or equal to the query's `start_at` if one was specified.
151
+ - `raw_end_at`: The timestamp marking the end of this span's constant value. For the newest span, the value is `None`. For other spans, it's the raw_start_at of the immediately newer data point, which is next span in the list.
152
+ - `id`: Currently `None`. In a future version of the SDK, this will be the span's unique identifier.
153
+ - `metadata`: Currently `None`. In a future version, this can be used to store annotations or other metadata related to the span.
154
+
155
+ Returns a list of `Span` objects, sorted in descending chronological order (newest span first).
156
+ If no relevant value changes are found, an empty list is returned.
157
+
158
+ ## `get_metric_data(?names, ?start_at, ?end_at)`
159
+
160
+ Return all data-points within a given [`start_at`, `end_at`) interval, optionally selecting a given list of metric names. The return value is a Pandas DataFrame with four columns: name, time, value_double, value_string. One row is returned for each metric value transition.
161
+
162
+ ## `get_metrics(?start_at, ?end_at)`
163
+
164
+ Return a list of metrics that had at least one transition in the given [`start_at`, `end_at`) interval. All metrics are returned if no `start_at` and `end_at` are given.
165
+
cvec-0.1.0/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # CVec Client Library
2
+
3
+ The "cvec" package is the Python SDK for CVector Energy.
4
+
5
+ # Getting Started
6
+
7
+ ## Installation
8
+
9
+ Assuming that you have a supported version of Python installed, you can first create a venv with:
10
+
11
+ ```
12
+ python -m venv .venv
13
+ ```
14
+
15
+ Then, activate the venv:
16
+
17
+ ```
18
+ . .venv/bin/activate
19
+ ```
20
+
21
+ Then, you can install cvec from PyPI with:
22
+
23
+ ```
24
+ pip install cvec
25
+ ```
26
+
27
+ ## Using cvec
28
+
29
+ Import the cvec package. We will also use the datetime module.
30
+
31
+ ```
32
+ import cvec
33
+ from datetime import datetime
34
+ ```
35
+
36
+ Construct the CVec client. The host, tenant, and api_key can be given through parameters to the constructor or from the environment variables CVEC_HOST, CVEC_TENANT, and CVEC_API_KEY:
37
+
38
+ ```
39
+ cvec = cvec.CVec()
40
+ ```
41
+
42
+ ### Spans
43
+
44
+ A span is a period of interest, such as an experiment, a baseline recording session, or an alarm. The initial state of a Span is implicitly defined by a period where a given metric has a constant value.
45
+
46
+ The newest span for a metric does not have an end time, since it has not ended yet (or has not ended by the finish of the queried period).
47
+
48
+ To get the spans on `my_tag_name` since 2025-05-14 10am, run:
49
+
50
+ ```
51
+ for span in cvec.get_spans("mygroup/myedge/mode", start_at=datetime(2025, 5, 14, 10, 0, 0)):
52
+ print("%s\t%s" % (span.value, span.raw_start_at))
53
+ ```
54
+
55
+ The output will be like:
56
+
57
+ ```
58
+ offline 2025-05-19 16:28:02.130000+00:00
59
+ starting 2025-05-19 16:28:01.107000+00:00
60
+ running 2025-05-19 15:29:28.795000+00:00
61
+ stopping 2025-05-19 15:29:27.788000+00:00
62
+ offline 2025-05-19 14:14:43.752000+00:00
63
+ ```
64
+
65
+ ### Metrics
66
+
67
+ A metric is a named set of time-series data points pertaining to a particular resource (for example, the value reported by a sensor). Metrics can have numeric or string values. Boolean values are mapped to 0 and 1. The get_metrics function returns a list of metric metadata.
68
+
69
+ To get all of the metrics that changed value at 10am on 2025-05-14, run:
70
+
71
+ ```
72
+ for item in cvec.get_metrics(start_at=datetime(2025, 5, 14, 10, 0, 0), end_at=datetime(2025, 5, 14, 11, 0, 0)):
73
+ print(item.name)
74
+ ```
75
+
76
+ Example output:
77
+
78
+ ```
79
+ mygroup/myedge/compressor01/status
80
+ mygroup/myedge/compressor01/interlocks/emergency_stop
81
+ mygroup/myedge/compressor01/stage1/pressure_out/psig
82
+ mygroup/myedge/compressor01/stage1/temp_out/c
83
+ mygroup/myedge/compressor01/stage2/pressure_out/psig
84
+ mygroup/myedge/compressor01/stage2/temp_out/c
85
+ mygroup/myedge/compressor01/motor/current/a
86
+ mygroup/myedge/compressor01/motor/power_kw
87
+ ```
88
+
89
+ ### Metric Data
90
+
91
+ The main content for a metric is a set of points where the metric value changed. These are returned as a Pandas Dataframe with columns for name, time, value_double, value_string.
92
+
93
+ To get all of the value changes for all metrics at 10am on 2025-05-14, run:
94
+
95
+ ```
96
+ cvec.get_metric_data(start_at=datetime(2025, 5, 14, 10, 0, 0), end_at=datetime(2025, 5, 14, 11, 0, 0))
97
+ ```
98
+
99
+ Example output:
100
+
101
+ ```
102
+ name time value_double value_string
103
+ 0 mygroup/myedge/mode 2025-05-14 10:10:41.949000+00:00 24.900000 starting
104
+ 1 mygroup/myedge/compressor01/interlocks/emergency_stop 2025-05-14 10:27:24.899000+00:00 0.0000000 None
105
+ 2 mygroup/myedge/compressor01/stage1/pressure_out/psig 2025-05-14 10:43:38.282000+00:00 123.50000 None
106
+ 3 mygroup/myedge/compressor01/stage1/temp_out/c 2025-05-14 10:10:41.948000+00:00 24.900000 None
107
+ 4 mygroup/myedge/compressor01/motor/current/a 2025-05-14 10:27:24.897000+00:00 12.000000 None
108
+ ... ... ... ... ...
109
+ 46253 mygroup/myedge/compressor01/stage1/temp_out/c 2025-05-14 10:59:55.725000+00:00 25.300000 None
110
+ 46254 mygroup/myedge/compressor01/stage2/pressure_out/psig 2025-05-14 10:59:56.736000+00:00 250.00000 None
111
+ 46255 mygroup/myedge/compressor01/stage2/temp_out/c 2025-05-14 10:59:57.746000+00:00 12.700000 None
112
+ 46256 mygroup/myedge/compressor01/motor/current/a 2025-05-14 10:59:58.752000+00:00 11.300000 None
113
+ 46257 mygroup/myedge/compressor01/motor/power_kw 2025-05-14 10:59:59.760000+00:00 523.40000 None
114
+
115
+ [46257 rows x 4 columns]
116
+ ```
117
+
118
+ # CVec Class
119
+
120
+ The SDK provides an API client class named `CVec` with the following functions.
121
+
122
+ ## `__init__(?host, ?tenant, ?api_key, ?default_start_at, ?default_end_at)`
123
+
124
+ Setup the SDK with the given host and API Key. The host and API key are loaded from environment variables CVEC_HOST, CVEC_TENANT, CVEC_API_KEY, if they are not given as arguments to the constructor. The `default_start_at` and `default_end_at` can provide a default query time interval for API methods.
125
+
126
+ ## `get_spans(name, ?start_at, ?end_at, ?limit)`
127
+
128
+ Return time spans for a metric. Spans are generated from value changes that occur after `start_at` (if specified) and before `end_at` (if specified).
129
+ If `start_at` is `None` (e.g., not provided as an argument and no class default `default_start_at` is set), the query for value changes is unbounded at the start. Similarly, if `end_at` is `None`, the query is unbounded at the end.
130
+
131
+ Each `Span` object in the returned list represents a period where the metric's value is constant and has the following attributes:
132
+ - `value`: The metric's value during the span.
133
+ - `name`: The name of the metric.
134
+ - `raw_start_at`: The timestamp of the value change that initiated this span's value. This will be greater than or equal to the query's `start_at` if one was specified.
135
+ - `raw_end_at`: The timestamp marking the end of this span's constant value. For the newest span, the value is `None`. For other spans, it's the raw_start_at of the immediately newer data point, which is next span in the list.
136
+ - `id`: Currently `None`. In a future version of the SDK, this will be the span's unique identifier.
137
+ - `metadata`: Currently `None`. In a future version, this can be used to store annotations or other metadata related to the span.
138
+
139
+ Returns a list of `Span` objects, sorted in descending chronological order (newest span first).
140
+ If no relevant value changes are found, an empty list is returned.
141
+
142
+ ## `get_metric_data(?names, ?start_at, ?end_at)`
143
+
144
+ Return all data-points within a given [`start_at`, `end_at`) interval, optionally selecting a given list of metric names. The return value is a Pandas DataFrame with four columns: name, time, value_double, value_string. One row is returned for each metric value transition.
145
+
146
+ ## `get_metrics(?start_at, ?end_at)`
147
+
148
+ Return a list of metrics that had at least one transition in the given [`start_at`, `end_at`) interval. All metrics are returned if no `start_at` and `end_at` are given.
@@ -0,0 +1,27 @@
1
+ [project]
2
+ name = "cvec"
3
+ version = "0.1.0"
4
+ description = "SDK for CVector Energy"
5
+ authors = [
6
+ {name = "CVector",email = "support@cvector.energy"}
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "pandas (>=2.2.3,<3.0.0)",
12
+ "psycopg (>=3.1.0,<4.0.0)" # Assuming a recent version of psycopg3
13
+ ]
14
+
15
+ [tool.poetry]
16
+ packages = [{include = "cvec", from = "src"}]
17
+
18
+
19
+ [tool.poetry.group.dev.dependencies]
20
+ pytest = "^8.3.5"
21
+ mypy = "^1.15.0"
22
+ pandas-stubs = "^2.2.3.250308"
23
+ ruff = "^0.11.10"
24
+
25
+ [build-system]
26
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
27
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,5 @@
1
+ from .cvec import CVec
2
+ from .span import Span
3
+ from .metric import Metric
4
+
5
+ __all__ = ["CVec", "Span", "Metric"]
@@ -0,0 +1,242 @@
1
+ import os
2
+ from datetime import datetime
3
+ from typing import Any, List, Optional
4
+
5
+ import pandas as pd
6
+ import psycopg
7
+
8
+ from .span import Span
9
+ from .metric import Metric
10
+
11
+
12
+ class CVec:
13
+ """
14
+ CVec API Client
15
+ """
16
+
17
+ host: Optional[str]
18
+ tenant: Optional[str]
19
+ api_key: Optional[str]
20
+ default_start_at: Optional[datetime]
21
+ default_end_at: Optional[datetime]
22
+
23
+ def __init__(
24
+ self,
25
+ host: Optional[str] = None,
26
+ tenant: Optional[str] = None,
27
+ api_key: Optional[str] = None,
28
+ default_start_at: Optional[datetime] = None,
29
+ default_end_at: Optional[datetime] = None,
30
+ ) -> None:
31
+ """
32
+ Setup the SDK with the given host and API Key.
33
+ The host and API key are loaded from environment variables CVEC_HOST,
34
+ CVEC_TENANT, CVEC_API_KEY, if they are not given as arguments to the constructor.
35
+ The default_start_at and default_end_at can provide a default query time interval for API methods.
36
+ """
37
+ self.host = host or os.environ.get("CVEC_HOST")
38
+ self.tenant = tenant or os.environ.get("CVEC_TENANT")
39
+ self.api_key = api_key or os.environ.get("CVEC_API_KEY")
40
+ self.default_start_at = default_start_at
41
+ self.default_end_at = default_end_at
42
+
43
+ if not self.host:
44
+ raise ValueError(
45
+ "CVEC_HOST must be set either as an argument or environment variable"
46
+ )
47
+ if not self.tenant:
48
+ raise ValueError(
49
+ "CVEC_TENANT must be set either as an argument or environment variable"
50
+ )
51
+ if not self.api_key:
52
+ raise ValueError(
53
+ "CVEC_API_KEY must be set either as an argument or environment variable"
54
+ )
55
+
56
+ def _get_db_connection(self) -> psycopg.Connection:
57
+ """Helper method to establish a database connection."""
58
+ return psycopg.connect(
59
+ user=self.tenant,
60
+ password=self.api_key,
61
+ host=self.host,
62
+ dbname=self.tenant,
63
+ )
64
+
65
+ def get_spans(
66
+ self,
67
+ name: str,
68
+ start_at: Optional[datetime] = None,
69
+ end_at: Optional[datetime] = None,
70
+ limit: Optional[int] = None,
71
+ ) -> List[Span]:
72
+ """
73
+ Return time spans for a metric. Spans are generated from value changes
74
+ that occur after `start_at` (if specified) and before `end_at` (if specified).
75
+ If `start_at` is `None` (e.g., not provided via argument or class default),
76
+ the query is unbounded at the start. If `end_at` is `None`, it's unbounded at the end.
77
+
78
+ Each span represents a period where the metric's value is constant.
79
+ - `value`: The metric's value during the span.
80
+ - `name`: The name of the metric.
81
+ - `raw_start_at`: The timestamp of the value change that initiated this span's value.
82
+ This will be >= `_start_at` if `_start_at` was specified.
83
+ - `raw_end_at`: The timestamp marking the end of this span's constant value.
84
+ For the newest span, the value is `None`. For other spans, it's the raw_start_at of the immediately newer data point, which is next span in the list.
85
+ - `id`: Currently `None`.
86
+ - `metadata`: Currently `None`.
87
+
88
+ Returns a list of Span objects, sorted in descending chronological order (newest span first).
89
+ Each Span object has attributes corresponding to the fields listed above.
90
+ If no relevant value changes are found, an empty list is returned.
91
+ The `limit` parameter restricts the number of spans returned.
92
+ """
93
+ _start_at = start_at or self.default_start_at
94
+ _end_at = end_at or self.default_end_at
95
+
96
+ with self._get_db_connection() as conn:
97
+ with conn.cursor() as cur:
98
+ query_params = {
99
+ "metric": name,
100
+ "start_at": _start_at,
101
+ "end_at": _end_at,
102
+ # Fetch up to 'limit' points. If limit is None, then the `LIMIT NULL` clause
103
+ # has no effect (in PostgreSQL).
104
+ "limit": limit,
105
+ }
106
+
107
+ combined_query = """
108
+ SELECT
109
+ time,
110
+ value_double,
111
+ value_string
112
+ FROM metric_data
113
+ WHERE metric = %(metric)s
114
+ AND (time >= %(start_at)s OR %(start_at)s IS NULL)
115
+ AND (time < %(end_at)s OR %(end_at)s IS NULL)
116
+ ORDER BY time DESC
117
+ LIMIT %(limit)s
118
+ """
119
+ cur.execute(combined_query, query_params)
120
+ db_rows = cur.fetchall()
121
+ spans = []
122
+
123
+ # None indicates that the end time is not known; the span extends beyond
124
+ # the query period.
125
+ raw_end_at = None
126
+ for time, value_double, value_string in db_rows:
127
+ raw_start_at = time
128
+ value = value_double if value_double is not None else value_string
129
+ spans.append(
130
+ Span(
131
+ id=None,
132
+ name=name,
133
+ value=value,
134
+ raw_start_at=raw_start_at,
135
+ raw_end_at=raw_end_at,
136
+ metadata=None,
137
+ )
138
+ )
139
+ raw_end_at = raw_start_at
140
+
141
+ return spans
142
+
143
+ def get_metric_data(
144
+ self,
145
+ names: Optional[List[str]] = None,
146
+ start_at: Optional[datetime] = None,
147
+ end_at: Optional[datetime] = None,
148
+ ) -> pd.DataFrame:
149
+ """
150
+ Return all data-points within a given [start_at, end_at) interval,
151
+ optionally selecting a given list of metric names.
152
+ The return value is a Pandas DataFrame with four columns: name, time, value_double, value_string.
153
+ One row is returned for each metric value transition.
154
+ """
155
+ _start_at = start_at or self.default_start_at
156
+ _end_at = end_at or self.default_end_at
157
+
158
+ params = {
159
+ "start_at": _start_at,
160
+ "end_at": _end_at,
161
+ "tag_names_is_null": names is None,
162
+ # Pass an empty tuple if names is None or empty, otherwise the tuple of names.
163
+ # ANY(%(empty_tuple)s) will correctly result in no matches if names is empty.
164
+ # If names is None, the tag_names_is_null condition handles it.
165
+ "tag_names_list": names if names else [],
166
+ }
167
+
168
+ sql_query = """
169
+ SELECT metric AS name, time, value_double, value_string
170
+ FROM metric_data
171
+ WHERE (time >= %(start_at)s OR %(start_at)s IS NULL)
172
+ AND (time < %(end_at)s OR %(end_at)s IS NULL)
173
+ AND (%(tag_names_is_null)s IS TRUE OR metric = ANY(%(tag_names_list)s))
174
+ ORDER BY name, time ASC
175
+ """
176
+
177
+ with self._get_db_connection() as conn:
178
+ with conn.cursor() as cur:
179
+ cur.execute(sql_query, params)
180
+ rows = cur.fetchall()
181
+
182
+ if not rows:
183
+ return pd.DataFrame(
184
+ columns=["name", "time", "value_double", "value_string"]
185
+ )
186
+
187
+ # Create DataFrame from fetched rows
188
+ df = pd.DataFrame(
189
+ rows, columns=["name", "time", "value_double", "value_string"]
190
+ )
191
+
192
+ # Return the DataFrame with the required columns
193
+ return df[["name", "time", "value_double", "value_string"]]
194
+
195
+ def get_metrics(
196
+ self, start_at: Optional[datetime] = None, end_at: Optional[datetime] = None
197
+ ) -> List[Metric]:
198
+ """
199
+ Return a list of metrics that had at least one transition in the given [start_at, end_at) interval.
200
+ All metrics are returned if no start_at and end_at are given.
201
+ """
202
+ sql_query: str
203
+ params: Optional[dict[str, Any]]
204
+
205
+ if start_at is None and end_at is None:
206
+ # No time interval specified by arguments, return all tags
207
+ sql_query = """
208
+ SELECT id, normalized_name AS name, birth_at, death_at
209
+ FROM tag_names
210
+ ORDER BY name ASC;
211
+ """
212
+ params = None
213
+ else:
214
+ # Time interval specified, find tags with transitions in the interval
215
+ _start_at = start_at or self.default_start_at
216
+ _end_at = end_at or self.default_end_at
217
+
218
+ params = {"start_at_param": _start_at, "end_at_param": _end_at}
219
+ sql_query = f"""
220
+ SELECT DISTINCT metric_id AS id, metric AS name, birth_at, death_at
221
+ FROM {self.tenant}.metric_data
222
+ WHERE (time >= %(start_at_param)s OR %(start_at_param)s IS NULL)
223
+ AND (time < %(end_at_param)s OR %(end_at_param)s IS NULL)
224
+ ORDER BY name ASC;
225
+ """
226
+
227
+ with self._get_db_connection() as conn:
228
+ with conn.cursor() as cur:
229
+ cur.execute(sql_query, params)
230
+ rows = cur.fetchall()
231
+
232
+ # Format rows into list of Metric objects
233
+ metrics_list = [
234
+ Metric(
235
+ id=row[0],
236
+ name=row[1],
237
+ birth_at=row[2],
238
+ death_at=row[3],
239
+ )
240
+ for row in rows
241
+ ]
242
+ return metrics_list
@@ -0,0 +1,31 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+
5
+ class Metric:
6
+ """
7
+ Represents metadata for a metric.
8
+ """
9
+
10
+ id: int
11
+ name: str
12
+ birth_at: Optional[datetime]
13
+ death_at: Optional[datetime]
14
+
15
+ def __init__(
16
+ self,
17
+ id: int,
18
+ name: str,
19
+ birth_at: Optional[datetime],
20
+ death_at: Optional[datetime],
21
+ ):
22
+ self.id = id
23
+ self.name = name
24
+ self.birth_at = birth_at
25
+ self.death_at = death_at
26
+
27
+ def __repr__(self) -> str:
28
+ return (
29
+ f"Metric(id={self.id!r}, name={self.name!r}, "
30
+ f"birth_at={self.birth_at!r}, death_at={self.death_at!r})"
31
+ )
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561
@@ -0,0 +1,42 @@
1
+ from datetime import datetime
2
+ from typing import Any, Optional, Union
3
+
4
+
5
+ class Span:
6
+ """
7
+ Represents a time span where a metric has a constant value.
8
+ """
9
+
10
+ id: Optional[Any]
11
+ name: str
12
+ value: Optional[Union[float, str]]
13
+ raw_start_at: datetime
14
+ raw_end_at: Optional[datetime]
15
+ metadata: Optional[Any]
16
+
17
+ def __init__(
18
+ self,
19
+ id: Optional[Any],
20
+ name: str,
21
+ value: Optional[Union[float, str]],
22
+ raw_start_at: datetime,
23
+ raw_end_at: Optional[datetime],
24
+ metadata: Optional[Any],
25
+ ):
26
+ self.id = id
27
+ self.name = name
28
+ self.value = value
29
+ self.raw_start_at = raw_start_at
30
+ self.raw_end_at = raw_end_at
31
+ self.metadata = metadata
32
+
33
+ def __repr__(self) -> str:
34
+ raw_start_at_repr = (
35
+ self.raw_start_at.isoformat() if self.raw_start_at else "None"
36
+ )
37
+ raw_end_at_repr = self.raw_end_at.isoformat() if self.raw_end_at else "None"
38
+ return (
39
+ f"Span(id={self.id!r}, name={self.name!r}, value={self.value!r}, "
40
+ f"raw_start_at={raw_start_at_repr}, raw_end_at={raw_end_at_repr}, "
41
+ f"metadata={self.metadata!r})"
42
+ )