influxdata-plugin-utils 0.1.0__tar.gz → 0.2.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.
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-07-12
11
+
12
+ ### Added
13
+
14
+ - `write.write_data` — optional `database` parameter for writing to another
15
+ database.
16
+ - `introspection` — optional `database` parameter for schema helpers and
17
+ `query_window`.
18
+ - `parsing.parse_timedelta` — `ms` (milliseconds) and `us` (microseconds)
19
+ duration units.
20
+
21
+ ### Changed
22
+
23
+ - `write.write_data` — `no_sync` now defaults to `None`: writes go through
24
+ `write` / `write_to_db` (available on all InfluxDB 3 versions); passing a
25
+ boolean switches to `write_sync` / `write_sync_to_db` (InfluxDB 3.8+).
26
+
10
27
  ## [0.1.0] - 2026-07-08
11
28
 
12
29
  ### Added
@@ -22,5 +39,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
39
  - `write` — `build_line`, `build_line_typed`, `add_field_with_type`,
23
40
  `write_data` (batching + retry), `BatchLines`.
24
41
 
25
- [Unreleased]: https://github.com/influxdata/influxdb3_plugins/compare/utils-v0.1.0...HEAD
26
- [0.1.0]: https://github.com/influxdata/influxdb3_plugins/releases/tag/utils-v0.1.0
42
+ [Unreleased]: https://github.com/influxdata/influxdb3_plugins/compare/utils-v0.2.0...HEAD
43
+ [0.2.0]: https://github.com/influxdata/influxdb3_plugins/compare/utils-v0.1.0...utils-v0.2.0
44
+ [0.1.0]: https://github.com/influxdata/influxdb3_plugins/releases/tag/utils-v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: influxdata-plugin-utils
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Shared helpers for InfluxDB 3 plugins.
5
5
  Project-URL: Homepage, https://github.com/influxdata/influxdb3_plugins
6
6
  Project-URL: Repository, https://github.com/influxdata/influxdb3_plugins
@@ -36,7 +36,7 @@ pip install -e influxdata-plugin-utils
36
36
  | Module | What it provides |
37
37
  |-----------------|-----------------------------------------------------------------------------------------------------------------------------|
38
38
  | `config` | `load_plugin_config(args, validators)` (dynaconf-backed), `resolve_plugin_dir()`, `resolve_path()`, re-exported `Validator` |
39
- | `introspection` | `get_table_names()`, `get_tag_names()`, `get_field_names()`, `query_window()` |
39
+ | `introspection` | `get_table_names()`, `get_tag_names()`, `get_field_names()`, `query_window()` with optional `database=` |
40
40
  | `parsing` | `parse_timedelta()`, `parse_timestamp_ns()`, `parse_int()`, `parse_bool()`, `parse_delimited_list()`, `parse_key_value()` |
41
41
  | `cache` | `cached(influxdb3_local, key, producer, ttl_seconds=3600)` |
42
42
  | `write` | `build_line()`, `build_line_typed()`, `add_field_with_type()`, `write_data()`, `BatchLines` |
@@ -82,9 +82,32 @@ lines = [
82
82
  ]
83
83
  write_data(influxdb3_local, lines) # batched + retried by default
84
84
  # write_data(influxdb3_local, lines, batch=False, retries=0) # opt out
