loki-reader-core 0.2.1__tar.gz → 0.2.2__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.
Files changed (21) hide show
  1. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/PKG-INFO +1 -1
  2. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/pyproject.toml +1 -1
  3. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/__init__.py +1 -1
  4. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/client.py +18 -2
  5. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/tests/test_client.py +25 -5
  6. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/.gitignore +0 -0
  7. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/README.md +0 -0
  8. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/build-publish.sh +0 -0
  9. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/dev-requirements.txt +0 -0
  10. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/exceptions.py +0 -0
  11. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/models/__init__.py +0 -0
  12. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/models/log_entry.py +0 -0
  13. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/models/log_stream.py +0 -0
  14. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/models/metric_sample.py +0 -0
  15. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/models/metric_series.py +0 -0
  16. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/models/query_result.py +0 -0
  17. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/models/query_stats.py +0 -0
  18. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/src/loki_reader_core/utils.py +0 -0
  19. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/tests/__init__.py +0 -0
  20. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/tests/test_models.py +0 -0
  21. {loki_reader_core-0.2.1 → loki_reader_core-0.2.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loki-reader-core
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Python library for querying Grafana Loki logs via REST API
5
5
  Project-URL: Homepage, https://github.com/jmazzahacks/loki-reader-core
6
6
  Project-URL: Issues, https://github.com/jmazzahacks/loki-reader-core/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "loki-reader-core"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  authors = [
5
5
  { name="Jason Byteforge", email="jason@mzmail.me" },
6
6
  ]
@@ -6,7 +6,7 @@ from .client import LokiClient
6
6
  from .exceptions import LokiAuthError, LokiConnectionError, LokiError, LokiQueryError
7
7
  from .models import LogEntry, LogStream, MetricSample, MetricSeries, QueryResult, QueryStats
8
8
 
9
- __version__ = "0.2.1"
9
+ __version__ = "0.2.2"
10
10
 
11
11
  __all__ = [
12
12
  "LokiClient",
@@ -269,6 +269,20 @@ class LokiClient:
269
269
 
270
270
  return data
271
271
 
272
+ def _discovery_time_range(self) -> tuple[int, int]:
273
+ """Get a 30-day time range for label discovery queries.
274
+
275
+ Without a time range, Loki's label API only returns labels for
276
+ recently active streams. Using a 30-day window ensures we find
277
+ apps that haven't logged in a while.
278
+
279
+ Returns:
280
+ Tuple of (start_ns, end_ns) covering the last 30 days.
281
+ """
282
+ end = now_ns()
283
+ start = end - (30 * 24 * NANOSECONDS_PER_HOUR)
284
+ return (start, end)
285
+
272
286
  def _find_app_label(self, app_value: str) -> str:
273
287
  """Discover which label name contains the given app value.
274
288
 
@@ -287,8 +301,9 @@ class LokiClient:
287
301
  if app_value in self._app_label_cache:
288
302
  return self._app_label_cache[app_value]
289
303
 
304
+ start, end = self._discovery_time_range()
290
305
  for label_name in APP_LABEL_NAMES:
291
- values = self.get_label_values(label_name)
306
+ values = self.get_label_values(label_name, start=start, end=end)
292
307
  if app_value in values:
293
308
  self._app_label_cache[app_value] = label_name
294
309
  return label_name
@@ -310,8 +325,9 @@ class LokiClient:
310
325
  if self._severity_label_cache is not None:
311
326
  return self._severity_label_cache
312
327
 
328
+ start, end = self._discovery_time_range()
313
329
  for label_name in SEVERITY_LABEL_NAMES:
314
- values = self.get_label_values(label_name)
330
+ values = self.get_label_values(label_name, start=start, end=end)
315
331
  if values:
316
332
  self._severity_label_cache = label_name
317
333
  return label_name
@@ -453,12 +453,16 @@ class TestLabelDiscovery:
453
453
  mock_glv.return_value = ["myapp", "otherapp"]
454
454
  result = client._find_app_label("myapp")
455
455
  assert result == "application"
456
- mock_glv.assert_called_once_with("application")
456
+ mock_glv.assert_called_once()
457
+ args = mock_glv.call_args
458
+ assert args.args[0] == "application"
459
+ assert "start" in args.kwargs
460
+ assert "end" in args.kwargs
457
461
 
458
462
  def test_find_app_label_job(self) -> None:
459
463
  client = LokiClient(base_url="http://localhost:3100")
460
464
  with patch.object(client, "get_label_values") as mock_glv:
461
- def side_effect(label: str) -> list[str]:
465
+ def side_effect(label: str, start: int = None, end: int = None) -> list[str]:
462
466
  if label == "job":
463
467
  return ["myapp", "worker"]
464
468
  return []
@@ -480,7 +484,11 @@ class TestLabelDiscovery:
480
484
  mock_glv.return_value = ["info", "error", "warn"]
481
485
  result = client._find_severity_label()
482
486
  assert result == "level"
483
- mock_glv.assert_called_once_with("level")
487
+ mock_glv.assert_called_once()
488
+ args = mock_glv.call_args
489
+ assert args.args[0] == "level"
490
+ assert "start" in args.kwargs
491
+ assert "end" in args.kwargs
484
492
 
485
493
  def test_find_severity_label_none(self) -> None:
486
494
  client = LokiClient(base_url="http://localhost:3100")
@@ -498,7 +506,7 @@ class TestLabelDiscovery:
498
506
  client._find_app_label("myapp")
499
507
 
500
508
  # Only called once - second call uses cache
501
- mock_glv.assert_called_once_with("application")
509
+ mock_glv.assert_called_once()
502
510
 
503
511
  def test_severity_label_cached(self) -> None:
504
512
  client = LokiClient(base_url="http://localhost:3100")
@@ -508,7 +516,19 @@ class TestLabelDiscovery:
508
516
  client._find_severity_label()
509
517
  client._find_severity_label()
510
518
 
511
- mock_glv.assert_called_once_with("level")
519
+ mock_glv.assert_called_once()
520
+
521
+ def test_discovery_uses_30_day_range(self) -> None:
522
+ client = LokiClient(base_url="http://localhost:3100")
523
+ with patch.object(client, "get_label_values") as mock_glv:
524
+ mock_glv.return_value = ["myapp"]
525
+ client._find_app_label("myapp")
526
+
527
+ args = mock_glv.call_args
528
+ start = args.kwargs["start"]
529
+ end = args.kwargs["end"]
530
+ diff_days = (end - start) / (24 * 60 * 60 * 1_000_000_000)
531
+ assert diff_days == 30
512
532
 
513
533
 
514
534
  class TestMergeStreams: