python-traceprint 0.1.2__tar.gz → 0.1.3__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 (93) hide show
  1. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.gitignore +0 -1
  2. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/implementation/deliverables.yaml +5 -5
  3. python_traceprint-0.1.3/.macrostates/implementation/release.yaml +4 -0
  4. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/001_project.md +65 -0
  5. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/002_api.md +180 -0
  6. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/003_formatting.md +109 -0
  7. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/004_interception.md +115 -0
  8. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/005_contexts.md +126 -0
  9. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/006_logging.md +115 -0
  10. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/007_release.md +184 -0
  11. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/008_acceptance.md +224 -0
  12. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/README.md +73 -0
  13. python_traceprint-0.1.3/.macrostates/specs/900_traceprint/annex_observed-compatibility.md +114 -0
  14. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/900_traceprint/package.yaml +2 -2
  15. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/composition.yaml +2 -2
  16. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/main.md +8 -5
  17. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/CHANGELOG.md +11 -1
  18. python_traceprint-0.1.3/CONTRIBUTING.md +84 -0
  19. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/PKG-INFO +8 -3
  20. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/README.md +7 -2
  21. python_traceprint-0.1.3/docs/releasing.rst +69 -0
  22. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/docs/usage.rst +4 -0
  23. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/pyproject.toml +2 -0
  24. python_traceprint-0.1.2/.macrostates/implementation/release.yaml +0 -4
  25. python_traceprint-0.1.2/.macrostates/specs/900_traceprint/README.md +0 -32
  26. python_traceprint-0.1.2/.macrostates/specs/900_traceprint/api.md +0 -29
  27. python_traceprint-0.1.2/.macrostates/specs/900_traceprint/interception.md +0 -24
  28. python_traceprint-0.1.2/.macrostates/specs/900_traceprint/project.md +0 -24
  29. python_traceprint-0.1.2/.macrostates/specs/900_traceprint/release.md +0 -76
  30. python_traceprint-0.1.2/CONTRIBUTING.md +0 -50
  31. python_traceprint-0.1.2/docs/releasing.rst +0 -56
  32. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/001_concepts.md +0 -0
  33. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/002_project-composition.md +0 -0
  34. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/003_progressive-disclosure.md +0 -0
  35. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/LICENSE +0 -0
  36. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/README.md +0 -0
  37. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/annex_cli.md +0 -0
  38. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/annex_official-packages.md +0 -0
  39. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/annex_package-lock.md +0 -0
  40. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/annex_project-kick-off.md +0 -0
  41. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/package.yaml +0 -0
  42. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/resources/project-kickoff.md +0 -0
  43. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/001_concepts.md +0 -0
  44. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/002_artifacts.md +0 -0
  45. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/003_execution.md +0 -0
  46. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/LICENSE +0 -0
  47. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/README.md +0 -0
  48. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/annex_conflicts.md +0 -0
  49. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/annex_exceptions.md +0 -0
  50. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/annex_project-state.md +0 -0
  51. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/annex_versioning.md +0 -0
  52. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/package.yaml +0 -0
  53. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/001_conventions.md +0 -0
  54. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/002_contents.md +0 -0
  55. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/003_documentation.md +0 -0
  56. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/004_technology.md +0 -0
  57. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/LICENSE +0 -0
  58. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/README.md +0 -0
  59. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/package.yaml +0 -0
  60. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/020_python-1/001_python.md +0 -0
  61. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/020_python-1/LICENSE +0 -0
  62. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/020_python-1/README.md +0 -0
  63. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/020_python-1/package.yaml +0 -0
  64. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/001_project-layout.md +0 -0
  65. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/002_dependency-management.md +0 -0
  66. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/003_library-api.md +0 -0
  67. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/004_formatting.md +0 -0
  68. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/005_linting.md +0 -0
  69. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/006_type-checking.md +0 -0
  70. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/007_testing.md +0 -0
  71. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/008_packaging.md +0 -0
  72. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/009_documentation.md +0 -0
  73. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/010_examples.md +0 -0
  74. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/LICENSE +0 -0
  75. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/README.md +0 -0
  76. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/package.yaml +0 -0
  77. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/composition.lock.yaml +0 -0
  78. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/LICENSE +0 -0
  79. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/docs/api.rst +0 -0
  80. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/docs/conf.py +0 -0
  81. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/docs/index.rst +0 -0
  82. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/examples/main_loop.py +0 -0
  83. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/examples/sklearn_style_transformer.py +0 -0
  84. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/hatch_build.py +0 -0
  85. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/src/traceprint/__init__.py +0 -0
  86. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/src/traceprint/colors.py +0 -0
  87. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/src/traceprint/py.typed +0 -0
  88. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/src/traceprint/tracer.py +0 -0
  89. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/integration/test_logging.py +0 -0
  90. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/integration/test_release.py +0 -0
  91. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/packaging/check_release.py +0 -0
  92. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/packaging/check_wheel.py +0 -0
  93. {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/unit/test_tracer.py +0 -0
@@ -6,7 +6,6 @@ __pycache__/
6
6
  .ruff_cache/
7
7
  .mypy_cache/
8
8
  .pyright/
9
- uv.lock
10
9
 
11
10
  # Build outputs
12
11
  dist/
@@ -3,10 +3,10 @@ deliverables:
3
3
  traceprint:
4
4
  strategy: independent
5
5
  version_scheme: pep440
6
- version: 0.1.2
6
+ version: 0.1.3
7
7
  status: released
8
- specification: specs-1.0.0
9
- implementation: imp-1.0.0
10
- release_date: '2026-10-14'
8
+ specification: specs-1.1.0
9
+ implementation: imp-1.1.0
10
+ release_date: '2026-10-25'
11
11
  source:
12
- tag: v0.1.2
12
+ tag: v0.1.3
@@ -0,0 +1,4 @@
1
+ schema_version: 2
2
+ version: imp-1.1.0
3
+ specification: specs-1.1.0
4
+ release_date: '2026-10-25'
@@ -0,0 +1,65 @@
1
+ # Project
2
+
3
+ Traceprint exists to make ad hoc development output easier to follow without
4
+ requiring a full observability stack.
5
+
6
+ ## Responsibilities
7
+
8
+ Traceprint should provide:
9
+
10
+ - formatted text output with timestamp, elapsed time, cumulative time, labels,
11
+ and nesting depth;
12
+ - optional ANSI colors for terminal use;
13
+ - manual spans for nested operations;
14
+ - a decorator for function-level tracing;
15
+ - Python logging handlers that route logging records through tracer formatting.
16
+
17
+ ## Non-goals
18
+
19
+ Traceprint is not a replacement for structured logging, distributed tracing,
20
+ metrics, OpenTelemetry, or production observability infrastructure.
21
+
22
+ Traceprint should not include unrelated project, pipeline, placeholder, or
23
+ filesystem utility functions.
24
+
25
+ ## Identity and supported use
26
+
27
+ - Distribution: `python-traceprint`; import package: `traceprint`; license: MIT.
28
+ - Python 3.12 or later; no runtime dependencies outside the Python standard library.
29
+ - Public API maturity: alpha. Repository specification versions and library
30
+ versions are independent; a specification 1.x does not declare the library stable.
31
+ - Supported use is ordinary text streams, development scripts, short-lived CLIs
32
+ and Python logging. Callers coordinate shared state and own stream/file cleanup.
33
+
34
+ The library is a synchronous formatter and a partial text-stream adapter. It is
35
+ not a general `TextIOBase` implementation: only `write`, `flush` and the explicitly
36
+ specified tracing operations are required. Do not infer `encoding`, `fileno`,
37
+ `isatty`, binary writes or a `close` operation on `Tracer` from stream interception.
38
+ TTY detection is delegated to the underlying sink during construction/configuration.
39
+
40
+ ## Product structure
41
+
42
+ The importable package lives under `src/traceprint/`. Its root exports the API
43
+ in [API](002_api.md), `colors.py` supplies color constants/conversion, `tracer.py`
44
+ supplies tracer/context/logging behavior, and an empty `py.typed` marks typing
45
+ support. Private implementation organization may vary while these supported
46
+ interfaces and outputs remain equivalent.
47
+
48
+ The repository also provides tests, two runnable examples, `hatch_build.py`,
49
+ `pyproject.toml`, MIT licensing, README/contribution/changelog material and Sphinx
50
+ API/usage/release documentation. The latter is a product maintained according to
51
+ this package and the Python-library documentation rules. Build and release
52
+ requirements are owned by [Library versions and releases](007_release.md).
53
+
54
+ ## Shared-state boundaries
55
+
56
+ A tracer owns its label stack, depth, timing, enabled/locked flags, sink,
57
+ interception captures, attached mirrors and current line state. None of these
58
+ is thread-local, task-local or isolated by `contextvars`. Intercepted stdout and
59
+ stderr share one sink and one context/line state.
60
+
61
+ Do not promise concurrent-context isolation, asynchronous coroutine/generator
62
+ execution tracing, overlapping tracer installation management or transactional
63
+ configuration. Keep enable/lock settings stable while a context is active and
64
+ balance manual push/pop operations. Unsupported mutation observations belong to
65
+ the [compatibility annex](annex_observed-compatibility.md).
@@ -0,0 +1,180 @@
1
+ # API
2
+
3
+ Traceprint's public API is small and explicit. Signatures below specify argument
4
+ positions, keyword-only parameters and defaults; ordinary Python exceptions are
5
+ used rather than a separate error type. Values must satisfy the annotated text,
6
+ stream and logging contracts; constructors do not perform general type validation.
7
+
8
+ ## Public imports
9
+
10
+ The root exports exactly these names through `__all__`, in this order:
11
+
12
+ ```text
13
+ DARK_GREY, DEFAULT_COLORS, RESET, LoggerMixin, Tracer,
14
+ TracerFileHandler, TracerHandler, trace, tracer
15
+ ```
16
+
17
+ The color values are owned by [Formatting](003_formatting.md#color-values).
18
+ `Span` and the logger proxy are returned objects, not package-root exports.
19
+ There is no required `traceprint.__version__`; distribution version lookup uses
20
+ `importlib.metadata.version("python-traceprint")`.
21
+
22
+ ## Tracer construction
23
+
24
+ All constructor arguments are keyword-only:
25
+
26
+ ```python
27
+ Tracer(
28
+ *,
29
+ use_colors: bool | None = None,
30
+ use_tabulation: bool = False,
31
+ color_scheme: list[str] | None = None,
32
+ metacolor: str | None = DARK_GREY,
33
+ print_transitions: bool = False,
34
+ filter_levels: range | Iterable[int] | Callable[[int], bool] | None = None,
35
+ start_enabled: bool = True,
36
+ output_format: str = DEFAULT_OUTPUT_FORMAT,
37
+ timestamp_format: str = "%Y-%m-%d %H:%M:%S",
38
+ stream: TextIO | None = None,
39
+ error_stream: TextIO | None = None,
40
+ intercept_stdout: bool = False,
41
+ intercept_stderr: bool = False,
42
+ )
43
+ ```
44
+
45
+ `DEFAULT_OUTPUT_FORMAT` denotes the exact literal specified in
46
+ [Formatting](003_formatting.md#format-fields), not a new root export.
47
+
48
+ | Option | Meaning |
49
+ | --- | --- |
50
+ | `use_colors` | `None` detects the sink's TTY status; explicit booleans select color output. |
51
+ | `use_tabulation` | Enable four spaces per depth, with placement independent of color output. |
52
+ | `color_scheme` | Ordered ANSI foreground strings; `None` or an empty list selects `DEFAULT_COLORS`. |
53
+ | `metacolor` | Color metadata separately; a falsey value selects whole-line depth coloring. |
54
+ | `print_transitions` | Emit the context entry/exit messages specified in [Contexts](005_contexts.md). |
55
+ | `filter_levels` | Allowed depths or a predicate returning truthy for allowed depths. |
56
+ | `start_enabled` | Initial formatting/depth-tracking state. |
57
+ | `output_format` | Python format string containing the supported fields. |
58
+ | `timestamp_format` | UTC `strftime` format. |
59
+ | `stream` | Output sink, defaulting to current stdout; preserve whether it was explicitly supplied. |
60
+ | `error_stream` | Accepted compatibility parameter; it does not create a separate stderr output route. |
61
+ | `intercept_stdout`, `intercept_stderr` | Explicit construction-time installation requests, both false by default. |
62
+
63
+ Construction initializes `depth = 0`, `labels = []`, an unlocked state, no active
64
+ interception captures, no attached file mirrors and a fresh line state. Read the
65
+ performance clock once and use that value as both the start and last-format time.
66
+ Resolve colors before optional installation; installation does not redetect TTY
67
+ status or reset timing, labels or line state.
68
+
69
+ Use the supplied stream when present in ordinary use, otherwise current stdout.
70
+ The alpha's falsey-stream selection nuance is documented in the
71
+ [compatibility annex](annex_observed-compatibility.md#stream-and-installation-edges).
72
+ A supplied nonempty palette and the default palette are shared by reference,
73
+ not copied. Keep a palette nonempty after selection.
74
+
75
+ ## Tracer state and operations
76
+
77
+ Readable/mutable public configuration attributes are `use_colors`,
78
+ `use_tabulation`, `color_scheme`, `metacolor`, `print_transitions`, `filter_levels`,
79
+ `output_format` and `timestamp_format`. `depth` and `labels` expose current context;
80
+ prefer balanced context operations to direct mutation. `enabled` is read-only.
81
+ Depth and labels are separate mutable values; assigning one does not automatically
82
+ reconcile the other. Configuration changes do not reset clocks or line state.
83
+
84
+ | Signature | Result and owner |
85
+ | --- | --- |
86
+ | `enabled -> bool` | Current enabled flag. |
87
+ | `install(*, stdout: bool = True, stderr: bool = False) -> None` | Explicit stream capture/replacement; [Interception](004_interception.md#installation). |
88
+ | `uninstall() -> None` | Restore captured streams and clear installation flags. |
89
+ | `enable() -> None`, `disable() -> None` | Set formatting/depth-tracking state without resetting context or clocks. |
90
+ | `lock() -> None`, `unlock() -> None` | Set depth-mutation lock without changing existing context. |
91
+ | `configure(**kwargs) -> None` | Ordered configuration updates described below. |
92
+ | `format(message: str) -> str` | Format without output; advances timing; [Formatting](003_formatting.md). |
93
+ | `write(data: str) -> int` | Immediate fragment output; returns input length; [Interception](004_interception.md#writing-fragments). |
94
+ | `flush() -> None` | Flush the sink and each mirror without ending a line. |
95
+ | `push(label: str | None = None) -> None` | Manual depth entry; [Contexts](005_contexts.md#manual-push-and-pop). |
96
+ | `pop(label: str | None = None) -> None` | Manual depth exit, safe at depth zero. |
97
+ | `span(label: str) -> Span` | Return a synchronous context manager; [Contexts](005_contexts.md#spans). |
98
+ | `add_file_handler(handler: TracerFileHandler) -> None` | Append a mirror in attachment order. |
99
+ | `remove_file_handler(handler: TracerFileHandler) -> None` | Remove its first matching attachment; missing attachment is a no-op. |
100
+
101
+ There is no return-value aggregation from sink/mirror writes and no implicit
102
+ closing of a sink or attached handlers. See [Logging](006_logging.md) for handler
103
+ ownership and failure behavior.
104
+
105
+ ## Configuration order
106
+
107
+ Apply one `configure` call in this order:
108
+
109
+ 1. If either interception key is supplied, derive both desired flags. Missing
110
+ flags retain their current installed state; supplied values use `bool(value)`.
111
+ Uninstall the current captures, then install the derived combination.
112
+ 2. If `use_colors` is supplied, `None` redetects the current sink's TTY status;
113
+ any other value uses `bool(value)`.
114
+ 3. If `color_scheme` is supplied, select the provided sequence or default palette
115
+ using its truthiness.
116
+ 4. Assign remaining keys in caller order when the tracer already has that
117
+ attribute. Otherwise raise `AttributeError("Tracer has no attribute NAME")`.
118
+
119
+ This is not a constructor replay. Names such as `stream`, `error_stream` and
120
+ `start_enabled` are not public configuration attributes; use lifecycle methods
121
+ for enabled state and construction for a sink. A read-only attribute assignment
122
+ raises normal `AttributeError`. Earlier updates are retained if a later one
123
+ fails. The permissive alpha handling of private/method attributes is an
124
+ [observation](annex_observed-compatibility.md#configuration-and-mutable-state),
125
+ not an additional supported configuration surface.
126
+
127
+ ## Global tracer
128
+
129
+ Create one global convenience `tracer` when the runtime module first imports,
130
+ using ordinary constructor defaults. Root imports refer to this same object.
131
+ Import alone must not replace stdout/stderr, open logging files, attach root
132
+ logging handlers or configure the root logger. Environment labels are the only
133
+ specified import-time configuration beyond constructor defaults.
134
+
135
+ ## Environment variables
136
+
137
+ Read `TRACEPRINT_INITIAL_LABELS` once at import time, defaulting to the empty
138
+ string. Strip the complete value; split it on `/`; strip each component and
139
+ discard empty components. Push each remaining component in order on the global
140
+ tracer. This sets both labels and depth; it does not install the tracer.
141
+
142
+ For example, `" / job // load / "` produces labels `["job", "load"]` and depth 2.
143
+ Explicit new tracer instances do not read this variable. Later environment edits
144
+ do not mutate the existing global instance. There are no other Traceprint-owned
145
+ environment variables.
146
+
147
+ ## Context and logging interfaces
148
+
149
+ ```python
150
+ trace(_func: Callable[..., Any] | None = None, *,
151
+ label: str | None = None, force: bool = False,
152
+ tracer_instance: Tracer | None = None) -> Callable[..., Any]
153
+
154
+ TracerHandler(level: int = logging.NOTSET, *,
155
+ tracer_instance: Tracer | None = None)
156
+
157
+ TracerFileHandler(filename: str | os.PathLike[str], mode: str = "a",
158
+ level: int = logging.NOTSET, encoding: str = "utf-8", *,
159
+ tracer_instance: Tracer | None = None)
160
+
161
+ LoggerMixin(*, verbose: bool = True, logging_level: int = logging.INFO,
162
+ logging_passthrough: bool = False, lazy_init: bool = False)
163
+ ```
164
+
165
+ `trace` supports bare and parameterized decorator forms. Its target selection,
166
+ transitions, result/exception behavior and `force` boundary are owned by
167
+ [Contexts](005_contexts.md#function-decorator).
168
+
169
+ Both handlers subclass `logging.Handler` and support standard levels, formatters,
170
+ filters and `handle` behavior. They implement `emit(record: logging.LogRecord)
171
+ returning `None`. File handlers additionally expose `write(data: str) -> None`,
172
+ `flush() -> None` and `close() -> None`, and the filename/mode/encoding/tracer/stream
173
+ attributes described in [Logging](006_logging.md#file-handler).
174
+
175
+ `LoggerMixin.logger` returns a standard logger; `LoggerMixin.log` returns the same
176
+ instance's verbosity-gated proxy. The proxy supports `log(level, msg, *args,
177
+ **kwargs)`, `debug`, `info`, `warning`, `error` and `critical`, all returning `None`.
178
+ No proxy `exception` or `warn` method is required. Use `.logger` for the full
179
+ standard logging interface. Initialization and shared-logger effects are owned
180
+ by [Logging](006_logging.md#logger-mixin).
@@ -0,0 +1,109 @@
1
+ # Formatting and colors
2
+
3
+ ## Format fields
4
+
5
+ The exact default output format is:
6
+
7
+ ```text
8
+ [{timestamp}][{cumulative}][{elapsed}][{label_full}] {original_message}
9
+ ```
10
+
11
+ Use Python string formatting with these named values, all strings:
12
+
13
+ | Field | Value |
14
+ | --- | --- |
15
+ | `timestamp` | Current aware UTC datetime formatted with `timestamp_format`. |
16
+ | `cumulative` | Performance-clock seconds since construction, `f"{seconds:.3f}s"`. |
17
+ | `elapsed` | Seconds since the previous `format` call, `f"+{seconds:.3f}s"`. |
18
+ | `label` | The final label, or empty; append `/>` for regular labeled messages. |
19
+ | `label_full` | The slash-joined label stack, or empty; same regular-message suffix. |
20
+ | `original_message` | The input string without further stripping or interpolation. |
21
+
22
+ At each `format` call, read the performance clock, compute elapsed from the last
23
+ format time and cumulative from the start time, then set last-format time to the
24
+ new value. Obtain the UTC timestamp separately. Formatting advances timing even
25
+ when called directly, when the tracer is disabled, or when a later formatting
26
+ operation raises. Continuation fragments, blank writes, filtered writes and
27
+ flushes do not call `format` and do not advance timing.
28
+
29
+ Clocks measure tracer age and gaps between formatted messages, not exclusive
30
+ span duration. Timing text has three fractional digits and an `s` suffix, with
31
+ an explicit `+` before elapsed text. The default timestamp uses
32
+ `%Y-%m-%d %H:%M:%S`; `%z` yields `+0000`.
33
+
34
+ Compute the raw full label by joining labels with `/` and the short label from
35
+ the final entry. If the full label is nonempty and the message does not start
36
+ with literal `>` or `<`, append `/>` to both values. Otherwise keep them raw.
37
+ This rule examines the first character without stripping leading whitespace;
38
+ it is not a special transition object. Label strings are not escaped.
39
+
40
+ Custom formats may select, omit, repeat or reorder fields. Preserve message text
41
+ when the format includes `original_message`; do not force it into a format that
42
+ omits it. Unknown fields, incompatible format syntax and invalid timestamp formats
43
+ use the standard formatting/datetime exception behavior; there is no silent fallback.
44
+ For incremental writes, metadata/custom suffixes are applied to the first
45
+ nonblank fragment only; see [Writes](004_interception.md#writing-fragments).
46
+
47
+ ## Color values
48
+
49
+ `RESET` is `"\x1b[0m"`; `DARK_GREY` is `"\x1b[90m"`. `DEFAULT_COLORS` is an
50
+ ordered mutable list of truecolor foreground sequences generated from:
51
+
52
+ | Depth modulo 8 | RGB hex | Red, green, blue |
53
+ | --- | --- | --- |
54
+ | 0 | `#52C97F` | 82, 201, 127 |
55
+ | 1 | `#68BCCD` | 104, 188, 205 |
56
+ | 2 | `#7D7FD2` | 125, 127, 210 |
57
+ | 3 | `#C891D8` | 200, 145, 216 |
58
+ | 4 | `#DDA5BE` | 221, 165, 190 |
59
+ | 5 | `#E4C9B9` | 228, 201, 185 |
60
+ | 6 | `#E5EACC` | 229, 234, 204 |
61
+ | 7 | `#E0F2DF` | 224, 242, 223 |
62
+
63
+ The sequence for channels `r`, `g`, `b` is exactly
64
+ `"\x1b[38;2;{r};{g};{b}m"`, with decimal channel values. Select the current
65
+ line color at index `depth % len(color_scheme)`; custom palettes cycle by their
66
+ own length. No terminal capability negotiation beyond TTY color detection is
67
+ required. A caller can explicitly request ANSI output to a non-TTY sink.
68
+
69
+ The supporting module `traceprint.colors` also exposes
70
+ `hex_to_ansi(hexstr: str) -> str`, although it is not a root export. For normal
71
+ hex input, remove a single leading `#`, require six remaining characters, parse
72
+ each pair using base-16 integer conversion and generate the sequence above.
73
+ Bad length or pair-conversion failure raises
74
+ `ValueError(f"Expected #RRGGBB color, got {hexstr!r}")`; conversion failure chains
75
+ the original `ValueError`. No additional clamping is performed. Malformed inputs
76
+ accepted by Python's integer parser are discussed in the
77
+ [compatibility annex](annex_observed-compatibility.md#format-and-color-edges).
78
+
79
+ ## Color and indentation composition
80
+
81
+ After formatting fields into `text`, compute `indent` as four literal spaces
82
+ times depth when tabulation is enabled, otherwise the empty string. Indentation
83
+ must have the same placement whether colors are enabled or disabled.
84
+
85
+ 1. If `metacolor` is falsey, prepend `indent` to the entire `text`. When colors
86
+ are enabled, surround that complete result with the selected line color and
87
+ `RESET`; otherwise return it directly.
88
+ 2. If `metacolor` is truthy, find the first occurrence of the input message in
89
+ the formatted `text`. Split into `meta_part` before that occurrence and
90
+ `message_part` from that occurrence through the end. With colors disabled,
91
+ return `meta_part + indent + message_part`. With colors enabled, return:
92
+
93
+ ```text
94
+ metacolor + meta_part + RESET + line_color + indent + message_part + RESET
95
+ ```
96
+
97
+ 3. If the message does not occur in `text`, prepend `indent` to the whole text.
98
+ With colors enabled use `metacolor + indent + text + RESET`; with colors
99
+ disabled use `indent + text`.
100
+
101
+ This describes string concatenation, not literal names in emitted output. Do not
102
+ strip caller-supplied ANSI from console output. An empty `meta_part` still has
103
+ its metadata color/reset framing when colors are enabled. `format` adds no
104
+ newline of its own and does not update the fragment line state.
105
+
106
+ The first-substring rule is a compatibility detail of the existing formatter;
107
+ it is not a promise of placeholder-aware parsing. See the
108
+ [compatibility annex](annex_observed-compatibility.md#format-and-color-edges)
109
+ when a custom format repeats a message or includes it in metadata.
@@ -0,0 +1,115 @@
1
+ # Interception
2
+
3
+ Traceprint may replace process streams only when the caller explicitly requests
4
+ that behavior. The same object also works as `print(..., file=target)` or through
5
+ direct `write` calls without installation. Writes have immediate effects; no
6
+ complete-line buffering is permitted merely to join `print` arguments.
7
+
8
+ ## Installation
9
+
10
+ `install(*, stdout=True, stderr=False)` handles stdout first, then stderr:
11
+
12
+ - For a requested stream not already intercepted by this tracer, capture its
13
+ current process object and replace it with the tracer. Mark that stream installed.
14
+ - If stdout is newly installed and no explicit sink was supplied at construction,
15
+ choose the captured stdout as the sink.
16
+ - If stderr is newly installed, this call's `stdout` argument is false and no
17
+ explicit sink was supplied, choose the captured stderr as the sink.
18
+ - Otherwise retain the sink. An explicit constructor sink wins in every case.
19
+
20
+ This makes repeated requests for an already-installed stream idempotent without
21
+ recapturing the tracer itself. A false argument does not uninstall a previously
22
+ installed stream; use `uninstall` or `configure` to remove interception. With
23
+ both streams requested in the same call, both feed the selected stdout sink.
24
+ There is no separate routing through `error_stream`.
25
+
26
+ `uninstall()` restores each captured stdout/stderr object for which this tracer
27
+ has an active installation flag, then clears both flags and captures. Repeating
28
+ uninstall is a no-op. It does not close/reset the sink, clocks, labels, line state
29
+ or attached file handlers. Install/uninstall do not enable a disabled tracer.
30
+
31
+ Callers should use `try/finally` to restore streams. Normal single-tracer cleanup
32
+ must restore object identity, not just an equivalent stream. Overlapping tracer
33
+ ownership and external stream replacements are not automatically coordinated;
34
+ see [compatibility](annex_observed-compatibility.md#stream-and-installation-edges).
35
+
36
+ ## Depth filtering
37
+
38
+ At the point a write chunk or transition is considered, determine whether the
39
+ current depth is allowed:
40
+
41
+ - `None`: allow every depth.
42
+ - `range`: allow exactly its members.
43
+ - Callable: call it with the current depth and allow a truthy result.
44
+ - Other iterable: allow when membership contains the current depth.
45
+
46
+ Suppressing output does not suppress context mutation. A disallowed chunk must
47
+ not reach either the sink or mirrors, call `format`, advance timing or alter
48
+ visible line state. Evaluate the write filter once per chunk, including whitespace
49
+ chunks. Context transition operations may also perform their own precheck.
50
+ Exceptions from predicates/membership propagate during direct tracer operations.
51
+ Iterable membership has ordinary Python semantics, including consumption of
52
+ one-shot iterators; prefer a reusable collection for stable membership.
53
+
54
+ ## Writing fragments
55
+
56
+ `write(data: str)` returns `len(data)` on a successful call, even when all output
57
+ is filtered. It does not return the length of metadata or the underlying sink's
58
+ write result. Preserve message whitespace and original CR/LF endings.
59
+
60
+ When disabled, write `data` directly to the sink, bypassing formatting, filtering
61
+ and mirrors. For a nonempty input, set line state according to whether it ends
62
+ with CR or LF: an ending resets it; otherwise mark an existing line. An empty
63
+ disabled write still calls the sink but leaves line state unchanged.
64
+
65
+ When enabled:
66
+
67
+ 1. Split `data` using `str.splitlines(keepends=True)`. An empty input creates no
68
+ chunks and causes no sink write.
69
+ 2. For each allowed chunk, remove only trailing CR/LF characters into an `ending`
70
+ suffix, leaving `message`. Do not strip other characters from the emitted text.
71
+ 3. If `message.strip()` is nonempty and no line has started, call `format(message)`.
72
+ If a line has started, emit the original message unchanged, or surround it
73
+ with the current depth color and `RESET` when colors are enabled. Do not
74
+ repeat metadata or indentation on a continuation.
75
+ 4. Write that result plus its ending, then mark the line started. For a whitespace-
76
+ only chunk, write the original chunk without formatting or starting a new line.
77
+ 5. Any nonempty CR/LF ending resets line state after that write.
78
+
79
+ Every enabled write reaches the sink first, followed by all mirrors in attachment
80
+ order with the same already-formatted text. Do not catch sink/mirror exceptions
81
+ in the tracer, automatically retry, or roll back effects already written. Line
82
+ state changes after the corresponding successful write operation.
83
+
84
+ Whitespace fragments are immediate too. Leading whitespace supplied separately
85
+ can precede the first metadata prefix; whitespace within a nonblank fragment
86
+ remains part of that formatted message. Blank lines have no metadata. Unicode
87
+ line separators recognized by `splitlines` but not CR/LF remain in their message
88
+ or whitespace chunk and do not reset this CR/LF line state. These unusual cases
89
+ are further illustrated in the [compatibility annex](annex_observed-compatibility.md).
90
+
91
+ Labels, timing and indentation belong to the first nonblank fragment's metadata.
92
+ Later context/configuration changes do not cause a second prefix before a newline.
93
+ Continuation coloring uses the current palette/depth at that write. A custom
94
+ format suffix follows the first fragment, not the eventual complete line.
95
+
96
+ ## Flush and mirroring lifecycle
97
+
98
+ `flush()` flushes the sink, then every attached mirror in attachment order,
99
+ regardless of enabled state. It does not output pending text, add a newline,
100
+ reset line state or advance clocks. Exceptions propagate; later mirrors are not
101
+ flushed after an earlier failure.
102
+
103
+ Attaching a handler appends it without deduplication or opening/closing it.
104
+ Removing a handler removes the first equal attachment if present and otherwise
105
+ does nothing. Attaching the same handler twice yields two mirror writes/flushes;
106
+ ordinary use should attach once. The caller removes and closes owned handlers.
107
+ Handler behavior and ANSI stripping are owned by [Logging](006_logging.md).
108
+
109
+ ## Formatting
110
+
111
+ Formatted output preserves message text when the configured format contains its
112
+ placeholder. Colors are optional and auto-disabled for non-TTY sinks. Exact
113
+ format fields, colors and indentation rules are in [Formatting](003_formatting.md).
114
+ File output strips terminal escape sequences as specified in [Logging](006_logging.md);
115
+ there is no current option to preserve ANSI in files.
@@ -0,0 +1,126 @@
1
+ # Contexts and decorators
2
+
3
+ ## Common context rules
4
+
5
+ Depth starts at zero and labels form a stack in outer-to-inner order. A normal
6
+ entry adds one depth and one label; a normal exit removes one of each. Filtering
7
+ affects output, not this tracking. Locked tracers retain their existing depth and
8
+ labels while still being able to print allowed transitions/messages.
9
+
10
+ Enable/disable and lock/unlock are setters, not counters or context managers.
11
+ They do not reset labels, clocks, mirrors or line state. Disabled manual operations
12
+ and spans do not track depth. Keep these flags stable inside active spans/decorated
13
+ calls and balance manual operations. Behavior outside those conditions is recorded
14
+ in [compatibility observations](annex_observed-compatibility.md#context-mutation-edges).
15
+
16
+ Transition strings below are supplied to `write` with a trailing newline and
17
+ are therefore subject to filtering, current line state and ordinary formatting.
18
+ Transition output is optional; no transitions are printed by default.
19
+
20
+ ## Manual push and pop
21
+
22
+ `push(label=None)`:
23
+
24
+ 1. If disabled, return without any effect.
25
+ 2. If unlocked, increment depth and append the supplied truthy label, otherwise
26
+ append `"level" + str(new_depth)`. Thus `None` and `""` generate `levelN` labels.
27
+ 3. If the supplied label itself is truthy, transitions are enabled and the new
28
+ current depth is allowed, write `"> LABEL\n"`.
29
+
30
+ Generated labels do not trigger a transition when no truthy label was supplied.
31
+ When locked, an explicitly labeled transition can still be written at the existing
32
+ depth; the label stack is unchanged.
33
+
34
+ `pop(label=None)`:
35
+
36
+ 1. If disabled, return without any effect.
37
+ 2. If unlocked and depth is above zero, decrement depth and remove the final
38
+ stored label. At depth zero, do not decrement or remove anything.
39
+ 3. Select the supplied truthy label, otherwise the removed label. If that selected
40
+ value is truthy, transitions are enabled and the resulting depth is allowed,
41
+ write `"< LABEL\n"`.
42
+
43
+ An explicit pop label names the transition, not a stack entry to search/remove.
44
+ At depth zero or while locked, `pop("name")` may still print an exit transition.
45
+ With no selected label, print nothing. Manual push transitions use the entered
46
+ context; manual pop transitions use the parent context after removal.
47
+
48
+ ## Spans
49
+
50
+ `span(label)` constructs a synchronous context manager without changing state.
51
+ The returned object exposes its target as `.tracer` and exact label as `.label`.
52
+ Entering returns that context object, so `with target.span("load") as context`
53
+ binds the span, not the tracer.
54
+
55
+ On entry when enabled:
56
+
57
+ 1. If transitions are enabled and the parent depth is allowed, write `"> LABEL\n"`.
58
+ 2. If unlocked, increment depth and append the exact supplied label, including an
59
+ empty string. Unlike manual `push`, spans do not generate a replacement label.
60
+
61
+ On exit when enabled:
62
+
63
+ 1. If unlocked, decrement depth and remove the final label.
64
+ 2. If transitions are enabled and the resulting parent depth is allowed, write
65
+ `"< LABEL\n"` on ordinary exit, or
66
+ `"< LABEL [failed: EXCEPTION_CLASS_NAME]\n"` when exiting with an exception.
67
+
68
+ Return `None` from exit; do not suppress the body's exception. On normal supported
69
+ use, cleanup restores the prior depth/labels even when the body raises. Disabled
70
+ entry/exit performs no context/transition work; locked entry/exit performs no
71
+ depth mutation. Spans do not install/uninstall streams or flush/close sinks.
72
+
73
+ ## Function decorator
74
+
75
+ Support all these forms, preserving the function's metadata with `functools.wraps`:
76
+
77
+ ```python
78
+ @trace
79
+ def work(): ...
80
+
81
+
82
+ @trace(label="load")
83
+ def load(): ...
84
+
85
+
86
+ @trace(tracer_instance=target)
87
+ def run(): ...
88
+ ```
89
+
90
+ Select the supplied tracer or global `tracer` when the decorator is created;
91
+ do not select a new tracer on each call. The optional first positional function
92
+ supports the bare form; when it is `None`, return a decorator awaiting a function.
93
+ A truthy explicit label wins; otherwise use `function.__name__`.
94
+
95
+ On each wrapped call:
96
+
97
+ 1. If the target is disabled, directly call the function with the original
98
+ arguments and return its result. No transitions or context mutation occur.
99
+ 2. Capture whether the entry depth is filtered. If transitions are enabled and
100
+ either entry depth is allowed or `force=True`, attempt
101
+ `"> LABEL()\n"` at the parent depth.
102
+ 3. If unlocked, increment depth and append the selected label.
103
+ 4. Call the function with unchanged positional/keyword arguments. Return its
104
+ result unchanged; let its exceptions propagate.
105
+ 5. In `finally`, if unlocked, decrement depth and remove the last label. If
106
+ transitions are enabled and either the captured entry depth was allowed or
107
+ `force=True`, attempt `"< LABEL()\n"` at the resulting parent depth.
108
+
109
+ The decorator's exit has no span-style `[failed: ...]` suffix. Capture the entry
110
+ filter decision once for both transition attempts; each actual `write` still
111
+ applies the current depth filter. `force` is not a bypass of write-level filtering.
112
+ That alpha compatibility boundary is discussed in the
113
+ [annex](annex_observed-compatibility.md#transition-filtering).
114
+
115
+ The wrapper traces synchronous function invocation only. For an async function
116
+ or generator, it surrounds creation of the coroutine/generator object, not later
117
+ execution, awaits or iteration. Do not add special async/generator tracing while
118
+ replicating this library.
119
+
120
+ ## Failure boundaries
121
+
122
+ Context tracking is not transactional with output. A transition write can fail
123
+ before body execution or during exit, using the direct stream failure behavior.
124
+ Entry/exit may therefore fail independently of the body. Normal exception cleanup
125
+ guarantees assume a writable sink, balanced context and stable enabled/locked state;
126
+ no recovery for externally corrupted stacks or flag changes is required.