85
+ # write_data(influxdb3_local, lines, database="other_db") # another database
86
+ # write_data(influxdb3_local, lines, no_sync=True) # write_sync API (3.8+)
87
+ ```
88
+
89
+ ## Cross-database queries
90
+
91
+ On InfluxDB versions that support processing-engine cross-database queries,
92
+ the introspection helpers accept `database=` and pass it through to
93
+ `influxdb3_local.query`.
94
+ Cached schema results are separated per database.
95
+
96
+ ```python
97
+ from influxdata_plugin_utils.introspection import get_field_names, query_window
98
+
99
+ fields = get_field_names(influxdb3_local, "cpu", database="source_db")
100
+ rows = query_window(
101
+ influxdb3_local,
102
+ "cpu",
103
+ start=start,
104
+ end=end,
105
+ columns=fields,
106
+ database="source_db",
107
+ )
85
108
  ```
86
109
 
87
110
  ## License
88
111
 
89
112
  Licensed under either of [Apache License 2.0](LICENSE-APACHE) or
90
- [MIT license](LICENSE-MIT) at your option.
113
+ [MIT license](LICENSE-MIT) at your option.
@@ -19,7 +19,7 @@ pip install -e influxdata-plugin-utils
19
19
  | Module | What it provides |
20
20
  |-----------------|-----------------------------------------------------------------------------------------------------------------------------|
21
21
  | `config` | `load_plugin_config(args, validators)` (dynaconf-backed), `resolve_plugin_dir()`, `resolve_path()`, re-exported `Validator` |
22
- | `introspection` | `get_table_names()`, `get_tag_names()`, `get_field_names()`, `query_window()` |
22
+ | `introspection` | `get_table_names()`, `get_tag_names()`, `get_field_names()`, `query_window()` with optional `database=` |
23
23
  | `parsing` | `parse_timedelta()`, `parse_timestamp_ns()`, `parse_int()`, `parse_bool()`, `parse_delimited_list()`, `parse_key_value()` |
24
24
  | `cache` | `cached(influxdb3_local, key, producer, ttl_seconds=3600)` |
25
25
  | `write` | `build_line()`, `build_line_typed()`, `add_field_with_type()`, `write_data()`, `BatchLines` |
@@ -65,9 +65,32 @@ lines = [
65
65
  ]
66
66
  write_data(influxdb3_local, lines) # batched + retried by default
67
67
  # write_data(influxdb3_local, lines, batch=False, retries=0) # opt out
68
+ # write_data(influxdb3_local, lines, database="other_db") # another database
69
+ # write_data(influxdb3_local, lines, no_sync=True) # write_sync API (3.8+)
70
+ ```
71
+
72
+ ## Cross-database queries
73
+
74
+ On InfluxDB versions that support processing-engine cross-database queries,
75
+ the introspection helpers accept `database=` and pass it through to
76
+ `influxdb3_local.query`.
77
+ Cached schema results are separated per database.
78
+
79
+ ```python
80
+ from influxdata_plugin_utils.introspection import get_field_names, query_window
81
+
82
+ fields = get_field_names(influxdb3_local, "cpu", database="source_db")
83
+ rows = query_window(
84
+ influxdb3_local,
85
+ "cpu",
86
+ start=start,
87
+ end=end,
88
+ columns=fields,
89
+ database="source_db",
90
+ )
68
91
  ```
69
92
 
70
93
  ## License
71
94
 
72
95
  Licensed under either of [Apache License 2.0](LICENSE-APACHE) or
73
- [MIT license](LICENSE-MIT) at your option.
96
+ [MIT license](LICENSE-MIT) at your option.
@@ -8,7 +8,7 @@ Modules:
8
8
  write - LineBuilder builders and resilient write_data
9
9
  """
10
10
 
11
- __version__ = "0.1.0"
11
+ __version__ = "0.2.0"
12
12
 
13
13
  from . import cache, config, introspection, parsing, write
14
14
  from .cache import cached
@@ -2,6 +2,8 @@
2
2
 
3
3
  Each helper takes ``influxdb3_local`` explicitly and may optionally use the
4
4
  TTL cache. Queries mirror the patterns already used across the plugins.
5
+ Helpers accept an optional ``database`` argument for engines that support
6
+ cross-database plugin queries.
5
7
  """
6
8
 
7
9
  from .cache import cached
@@ -22,13 +24,40 @@ def _quote_identifier(identifier: str) -> str:
22
24
  return '"' + identifier.replace('"', '""') + '"'
23
25
 
24
26
 
27
+ def _query(
28
+ influxdb3_local,
29
+ query: str,
30
+ args: dict | None = None,
31
+ database: str | None = None,
32
+ ):
33
+ """Run a plugin query, preserving the old call shape when no database is set."""
34
+ if database is None:
35
+ if args is None:
36
+ return influxdb3_local.query(query)
37
+ return influxdb3_local.query(query, args)
38
+ if args is None:
39
+ return influxdb3_local.query(query, database=database)
40
+ return influxdb3_local.query(query, args, database=database)
41
+
42
+
43
+ def _cache_key(base: str, database: str | None) -> str:
44
+ """Return a cache key, keeping existing default-database keys unchanged."""
45
+ if database is None:
46
+ return base
47
+ return f"{base}:database:{database}"
48
+
49
+
25
50
  def get_table_names(
26
- influxdb3_local, *, use_cache: bool = True, ttl_seconds: int = 3600
51
+ influxdb3_local,
52
+ *,
53
+ database: str | None = None,
54
+ use_cache: bool = True,
55
+ ttl_seconds: int = 3600,
27
56
  ) -> list[str]:
