fluidattacks_core_logging 12.0.0__tar.gz → 12.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.
Files changed (28) hide show
  1. fluidattacks_core_logging-12.2.0/.envrc +1 -0
  2. fluidattacks_core_logging-12.2.0/.gitignore +15 -0
  3. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/PKG-INFO +1 -1
  4. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/__init__.py +15 -0
  5. fluidattacks_core_logging-12.2.0/fluidattacks_core/logging/capture.py +57 -0
  6. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/handlers.py +29 -5
  7. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/import-linter.cfg +2 -10
  8. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/pyproject.toml +1 -1
  9. fluidattacks_core_logging-12.2.0/tests/test_capture.py +69 -0
  10. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/tests/test_logging.py +166 -0
  11. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/uv.lock +1 -1
  12. fluidattacks_core_logging-12.0.0/.envrc +0 -1
  13. fluidattacks_core_logging-12.0.0/.gitignore +0 -5
  14. fluidattacks_core_logging-12.0.0/build/default.nix +0 -19
  15. fluidattacks_core_logging-12.0.0/build/filter.nix +0 -13
  16. fluidattacks_core_logging-12.0.0/flake.lock +0 -303
  17. fluidattacks_core_logging-12.0.0/flake.nix +0 -29
  18. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/filters.py +0 -0
  19. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/formatters.py +0 -0
  20. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/presets.py +0 -0
  21. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/py.typed +0 -0
  22. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/sources/__init__.py +0 -0
  23. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/sources/types.py +0 -0
  24. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/sources/utils.py +0 -0
  25. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/fluidattacks_core/logging/utils.py +0 -0
  26. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/mypy.ini +0 -0
  27. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/ruff.toml +0 -0
  28. {fluidattacks_core_logging-12.0.0 → fluidattacks_core_logging-12.2.0}/tests/__init__.py +0 -0
@@ -0,0 +1 @@
1
+ use flake ../../..#fluidattacks-core-logging
@@ -0,0 +1,15 @@
1
+ dist/
2
+ dist-*/
3
+ *.zip
4
+ /utils/terraform-modules/**/.terraform.lock.hcl
5
+ /.cargo-cache
6
+ target/
7
+ .direnv/
8
+ .venv/
9
+ .vscode/settings.json
10
+ .zed/settings.json
11
+ # each package's shell writes its own, beside the package rather than here
12
+ **/.vscode/settings.json
13
+ *.so
14
+ # the publish check builds into its own tree per architecture
15
+ target-*/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fluidattacks_core_logging
3
- Version: 12.0.0
3
+ Version: 12.2.0
4
4
  Summary: Fluid Attacks Core Logging Library
5
5
  Author-email: Development <development@fluidattacks.com>
6
6
  License: MPL-2.0
@@ -5,6 +5,7 @@ import logging.config
5
5
  import sys
6
6
  from typing import TYPE_CHECKING
7
7
 
8
+ from fluidattacks_core.logging.capture import CapturedLog, CaptureHandler
8
9
  from fluidattacks_core.logging.presets import DATE_FORMAT, PRODUCT_LOGGING