28
57
  """Return base table names via ``SHOW TABLES``."""
29
58
 
30
59
  def producer() -> list[str]:
31
- rows = influxdb3_local.query("SHOW TABLES")
60
+ rows = _query(influxdb3_local, "SHOW TABLES", database=database)
32
61
  return [
33
62
  row["table_name"]
34
63
  for row in rows
@@ -36,12 +65,22 @@ def get_table_names(
36
65
  ]
37
66
 
38
67
  if use_cache:
39
- return cached(influxdb3_local, "shared:tables", producer, ttl_seconds=ttl_seconds)
68
+ return cached(
69
+ influxdb3_local,
70
+ _cache_key("shared:tables", database),
71
+ producer,
72
+ ttl_seconds=ttl_seconds,
73
+ )
40
74
  return producer()
41
75
 
42
76
 
43
77
  def get_tag_names(
44
- influxdb3_local, table: str, *, use_cache: bool = True, ttl_seconds: int = 3600
78
+ influxdb3_local,
79
+ table: str,
80
+ *,
81
+ database: str | None = None,
82
+ use_cache: bool = True,
83
+ ttl_seconds: int = 3600,
45
84
  ) -> list[str]:
46
85
  """Return tag column names of a table (``Dictionary(Int32, Utf8)`` columns)."""
47
86
 
@@ -50,14 +89,20 @@ def get_tag_names(
50
89
  "SELECT column_name FROM information_schema.columns "
51
90
  "WHERE table_name = $table AND data_type = $data_type"
52
91
  )
53
- rows = influxdb3_local.query(
54
- query, {"table": table, "data_type": _TAG_DATA_TYPE}
92
+ rows = _query(
93
+ influxdb3_local,
94
+ query,
95
+ {"table": table, "data_type": _TAG_DATA_TYPE},
96
+ database=database,
55
97
  )
56
98
  return [row["column_name"] for row in rows]
57
99
 
58
100
  if use_cache:
59
101
  return cached(
60
- influxdb3_local, f"shared:tags:{table}", producer, ttl_seconds=ttl_seconds
102
+ influxdb3_local,
103
+ _cache_key(f"shared:tags:{table}", database),
104
+ producer,
105
+ ttl_seconds=ttl_seconds,
61
106
  )
62
107
  return producer()
63
108
 
@@ -67,6 +112,7 @@ def get_field_names(
67
112
  table: str,
68
113
  *,
69
114
  numeric_only: bool = False,
115
+ database: str | None = None,
70
116
  use_cache: bool = True,
71
117
  ttl_seconds: int = 3600,
72
118
  ) -> list[str]:
@@ -80,7 +126,7 @@ def get_field_names(
80
126
  "SELECT column_name, data_type FROM information_schema.columns "
81
127
  "WHERE table_name = $table"
82
128
  )
83
- rows = influxdb3_local.query(query, {"table": table})
129
+ rows = _query(influxdb3_local, query, {"table": table}, database=database)
84
130
  names: list[str] = []
85
131
  for row in rows:
86
132
  name = row["column_name"]
@@ -93,7 +139,7 @@ def get_field_names(
93
139
  return names
94
140
 
95
141
  if use_cache:
96
- key = f"shared:fields:{table}:{int(numeric_only)}"
142
+ key = _cache_key(f"shared:fields:{table}:{int(numeric_only)}", database)
97
143
  return cached(influxdb3_local, key, producer, ttl_seconds=ttl_seconds)
98
144
  return producer()
99
145
 
@@ -105,6 +151,7 @@ def query_window(
105
151
  start,
106
152
  end,
107
153
  columns: list[str] | None = None,
154
+ database: str | None = None,
108
155
  ) -> list[dict]:
109
156
  """Run a basic ``time``-window query and return rows (``[]`` if none).
110
157
 
@@ -118,5 +165,10 @@ def query_window(
118
165
  f"SELECT {selected} FROM {_quote_identifier(table)} "
119
166
  "WHERE time >= $start AND time < $end ORDER BY time"
120
167
  )
121
- rows = influxdb3_local.query(query, {"start": start, "end": end})
122
- return rows or []
168
+ rows = _query(
169
+ influxdb3_local,
170
+ query,
171
+ {"start": start, "end": end},
172
+ database=database,
173
+ )
174
+ return rows or []
@@ -19,6 +19,8 @@ __all__ = [
19
19
 
20
20
  _DURATION_RE = re.compile(r"^\s*(\d+)\s*([a-zA-Z]+)\s*$")
21
21
  _DURATION_UNITS = {
22
+ "us": "microseconds",
23
+ "ms": "milliseconds",
22
24
  "s": "seconds",
23
25
  "min": "minutes",
24
26
  "h": "hours",
@@ -33,7 +35,7 @@ _FALSE = {"false", "f", "0", "no", "off"}
33
35
 
34
36
 
35
37
  def parse_timedelta(raw) -> timedelta:
36
- """Parse a duration like ``30s``, ``5min``, ``1h``, ``2d``, ``1w``."""
38
+ """Parse a duration like ``500us``, ``100ms``, ``30s``, ``5min``, ``1h``, ``2d``, ``1w``."""
37
39
  if isinstance(raw, timedelta):
38
40
  return raw
39
41
  match = _DURATION_RE.match(str(raw))
@@ -9,6 +9,7 @@ write helpers operate on already-built line objects and need no class.
9
9
  import math
10
10
  import random
11
11
  import time
12
+ from functools import partial
12
13
 
13
14
  from .parsing import parse_bool
14
15
 
@@ -160,7 +161,8 @@ def write_data(
160
161
  batch: bool = True,
161
162
  retries: int = 3,
162
163
  base_delay: float = 1.0,
163
- no_sync: bool = True,
164
+ no_sync: bool | None = None,
165
+ database: str | None = None,
164
166
  ) -> None:
165
167
  """Write LineBuilder objects with optional batching and retry.
166
168
 
@@ -170,8 +172,17 @@ def write_data(
170
172
  batch: Combine all lines into one payload via :class:`BatchLines`.
171
173
  Set ``False`` to write each line individually.
172
174
  retries: Number of extra attempts on failure (``0`` disables retry).
175
+ Effective only when ``no_sync`` is set: the default buffered
176
+ ``write`` / ``write_to_db`` never raise at call time.
173
177
  base_delay: Base seconds for exponential backoff with jitter.
174
- no_sync: Passed through to ``write_sync``.
178
+ no_sync: When set, writes go through ``write_sync`` /
179
+ ``write_sync_to_db`` with this flag (requires InfluxDB 3.8+),
180
+ which write immediately and raise on failure. When ``None``
181
+ (default), the universally available ``write`` / ``write_to_db``
182
+ are used: they only queue lines that are flushed after plugin
183
+ execution completes, so failures are not reported to the caller.
184
+ database: Target database; when ``None``, writes to the trigger's
185
+ database.
175
186
  """
176
187
  builders = [builder for builder in line_builders if builder is not None]
177
188
  if not builders:
@@ -180,13 +191,23 @@ def write_data(
180
191
  payloads = [BatchLines(builders)] if batch else builders
181
192
  attempts = max(retries, 0) + 1
182
193
 
194
+ if no_sync is None:
195
+ if database is not None:
196
+ write_fn = partial(influxdb3_local.write_to_db, database)
197
+ else:
198
+ write_fn = influxdb3_local.write
199
+ elif database is not None:
200
+ write_fn = partial(influxdb3_local.write_sync_to_db, database, no_sync=no_sync)
201
+ else:
202
+ write_fn = partial(influxdb3_local.write_sync, no_sync=no_sync)
203
+
183
204
  for payload in payloads:
184
205
  for attempt in range(attempts):
185
206
  try:
186
- influxdb3_local.write_sync(payload, no_sync=no_sync)
207
+ write_fn(payload)
187
208
  break
188
209
  except Exception:
189
210
  if attempt == attempts - 1:
190
211
  raise
191
212
  delay = (2**attempt) * base_delay + random.uniform(0, base_delay)
192
- time.sleep(delay)
213
+ time.sleep(delay)