9
10
  from fluidattacks_core.logging.sources.utils import (
10
11
  set_commit_ref_name,
@@ -45,10 +46,24 @@ def init_logging(preset: _DictConfigArgs | None = None) -> None:
45
46
  init_uncaught_exception_logging()
46
47
 
47
48
 
49
+ def init_logging_captured(preset: _DictConfigArgs | None = None) -> CapturedLog:
50
+ """`init_logging`, that also keeps a copy of everything logged.
51
+
52
+ The console is unchanged; the copy is what a program reads back to hand its output to
53
+ someone else once the run is over. Call it once: each call adds another copy.
54
+ """
55
+ init_logging(preset)
56
+ capture = CapturedLog()
57
+ logging.getLogger().addHandler(CaptureHandler(capture))
58
+ return capture
59
+
60
+
48
61
  __all__ = [
49
62
  "DATE_FORMAT",
50
63
  "PRODUCT_LOGGING",
64
+ "CapturedLog",
51
65
  "init_logging",
66
+ "init_logging_captured",
52
67
  "init_uncaught_exception_logging",
53
68
  "set_commit_ref_name",
54
69
  "set_commit_sha",
@@ -0,0 +1,57 @@
1
+ """Opt-in copy of what the logger writes.
2
+
3
+ For a program that has to hand its output to someone else after the run.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ import threading
10
+
11
+ from fluidattacks_core.logging.presets import DATE_FORMAT
12
+
13
+
14
+ class CapturedLog:
15
+ """What the logger wrote since `init_logging_captured`, as plain text lines.
16
+
17
+ Each line reads `<date> [<level>] [<logger>] <message>`, followed by the traceback
18
+ when the record carries one. It is the same whatever the environment, so it holds no
19
+ ANSI colors and no JSON, unlike the console.
20
+
21
+ It keeps everything: it never trims, and whoever receives it decides how much to
22
+ accept. Safe to read while other threads log.
23
+ """
24
+
25
+ def __init__(self) -> None:
26
+ self._lock = threading.Lock()
27
+ self._lines: list[str] = []
28
+
29
+ def _push(self, text: str) -> None:
30
+ with self._lock:
31
+ self._lines.append(text)
32
+
33
+ def contents(self) -> str:
34
+ """Return the output so far."""
35
+ with self._lock:
36
+ return "".join(self._lines)
37
+
38
+
39
+ class CaptureHandler(logging.Handler):
40
+ """Feeds every record it receives into a `CapturedLog`."""
41
+
42
+ def __init__(self, capture: CapturedLog) -> None:
43
+ super().__init__()
44
+ self.capture = capture
45
+ self.setFormatter(
46
+ logging.Formatter(
47
+ "{asctime} [{levelname}] [{name}] {message}",
48
+ datefmt=DATE_FORMAT,
49
+ style="{",
50
+ ),
51
+ )
52
+
53
+ def emit(self, record: logging.LogRecord) -> None:
54
+ try:
55
+ self.capture._push(self.format(record) + "\n") # noqa: SLF001
56
+ except Exception: # noqa: BLE001
57
+ self.handleError(record)
@@ -1,9 +1,10 @@
1
1
  import copy
2
2
  import logging
3
3
  import sys
4
+ import threading
4
5
  from logging.handlers import HTTPHandler, QueueHandler, QueueListener
5
- from queue import Queue
6
- from typing import TextIO
6
+ from queue import Full, Queue
7
+ from typing import TextIO, cast
7
8
 
8
9
  import simplejson as json
9
10
 
@@ -81,23 +82,46 @@ class ProductionSyncHandler(logging.StreamHandler[TextIO]):
81
82
  self.setFormatter(CustomJsonFormatter())
82
83
 
83
84
 
85
+ class _DrainingStopListener(QueueListener):
86
+ """Listener that drains the queue before stopping, so a full one cannot reject it."""
87
+
88
+ def stop(self) -> None:
89
+ cast("Queue[object]", self.queue).join()
90
+ super().stop()
91
+
92
+
84
93
  class ProductionAsyncHandler(CustomQueueHandler):
85
94
  """Logging handler for production environments implemented with `QueueHandler`.
86
95
 
87
96
  Includes:
88
97
  - Filters: `NoBatchFilter`, `ProductionOnlyFilter`
89
98
  - Formatter: `CustomJsonFormatter`
99
+
100
+ A positive `max_queue_size` drops records once the queue is full.
90
101
  """
91
102
 
92
- def __init__(self, stream: TextIO = sys.stderr) -> None:
103
+ def __init__(self, stream: TextIO = sys.stderr, max_queue_size: int = 0) -> None:
93
104
  handler = logging.StreamHandler(stream)
94
105
  handler.addFilter(ProductionOnlyFilter())
95
106
  handler.setFormatter(CustomJsonFormatter())
96
107
 
97
- super().__init__(Queue())
108
+ super().__init__(Queue(maxsize=max_queue_size))
98
109
  self.shutting_down = False
110
+ self.dropped = 0
111
+ self._dropped_lock = threading.Lock()
99
112
 
100
- self._attach_listener(QueueListener(self.queue, handler))
113
+ self._attach_listener(_DrainingStopListener(self.queue, handler))
114
+
115
+ def on_drop(self) -> None:
116
+ """Report a dropped record; it must neither log nor raise."""
117
+
118
+ def enqueue(self, record: logging.LogRecord) -> None:
119
+ try:
120
+ super().enqueue(record)
121
+ except Full:
122
+ with self._dropped_lock:
123
+ self.dropped += 1
124
+ self.on_drop()
101
125
 
102
126
  def emit(self, record: logging.LogRecord) -> None:
103
127
  if self.shutting_down:
@@ -1,16 +1,7 @@
1
1
  [importlinter]
2
- root_package = fluidattacks_core
2
+ root_package = fluidattacks_core.logging
3
3
  include_external_packages = True
4
4
 
5
- [importlinter:contract:portion]
6
- name = Single Portion
7
- type = layers
8
- containers =
9
- fluidattacks_core
10
- exhaustive = true
11
- layers =
12
- logging
13
-
14
5
  [importlinter:contract:dag]
15
6
  name = Direct Acyclic Graph
16
7
  type = layers
@@ -18,6 +9,7 @@
18
9
  fluidattacks_core.logging
19
10
  exhaustive = true
20
11
  layers =
12
+ capture
21
13
  presets
22
14
  handlers
23
15
  formatters
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fluidattacks_core_logging"
3
- version = "12.0.0"
3
+ version = "12.2.0"
4
4
  description = "Fluid Attacks Core Logging Library"
5
5
  authors = [{ name = "Development", email = "development@fluidattacks.com" }]
6
6
  license = { text = "MPL-2.0" }
@@ -0,0 +1,69 @@
1
+ import logging
2
+ from collections.abc import Iterator
3
+
4
+ import pytest
5
+
6
+ from fluidattacks_core.logging import CapturedLog, init_logging_captured
7
+ from fluidattacks_core.logging.capture import CaptureHandler
8
+
9
+
10
+ @pytest.fixture
11
+ def root_logger() -> Iterator[logging.Logger]:
12
+ root = logging.getLogger()
13
+ handlers, level = list(root.handlers), root.level
14
+ yield root
15
+ root.handlers[:] = handlers
16
+ root.setLevel(level)
17
+
18
+
19
+ def _written(capture: CapturedLog, text: str) -> None:
20
+ capture._push(text) # noqa: SLF001
21
+
22
+
23
+ def test_a_capture_keeps_what_the_logger_wrote(root_logger: logging.Logger) -> None:
24
+ capture = init_logging_captured()
25
+ logging.getLogger("scan").info("first")
26
+ logging.getLogger("scan").warning("second")
27
+ lines = capture.contents().splitlines()
28
+ assert len(lines) == 2
29
+ assert lines[0].endswith("[INFO] [scan] first")
30
+ assert lines[1].endswith("[WARNING] [scan] second")
31
+ assert root_logger.handlers
32
+
33
+
34
+ def test_the_copy_has_no_color_codes(root_logger: logging.Logger) -> None:
35
+ capture = init_logging_captured()
36
+ logging.getLogger("scan").error("boom")
37
+ assert "\x1b" not in capture.contents()
38
+ assert root_logger.handlers
39
+
40
+
41
+ def test_the_copy_is_the_same_in_production(
42
+ monkeypatch: pytest.MonkeyPatch, root_logger: logging.Logger
43
+ ) -> None:
44
+ monkeypatch.setenv("CI_COMMIT_REF_NAME", "trunk")
45
+ capture = init_logging_captured()
46
+ logging.getLogger("scan").info("plain")
47
+ assert capture.contents().endswith("[INFO] [scan] plain\n")
48
+ assert root_logger.handlers
49
+
50
+
51
+ def test_a_traceback_is_kept(root_logger: logging.Logger) -> None:
52
+ capture = CapturedLog()
53
+ root_logger.addHandler(CaptureHandler(capture))
54
+ root_logger.setLevel(logging.INFO)
55
+ try:
56
+ message = "bad value"
57
+ raise ValueError(message) # noqa: TRY301
58
+ except ValueError:
59
+ logging.getLogger("scan").exception("failed")
60
+ assert "Traceback" in capture.contents()
61
+ assert "ValueError: bad value" in capture.contents()
62
+
63
+
64
+ def test_a_long_log_is_kept_whole() -> None:
65
+ capture = CapturedLog()
66
+ lines = [f"line {number}\n" for number in range(100_000)]
67
+ for line in lines:
68
+ _written(capture, line)
69
+ assert capture.contents() == "".join(lines)
@@ -1,10 +1,20 @@
1
+ import io
1
2
  import logging
2
3
  import sys
4
+ import threading
5
+ import time
6
+ from queue import Queue
7
+ from typing import cast
3
8
 
4
9
  import pytest
5
10
  import simplejson as json
6
11
 
7
12
  from fluidattacks_core.logging import init_logging
13
+ from fluidattacks_core.logging.handlers import (
14
+ CustomQueueHandler,
15
+ ProductionAsyncHandler,
16
+ TelemetryAsyncHandler,
17
+ )
8
18
  from fluidattacks_core.logging.utils import set_telemetry_metadata
9
19
 
10
20
 
@@ -321,3 +331,159 @@ def test_colorful_formatter_uses_colors_for_warning(
321
331
  assert "\x1b[0m" in log_message
322
332
  assert "[WARNING] [product]" in log_message
323
333
  assert "A warning message" in log_message
334
+
335
+
336
+ def _listener_consumes_new_records(handler: ProductionAsyncHandler | TelemetryAsyncHandler) -> bool:
337
+ queue = handler.queue
338
+ assert isinstance(queue, Queue)
339
+
340
+ record = logging.LogRecord("test", logging.INFO, __file__, 1, "Test message", None, None)
341
+ queue.put_nowait(record)
342
+ time.sleep(0.2)
343
+
344
+ return queue.empty()
345
+
346
+
347
+ def test_custom_queue_handler_defaults_leave_no_listener() -> None:
348
+ # A fresh handler carries no listener on any interpreter: the class default
349
+ # leaves it empty, and `QueueHandler.__init__` clears it on Python 3.12+.
350
+ # Attaching is therefore the only thing that can install one, which the
351
+ # subclass tests below observe.
352
+ queue: Queue[logging.LogRecord] = Queue()
353
+
354
+ handler = CustomQueueHandler(queue)
355
+
356
+ assert handler.listener is None
357
+ assert handler.shutting_down is False
358
+
359
+
360
+ def test_production_async_handler_ends_init_with_a_running_listener() -> None:
361
+ handler = ProductionAsyncHandler(io.StringIO())
362
+ listener = handler.listener
363
+
364
+ handler.close()
365
+
366
+ assert listener is not None
367
+ assert isinstance(handler.queue, Queue)
368
+
369
+
370
+ def test_telemetry_async_handler_ends_init_with_a_running_listener() -> None:
371
+ handler = TelemetryAsyncHandler("test-service", "test-source", "test-token")
372
+ listener = handler.listener
373
+
374
+ handler.close()
375
+
376
+ assert listener is not None
377
+ assert isinstance(handler.queue, Queue)
378
+
379
+
380
+ def test_production_async_handler_close_stops_listener_and_is_idempotent() -> None:
381
+ handler = ProductionAsyncHandler(io.StringIO())
382
+
383
+ handler.close()
384
+ handler.close()
385
+
386
+ assert not _listener_consumes_new_records(handler)
387
+
388
+
389
+ def test_telemetry_async_handler_close_stops_listener_and_is_idempotent() -> None:
390
+ handler = TelemetryAsyncHandler("test-service", "test-source", "test-token")
391
+
392
+ handler.close()
393
+ handler.close()
394
+
395
+ assert not _listener_consumes_new_records(handler)
396
+
397
+
398
+ def test_async_handler_init_leaves_a_closable_handler() -> None:
399
+ # The class-level defaults make a close() racing __init__ a safe no-op, but
400
+ # __init__ must still finish with a handler that a later close() can stop:
401
+ # a listener left running behind an already-true shutting_down would never
402
+ # be drained.
403
+ handler = ProductionAsyncHandler(io.StringIO())
404
+
405
+ assert handler.shutting_down is False
406
+ assert handler.listener is not None
407
+
408
+ handler.close()
409
+
410
+ assert not _listener_consumes_new_records(handler)
411
+
412
+
413
+ class _BlockedStream(io.StringIO):
414
+ """Stream whose writes wait for `release`, keeping the listener busy."""
415
+
416
+ def __init__(self) -> None:
417
+ super().__init__()
418
+ self.release = threading.Event()
419
+
420
+ def write(self, s: str) -> int:
421
+ self.release.wait(timeout=5)
422
+ return super().write(s)
423
+
424
+
425
+ def _queue_of(handler: ProductionAsyncHandler) -> Queue[logging.LogRecord]:
426
+ return cast("Queue[logging.LogRecord]", handler.queue)
427
+
428
+
429
+ def _record(message: str) -> logging.LogRecord:
430
+ return logging.LogRecord("test", logging.INFO, __file__, 0, message, None, None)
431
+
432
+
433
+ def test_production_async_handler_is_unbounded_by_default() -> None:
434
+ handler = ProductionAsyncHandler(io.StringIO())
435
+
436
+ assert _queue_of(handler).maxsize == 0
437
+ assert handler.dropped == 0
438
+
439
+ handler.close()
440
+
441
+
442
+ def test_production_async_handler_drops_records_when_the_queue_is_full() -> None:
443
+ stream = _BlockedStream()
444
+ handler = ProductionAsyncHandler(stream, max_queue_size=2)
445
+ total = 10
446
+
447
+ for index in range(total):
448
+ handler.emit(_record(f"message {index}"))
449
+
450
+ # One record may be held by the busy listener, the rest wait in the queue.
451
+ assert _queue_of(handler).qsize() == 2
452
+ assert total - 3 <= handler.dropped <= total - 2
453
+
454
+ stream.release.set()
455
+ handler.close()
456
+
457
+
458
+ def test_production_async_handler_calls_on_drop_once_per_dropped_record() -> None:
459
+ drops: list[int] = []
460
+
461
+ class _Reporting(ProductionAsyncHandler):
462
+ def on_drop(self) -> None:
463
+ drops.append(1)
464
+
465
+ stream = _BlockedStream()
466
+ handler = _Reporting(stream, max_queue_size=1)
467
+
468
+ for index in range(5):
469
+ handler.emit(_record(f"message {index}"))
470
+
471
+ assert len(drops) == handler.dropped
472
+ assert handler.dropped >= 3
473
+
474
+ stream.release.set()
475
+ handler.close()
476
+
477
+
478
+ def test_production_async_handler_closes_with_a_full_queue() -> None:
479
+ stream = _BlockedStream()
480
+ handler = ProductionAsyncHandler(stream, max_queue_size=1)
481
+
482
+ for index in range(4):
483
+ handler.emit(_record(f"message {index}"))
484
+ stream.release.set()
485
+ handler.close()
486
+
487
+ assert handler.listener is not None
488
+ assert handler.shutting_down is True
489
+ assert not _listener_consumes_new_records(handler)
@@ -148,7 +148,7 @@ wheels = [
148
148
 
149
149
  [[package]]
150
150
  name = "fluidattacks-core-logging"
151
- version = "12.0.0"
151
+ version = "12.2.0"
152
152
  source = { editable = "." }
153
153
  dependencies = [
154
154
  { name = "python-json-logger" },
@@ -1 +0,0 @@
1
- use_flake ".#python311.devShell"
@@ -1,5 +0,0 @@
1
- target/
2
- .direnv/
3
- .venv/
4
- .vscode/settings.json
5
- .zed/settings.json
@@ -1,19 +0,0 @@
1
- { src, workspace_root }:
2
- {
3
- inherit src workspace_root;
4
- root_path = "develops/fluidattacks-core/python/logging";
5
- module_name = "fluidattacks_core";
6
- pypi_token = {
7
- method = "oidc";
8
- };
9
- override = {
10
- checks = {
11
- basedpyright = false;
12
- };
13
- configs = {
14
- import_linter = ../import-linter.cfg;
15
- mypy = ../mypy.ini;
16
- ruff = ../ruff.toml;
17
- };
18
- };
19
- }
@@ -1,13 +0,0 @@
1
- path_filter: src:
2
- path_filter {
3
- root = src;
4
- include = [
5
- "fluidattacks_core"
6
- "tests"
7
- "import-linter.cfg"
8
- "mypy.ini"
9
- "pyproject.toml"
10
- "ruff.toml"
11
- "uv.lock"
12
- ];
13
- }
@@ -1,303 +0,0 @@
1
- {
2
- "nodes": {
3
- "flake-parts": {
4
- "inputs": {
5
- "nixpkgs-lib": "nixpkgs-lib"
6
- },
7
- "locked": {
8
- "lastModified": 1762810396,
9
- "narHash": "sha256-dxFVgQPG+R72dkhXTtqUm7KpxElw3u6E+YlQ2WaDgt8=",
10
- "owner": "hercules-ci",
11
- "repo": "flake-parts",
12
- "rev": "0bdadb1b265fb4143a75bd1ec7d8c915898a9923",
13
- "type": "github"
14
- },
15
- "original": {
16
- "owner": "hercules-ci",
17
- "repo": "flake-parts",
18
- "type": "github"
19
- }
20
- },
21
- "nix_filter": {
22
- "locked": {
23
- "lastModified": 1757882181,
24
- "narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
25
- "owner": "numtide",
26
- "repo": "nix-filter",
27
- "rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
28
- "type": "github"
29
- },
30
- "original": {
31
- "owner": "numtide",
32
- "repo": "nix-filter",
33
- "type": "github"
34
- }
35
- },
36
- "nixpkgs": {
37
- "locked": {
38
- "lastModified": 1758690382,
39
- "narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
40
- "owner": "nixos",
41
- "repo": "nixpkgs",
42
- "rev": "e643668fd71b949c53f8626614b21ff71a07379d",
43
- "type": "github"
44
- },
45
- "original": {
46
- "owner": "nixos",
47
- "ref": "nixos-unstable",
48
- "repo": "nixpkgs",
49
- "type": "github"
50
- }
51
- },
52
- "nixpkgs-lib": {
53
- "locked": {
54
- "lastModified": 1761765539,
55
- "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=",
56
- "owner": "nix-community",
57
- "repo": "nixpkgs.lib",
58
- "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc",
59
- "type": "github"
60
- },
61
- "original": {
62
- "owner": "nix-community",
63
- "repo": "nixpkgs.lib",
64
- "type": "github"
65
- }
66
- },
67
- "nixpkgs_2": {
68
- "locked": {
69
- "lastModified": 1758690382,
70
- "narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
71
- "owner": "nixos",
72
- "repo": "nixpkgs",
73
- "rev": "e643668fd71b949c53f8626614b21ff71a07379d",
74
- "type": "github"
75
- },
76
- "original": {
77
- "owner": "nixos",
78
- "ref": "nixos-unstable",
79
- "repo": "nixpkgs",
80
- "type": "github"
81
- }
82
- },
83
- "nixpkgs_3": {
84
- "locked": {
85
- "lastModified": 1761373498,
86
- "narHash": "sha256-Q/uhWNvd7V7k1H1ZPMy/vkx3F8C13ZcdrKjO7Jv7v0c=",
87
- "owner": "nixos",
88
- "repo": "nixpkgs",
89
- "rev": "6a08e6bb4e46ff7fcbb53d409b253f6bad8a28ce",
90
- "type": "github"
91
- },
92
- "original": {
93
- "owner": "nixos",
94
- "ref": "nixos-unstable",
95
- "repo": "nixpkgs",
96
- "type": "github"
97
- }
98
- },
99
- "nixpkgs_flake": {
100
- "locked": {
101
- "lastModified": 1778253243,
102
- "narHash": "sha256-KkH2QvU6iRLDTGGEwH5jnMqHWj4zMTuHxi1S1zdr01w=",
103
- "owner": "nixos",
104
- "repo": "nixpkgs",
105
- "rev": "4fcda7103530b23c5ca44286344912082aad4220",
106
- "type": "github"
107
- },
108
- "original": {
109
- "owner": "nixos",
110
- "repo": "nixpkgs",
111
- "type": "github"
112
- }
113
- },
114
- "pyproject-build-systems": {
115
- "inputs": {
116
- "nixpkgs": "nixpkgs",
117
- "pyproject-nix": "pyproject-nix",
118
- "uv2nix": "uv2nix"
119
- },
120
- "locked": {
121
- "lastModified": 1761781027,
122
- "narHash": "sha256-YDvxPAm2WnxrznRqWwHLjryBGG5Ey1ATEJXrON+TWt8=",
123
- "owner": "pyproject-nix",
124
- "repo": "build-system-pkgs",
125
- "rev": "795a980d25301e5133eca37adae37283ec3c8e66",
126
- "type": "github"
127
- },
128
- "original": {
129
- "owner": "pyproject-nix",
130
- "repo": "build-system-pkgs",
131
- "type": "github"
132
- }
133
- },
134
- "pyproject-nix": {
135
- "inputs": {
136
- "nixpkgs": [
137
- "python_builder",
138
- "pyproject-build-systems",
139
- "nixpkgs"
140
- ]
141
- },
142
- "locked": {
143
- "lastModified": 1758265079,
144
- "narHash": "sha256-amLaLNwKSZPShQHzfgmc/9o76dU8xzN0743dWgvYlr8=",
145
- "owner": "nix-community",
146
- "repo": "pyproject.nix",
147
- "rev": "02e9418fd4af638447dca4b17b1280da95527fc9",
148
- "type": "github"
149
- },
150
- "original": {
151
- "owner": "nix-community",
152
- "repo": "pyproject.nix",
153
- "type": "github"
154
- }
155
- },
156
- "pyproject-nix_2": {
157
- "inputs": {
158
- "nixpkgs": "nixpkgs_2"
159
- },
160
- "locked": {
161
- "lastModified": 1762427963,
162
- "narHash": "sha256-CkPlAbIQ87wmjy5qHibfzk4DmMGBNqFer+lLfXjpP5M=",
163
- "owner": "pyproject-nix",
164
- "repo": "pyproject.nix",
165
- "rev": "4540ea004e04fcd12dd2738d51383d10f956f7b9",
166
- "type": "github"
167
- },
168
- "original": {
169
- "owner": "pyproject-nix",
170
- "repo": "pyproject.nix",
171
- "type": "github"
172
- }
173
- },
174
- "pyproject-nix_3": {
175
- "inputs": {
176
- "nixpkgs": [
177
- "python_builder",
178
- "uv2nix",
179
- "nixpkgs"
180
- ]
181
- },
182
- "locked": {
183
- "lastModified": 1760402624,
184
- "narHash": "sha256-jF6UKLs2uGc2rtved8Vrt58oTWjTQoAssuYs/0578Z4=",
185
- "owner": "pyproject-nix",
186
- "repo": "pyproject.nix",
187
- "rev": "84c4ea102127c77058ea1ed7be7300261fafc7d2",
188
- "type": "github"
189
- },
190
- "original": {
191
- "owner": "pyproject-nix",
192
- "repo": "pyproject.nix",
193
- "type": "github"
194
- }
195
- },
196
- "python_builder": {
197
- "inputs": {
198
- "nix_filter": "nix_filter",
199
- "nixpkgs_flake": "nixpkgs_flake",
200
- "pyproject-build-systems": "pyproject-build-systems",
201
- "pyproject-nix": "pyproject-nix_2",
202
- "shell-helpers": "shell-helpers",
203
- "uv2nix": "uv2nix_2"
204
- },
205
- "locked": {
206
- "dir": "common/utils/python-builder",
207
- "lastModified": 1787078362,
208
- "narHash": "sha256-HYL7Rn6SuNjYxkt/PwFLksZjsauCiY+ENbOWryLsIlw=",
209
- "ref": "refs/heads/trunk",
210
- "rev": "09fd25a13309eb5e3b5b2a061bb340df3ae5d281",
211
- "shallow": true,
212
- "type": "git",
213
- "url": "ssh://git@gitlab.com/fluidattacks/universe"
214
- },
215
- "original": {
216
- "dir": "common/utils/python-builder",
217
- "rev": "09fd25a13309eb5e3b5b2a061bb340df3ae5d281",
218
- "shallow": true,
219
- "type": "git",
220
- "url": "ssh://git@gitlab.com/fluidattacks/universe"
221
- }
222
- },
223
- "root": {
224
- "inputs": {
225
- "python_builder": "python_builder"
226
- }
227
- },
228
- "shell-helpers": {
229
- "inputs": {
230
- "flake-parts": "flake-parts",
231
- "nixpkgs": [
232
- "python_builder",
233
- "nixpkgs_flake"
234
- ]
235
- },
236
- "locked": {
237
- "dir": "common/utils/shell-helpers",
238
- "lastModified": 1762806066,
239
- "narHash": "sha256-Wz99Fl7SpPU1NkTHJ36XOo6DjOEAMXCw3ThRABOS2Hs=",
240
- "ref": "refs/heads/trunk",
241
- "rev": "d73cfdc7ae9e7f547e22a4481d4c2ccce38b26a8",
242
- "shallow": true,
243
- "type": "git",
244
- "url": "ssh://git@gitlab.com/fluidattacks/universe"
245
- },
246
- "original": {
247
- "dir": "common/utils/shell-helpers",
248
- "rev": "d73cfdc7ae9e7f547e22a4481d4c2ccce38b26a8",
249
- "shallow": true,
250
- "type": "git",
251
- "url": "ssh://git@gitlab.com/fluidattacks/universe"
252
- }
253
- },
254
- "uv2nix": {
255
- "inputs": {
256
- "nixpkgs": [
257
- "python_builder",
258
- "pyproject-build-systems",
259
- "nixpkgs"
260
- ],
261
- "pyproject-nix": [
262
- "python_builder",
263
- "pyproject-build-systems",
264
- "pyproject-nix"
265
- ]
266
- },
267
- "locked": {
268
- "lastModified": 1758933732,
269
- "narHash": "sha256-HAmm1GBS1myZCFuog0DC2ZLaynvZtiUI2Crmo+cdQI0=",
270
- "owner": "pyproject-nix",
271
- "repo": "uv2nix",
272
- "rev": "273ce18f913d8559e0d04f820d724308966d7c4d",
273
- "type": "github"
274
- },
275
- "original": {
276
- "owner": "pyproject-nix",
277
- "repo": "uv2nix",
278
- "type": "github"
279
- }
280
- },
281
- "uv2nix_2": {
282
- "inputs": {
283
- "nixpkgs": "nixpkgs_3",
284
- "pyproject-nix": "pyproject-nix_3"
285
- },
286
- "locked": {
287
- "lastModified": 1761872265,
288
- "narHash": "sha256-i25GRgp2vUOebY70L3NTAgkd+Pr1hnn5xM3qHxH0ONU=",
289
- "owner": "pyproject-nix",
290
- "repo": "uv2nix",
291
- "rev": "74dfb62871be152ad3673b143b0cc56105a4f3c5",
292
- "type": "github"
293
- },
294
- "original": {
295
- "owner": "pyproject-nix",
296
- "repo": "uv2nix",
297
- "type": "github"
298
- }
299
- }
300
- },
301
- "root": "root",
302
- "version": 7
303
- }
@@ -1,29 +0,0 @@
1
- {
2
- description = "Fluid Attacks Core logging package.";
3
-
4
- inputs = {
5
- python_builder = {
6
- url = "git+ssh://git@gitlab.com/fluidattacks/universe?shallow=1&rev=09fd25a13309eb5e3b5b2a061bb340df3ae5d281&dir=common/utils/python-builder";
7
- };
8
- };
9
-
10
- outputs =
11
- { self, ... }@inputs:
12
- let
13
- build_args =
14
- {
15
- system,
16
- python_version,
17
- nixpkgs,
18
- builders,
19
- scripts,
20
- }:
21
- import ./build {
22
- src = import ./build/filter.nix nixpkgs.nix-filter self;
23
- workspace_root = ./.;
24
- };
25
- in
26
- {
27
- packages = inputs.python_builder.outputs.build build_args;
28
- };
29
- }