agent-first-data 0.8.0__tar.gz → 0.9.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.
- {agent_first_data-0.8.0/agent_first_data.egg-info → agent_first_data-0.9.0}/PKG-INFO +22 -6
- agent_first_data-0.8.0/PKG-INFO → agent_first_data-0.9.0/README.md +20 -13
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/agent_first_data/__init__.py +18 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/agent_first_data/cli.py +25 -1
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/agent_first_data/format.py +182 -20
- agent_first_data-0.8.0/README.md → agent_first_data-0.9.0/agent_first_data.egg-info/PKG-INFO +29 -4
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/pyproject.toml +2 -2
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/tests/test_cli.py +14 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/tests/test_format.py +90 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/agent_first_data/afdata_logging.py +0 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/agent_first_data.egg-info/SOURCES.txt +0 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/agent_first_data.egg-info/dependency_links.txt +0 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/agent_first_data.egg-info/top_level.txt +0 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/setup.cfg +0 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/tests/test_afdata_logging.py +0 -0
- {agent_first_data-0.8.0 → agent_first_data-0.9.0}/tests/test_no_stderr_policy.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: A naming convention that lets AI agents understand your data without being told what it means.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
|
7
7
|
Requires-Python: >=3.9
|
|
@@ -74,7 +74,7 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
74
74
|
|
|
75
75
|
## API Reference
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
Public APIs are grouped by role: protocol builders, redaction helpers, output formatters, internal redaction tools, utility/CLI helpers, types, and AFDATA logging integration.
|
|
78
78
|
|
|
79
79
|
### Protocol Builders (returns dict)
|
|
80
80
|
|
|
@@ -91,13 +91,14 @@ build_json_error(message: str, hint: str = None, trace: Any = None) -> dict
|
|
|
91
91
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
###
|
|
94
|
+
### Redaction Helpers (returns Any)
|
|
95
95
|
|
|
96
96
|
Use these before raw HTTP/MCP/SSE serializers that do not call `output_json`.
|
|
97
97
|
|
|
98
98
|
```python
|
|
99
99
|
redacted_value(value: Any) -> Any
|
|
100
100
|
redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any
|
|
101
|
+
redacted_value_with_options(value: Any, redaction_options: RedactionOptions) -> Any
|
|
101
102
|
```
|
|
102
103
|
|
|
103
104
|
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
@@ -138,15 +139,18 @@ not_found = build_json(
|
|
|
138
139
|
)
|
|
139
140
|
```
|
|
140
141
|
|
|
141
|
-
###
|
|
142
|
+
### Output Formatters (returns str)
|
|
142
143
|
|
|
143
|
-
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies.
|
|
144
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. Use `OutputOptions` to pass legacy secret names such as `api_key` or request schema-preserving YAML/plain rendering with `OutputStyle.Raw`.
|
|
144
145
|
|
|
145
146
|
```python
|
|
146
147
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
147
148
|
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
149
|
+
output_json_with_options(value: Any, output_options: OutputOptions) -> str
|
|
148
150
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
151
|
+
output_yaml_with_options(value: Any, output_options: OutputOptions) -> str
|
|
149
152
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
153
|
+
output_plain_with_options(value: Any, output_options: OutputOptions) -> str
|
|
150
154
|
```
|
|
151
155
|
|
|
152
156
|
```python
|
|
@@ -154,8 +158,18 @@ class RedactionPolicy(enum.Enum):
|
|
|
154
158
|
RedactionTraceOnly = "RedactionTraceOnly"
|
|
155
159
|
RedactionNone = "RedactionNone"
|
|
156
160
|
RedactionStrict = "RedactionStrict"
|
|
161
|
+
|
|
162
|
+
RedactionOptions(policy: RedactionPolicy | None = None, secret_names: Sequence[str] = ())
|
|
163
|
+
|
|
164
|
+
class OutputStyle(enum.Enum):
|
|
165
|
+
Readable = "Readable"
|
|
166
|
+
Raw = "Raw"
|
|
167
|
+
|
|
168
|
+
OutputOptions(redaction: RedactionOptions = RedactionOptions(), style: OutputStyle = OutputStyle.Readable)
|
|
157
169
|
```
|
|
158
170
|
|
|
171
|
+
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching. `OutputOptions` combines `RedactionOptions` with `OutputStyle.Readable` (default suffix stripping and value formatting) or `OutputStyle.Raw` (no suffix stripping or value formatting).
|
|
172
|
+
|
|
159
173
|
**Example:**
|
|
160
174
|
```python
|
|
161
175
|
from agent_first_data import *
|
|
@@ -188,6 +202,7 @@ print(output_plain(data))
|
|
|
188
202
|
|
|
189
203
|
```python
|
|
190
204
|
internal_redact_secrets(value: Any) -> None # Manually redact secrets in-place
|
|
205
|
+
internal_redact_secrets_with_options(value: Any, redaction_options: RedactionOptions) -> None
|
|
191
206
|
```
|
|
192
207
|
|
|
193
208
|
Most users don't need this. Output functions automatically protect secrets.
|
|
@@ -219,6 +234,7 @@ class OutputFormat(enum.Enum): # JSON="json", YAML="yaml", PLAIN="plain"
|
|
|
219
234
|
cli_parse_output(s: str) -> OutputFormat # Parse --output flag; raises ValueError on unknown
|
|
220
235
|
cli_parse_log_filters(entries: list[str]) -> list[str] # Normalize --log: trim, lowercase, dedup, remove empty
|
|
221
236
|
cli_output(value: Any, format: OutputFormat) -> str # Dispatch to output_json/yaml/plain
|
|
237
|
+
cli_output_with_options(value: Any, format: OutputFormat, output_options: OutputOptions) -> str
|
|
222
238
|
build_cli_error(message: str, hint: str = None) -> dict # {code:"error", error_code:"invalid_request", hint?, retryable:False, trace:{duration_ms:0}}
|
|
223
239
|
```
|
|
224
240
|
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: agent-first-data
|
|
3
|
-
Version: 0.8.0
|
|
4
|
-
Summary: Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates for AI agents
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
|
7
|
-
Requires-Python: >=3.9
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
|
10
1
|
# agent-first-data
|
|
11
2
|
|
|
12
3
|
**Agent-First Data (AFDATA)** — Suffix-driven output formatting and protocol templates for AI agents.
|
|
@@ -74,7 +65,7 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
74
65
|
|
|
75
66
|
## API Reference
|
|
76
67
|
|
|
77
|
-
|
|
68
|
+
Public APIs are grouped by role: protocol builders, redaction helpers, output formatters, internal redaction tools, utility/CLI helpers, types, and AFDATA logging integration.
|
|
78
69
|
|
|
79
70
|
### Protocol Builders (returns dict)
|
|
80
71
|
|
|
@@ -91,13 +82,14 @@ build_json_error(message: str, hint: str = None, trace: Any = None) -> dict
|
|
|
91
82
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
92
83
|
```
|
|
93
84
|
|
|
94
|
-
###
|
|
85
|
+
### Redaction Helpers (returns Any)
|
|
95
86
|
|
|
96
87
|
Use these before raw HTTP/MCP/SSE serializers that do not call `output_json`.
|
|
97
88
|
|
|
98
89
|
```python
|
|
99
90
|
redacted_value(value: Any) -> Any
|
|
100
91
|
redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any
|
|
92
|
+
redacted_value_with_options(value: Any, redaction_options: RedactionOptions) -> Any
|
|
101
93
|
```
|
|
102
94
|
|
|
103
95
|
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
@@ -138,15 +130,18 @@ not_found = build_json(
|
|
|
138
130
|
)
|
|
139
131
|
```
|
|
140
132
|
|
|
141
|
-
###
|
|
133
|
+
### Output Formatters (returns str)
|
|
142
134
|
|
|
143
|
-
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies.
|
|
135
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. Use `OutputOptions` to pass legacy secret names such as `api_key` or request schema-preserving YAML/plain rendering with `OutputStyle.Raw`.
|
|
144
136
|
|
|
145
137
|
```python
|
|
146
138
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
147
139
|
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
140
|
+
output_json_with_options(value: Any, output_options: OutputOptions) -> str
|
|
148
141
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
142
|
+
output_yaml_with_options(value: Any, output_options: OutputOptions) -> str
|
|
149
143
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
144
|
+
output_plain_with_options(value: Any, output_options: OutputOptions) -> str
|
|
150
145
|
```
|
|
151
146
|
|
|
152
147
|
```python
|
|
@@ -154,8 +149,18 @@ class RedactionPolicy(enum.Enum):
|
|
|
154
149
|
RedactionTraceOnly = "RedactionTraceOnly"
|
|
155
150
|
RedactionNone = "RedactionNone"
|
|
156
151
|
RedactionStrict = "RedactionStrict"
|
|
152
|
+
|
|
153
|
+
RedactionOptions(policy: RedactionPolicy | None = None, secret_names: Sequence[str] = ())
|
|
154
|
+
|
|
155
|
+
class OutputStyle(enum.Enum):
|
|
156
|
+
Readable = "Readable"
|
|
157
|
+
Raw = "Raw"
|
|
158
|
+
|
|
159
|
+
OutputOptions(redaction: RedactionOptions = RedactionOptions(), style: OutputStyle = OutputStyle.Readable)
|
|
157
160
|
```
|
|
158
161
|
|
|
162
|
+
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching. `OutputOptions` combines `RedactionOptions` with `OutputStyle.Readable` (default suffix stripping and value formatting) or `OutputStyle.Raw` (no suffix stripping or value formatting).
|
|
163
|
+
|
|
159
164
|
**Example:**
|
|
160
165
|
```python
|
|
161
166
|
from agent_first_data import *
|
|
@@ -188,6 +193,7 @@ print(output_plain(data))
|
|
|
188
193
|
|
|
189
194
|
```python
|
|
190
195
|
internal_redact_secrets(value: Any) -> None # Manually redact secrets in-place
|
|
196
|
+
internal_redact_secrets_with_options(value: Any, redaction_options: RedactionOptions) -> None
|
|
191
197
|
```
|
|
192
198
|
|
|
193
199
|
Most users don't need this. Output functions automatically protect secrets.
|
|
@@ -219,6 +225,7 @@ class OutputFormat(enum.Enum): # JSON="json", YAML="yaml", PLAIN="plain"
|
|
|
219
225
|
cli_parse_output(s: str) -> OutputFormat # Parse --output flag; raises ValueError on unknown
|
|
220
226
|
cli_parse_log_filters(entries: list[str]) -> list[str] # Normalize --log: trim, lowercase, dedup, remove empty
|
|
221
227
|
cli_output(value: Any, format: OutputFormat) -> str # Dispatch to output_json/yaml/plain
|
|
228
|
+
cli_output_with_options(value: Any, format: OutputFormat, output_options: OutputOptions) -> str
|
|
222
229
|
build_cli_error(message: str, hint: str = None) -> dict # {code:"error", error_code:"invalid_request", hint?, retryable:False, trace:{duration_ms:0}}
|
|
223
230
|
```
|
|
224
231
|
|
|
@@ -5,13 +5,21 @@ from agent_first_data.format import (
|
|
|
5
5
|
build_json_error,
|
|
6
6
|
build_json,
|
|
7
7
|
RedactionPolicy,
|
|
8
|
+
RedactionOptions,
|
|
9
|
+
OutputStyle,
|
|
10
|
+
OutputOptions,
|
|
8
11
|
output_json,
|
|
9
12
|
output_json_with,
|
|
13
|
+
output_json_with_options,
|
|
10
14
|
output_yaml,
|
|
15
|
+
output_yaml_with_options,
|
|
11
16
|
output_plain,
|
|
17
|
+
output_plain_with_options,
|
|
12
18
|
internal_redact_secrets,
|
|
19
|
+
internal_redact_secrets_with_options,
|
|
13
20
|
redacted_value,
|
|
14
21
|
redacted_value_with,
|
|
22
|
+
redacted_value_with_options,
|
|
15
23
|
parse_size,
|
|
16
24
|
)
|
|
17
25
|
|
|
@@ -30,6 +38,7 @@ from agent_first_data.cli import (
|
|
|
30
38
|
cli_parse_output,
|
|
31
39
|
cli_parse_log_filters,
|
|
32
40
|
cli_output,
|
|
41
|
+
cli_output_with_options,
|
|
33
42
|
build_cli_error,
|
|
34
43
|
)
|
|
35
44
|
|
|
@@ -38,13 +47,21 @@ __all__ = [
|
|
|
38
47
|
"build_json_error",
|
|
39
48
|
"build_json",
|
|
40
49
|
"RedactionPolicy",
|
|
50
|
+
"RedactionOptions",
|
|
51
|
+
"OutputStyle",
|
|
52
|
+
"OutputOptions",
|
|
41
53
|
"output_json",
|
|
42
54
|
"output_json_with",
|
|
55
|
+
"output_json_with_options",
|
|
43
56
|
"output_yaml",
|
|
57
|
+
"output_yaml_with_options",
|
|
44
58
|
"output_plain",
|
|
59
|
+
"output_plain_with_options",
|
|
45
60
|
"internal_redact_secrets",
|
|
61
|
+
"internal_redact_secrets_with_options",
|
|
46
62
|
"redacted_value",
|
|
47
63
|
"redacted_value_with",
|
|
64
|
+
"redacted_value_with_options",
|
|
48
65
|
"parse_size",
|
|
49
66
|
"AfdataHandler",
|
|
50
67
|
"AfdataJsonHandler",
|
|
@@ -57,5 +74,6 @@ __all__ = [
|
|
|
57
74
|
"cli_parse_output",
|
|
58
75
|
"cli_parse_log_filters",
|
|
59
76
|
"cli_output",
|
|
77
|
+
"cli_output_with_options",
|
|
60
78
|
"build_cli_error",
|
|
61
79
|
]
|
|
@@ -5,7 +5,15 @@ from __future__ import annotations
|
|
|
5
5
|
import enum
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from agent_first_data.format import
|
|
8
|
+
from agent_first_data.format import (
|
|
9
|
+
OutputOptions,
|
|
10
|
+
output_json,
|
|
11
|
+
output_json_with_options,
|
|
12
|
+
output_yaml,
|
|
13
|
+
output_yaml_with_options,
|
|
14
|
+
output_plain,
|
|
15
|
+
output_plain_with_options,
|
|
16
|
+
)
|
|
9
17
|
|
|
10
18
|
|
|
11
19
|
class OutputFormat(enum.Enum):
|
|
@@ -69,6 +77,22 @@ def cli_output(value: Any, format: OutputFormat) -> str:
|
|
|
69
77
|
return output_json(value)
|
|
70
78
|
|
|
71
79
|
|
|
80
|
+
def cli_output_with_options(
|
|
81
|
+
value: Any,
|
|
82
|
+
format: OutputFormat,
|
|
83
|
+
output_options: OutputOptions,
|
|
84
|
+
) -> str:
|
|
85
|
+
"""Dispatch output formatting with explicit redaction and style.
|
|
86
|
+
|
|
87
|
+
JSON ignores OutputStyle and preserves original keys and values after redaction.
|
|
88
|
+
"""
|
|
89
|
+
if format is OutputFormat.YAML:
|
|
90
|
+
return output_yaml_with_options(value, output_options)
|
|
91
|
+
if format is OutputFormat.PLAIN:
|
|
92
|
+
return output_plain_with_options(value, output_options)
|
|
93
|
+
return output_json_with_options(value, output_options)
|
|
94
|
+
|
|
95
|
+
|
|
72
96
|
def build_cli_error(message: str, hint: str | None = None) -> dict:
|
|
73
97
|
"""Build a standard CLI parse error value.
|
|
74
98
|
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
"""AFDATA output formatting and protocol templates.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
output formatters, redaction, parse_size,
|
|
3
|
+
16 public APIs and 4 types: protocol builders, redacted value helpers,
|
|
4
|
+
output formatters, redaction, parse_size, RedactionPolicy, RedactionOptions,
|
|
5
|
+
OutputStyle, and OutputOptions.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
9
10
|
import json
|
|
10
11
|
import math
|
|
12
|
+
from dataclasses import dataclass, field
|
|
11
13
|
from datetime import datetime, timezone
|
|
12
14
|
from enum import Enum
|
|
13
|
-
from typing import Any
|
|
15
|
+
from typing import Any, Sequence
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
# ═══════════════════════════════════════════
|
|
@@ -55,6 +57,30 @@ class RedactionPolicy(str, Enum):
|
|
|
55
57
|
RedactionStrict = "RedactionStrict"
|
|
56
58
|
|
|
57
59
|
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class RedactionOptions:
|
|
62
|
+
"""Redaction options for legacy secret field names."""
|
|
63
|
+
|
|
64
|
+
policy: RedactionPolicy | None = None
|
|
65
|
+
# Exact field-name matches at any nesting level.
|
|
66
|
+
secret_names: Sequence[str] = ()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class OutputStyle(str, Enum):
|
|
70
|
+
"""Rendering style for YAML and plain output."""
|
|
71
|
+
|
|
72
|
+
Readable = "Readable"
|
|
73
|
+
Raw = "Raw"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class OutputOptions:
|
|
78
|
+
"""Output options combining redaction and rendering style."""
|
|
79
|
+
|
|
80
|
+
redaction: RedactionOptions = field(default_factory=RedactionOptions)
|
|
81
|
+
style: OutputStyle = OutputStyle.Readable
|
|
82
|
+
|
|
83
|
+
|
|
58
84
|
def output_json(value: Any) -> str:
|
|
59
85
|
"""Format as single-line JSON. Secrets redacted, original keys, raw values."""
|
|
60
86
|
return json.dumps(redacted_value(value), ensure_ascii=False, separators=(",", ":"))
|
|
@@ -65,19 +91,44 @@ def output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str:
|
|
|
65
91
|
return json.dumps(redacted_value_with(value, redaction_policy), ensure_ascii=False, separators=(",", ":"))
|
|
66
92
|
|
|
67
93
|
|
|
94
|
+
def output_json_with_options(value: Any, output_options: OutputOptions) -> str:
|
|
95
|
+
"""Format as single-line JSON with explicit output options."""
|
|
96
|
+
return json.dumps(
|
|
97
|
+
redacted_value_with_options(value, output_options.redaction),
|
|
98
|
+
ensure_ascii=False,
|
|
99
|
+
separators=(",", ":"),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
68
103
|
def output_yaml(value: Any) -> str:
|
|
69
104
|
"""Format as multi-line YAML. Keys stripped, values formatted, secrets redacted."""
|
|
70
|
-
value
|
|
105
|
+
return output_yaml_with_options(value, OutputOptions())
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def output_yaml_with_options(value: Any, output_options: OutputOptions) -> str:
|
|
109
|
+
"""Format as multi-line YAML with explicit output options."""
|
|
110
|
+
value = redacted_value_with_options(value, output_options.redaction)
|
|
71
111
|
lines = ["---"]
|
|
72
|
-
|
|
112
|
+
if output_options.style is OutputStyle.Raw:
|
|
113
|
+
_render_yaml_raw(value, 0, lines)
|
|
114
|
+
else:
|
|
115
|
+
_render_yaml_processed(value, 0, lines)
|
|
73
116
|
return "\n".join(lines)
|
|
74
117
|
|
|
75
118
|
|
|
76
119
|
def output_plain(value: Any) -> str:
|
|
77
120
|
"""Format as single-line logfmt. Keys stripped, values formatted, secrets redacted."""
|
|
78
|
-
value
|
|
121
|
+
return output_plain_with_options(value, OutputOptions())
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def output_plain_with_options(value: Any, output_options: OutputOptions) -> str:
|
|
125
|
+
"""Format as single-line logfmt with explicit output options."""
|
|
126
|
+
value = redacted_value_with_options(value, output_options.redaction)
|
|
79
127
|
pairs: list[tuple[str, str]] = []
|
|
80
|
-
|
|
128
|
+
if output_options.style is OutputStyle.Raw:
|
|
129
|
+
_collect_plain_pairs_raw(value, "", pairs)
|
|
130
|
+
else:
|
|
131
|
+
_collect_plain_pairs(value, "", pairs)
|
|
81
132
|
pairs.sort(key=lambda p: p[0].encode("utf-16-be"))
|
|
82
133
|
parts = []
|
|
83
134
|
for k, v in pairs:
|
|
@@ -95,6 +146,11 @@ def internal_redact_secrets(value: Any) -> None:
|
|
|
95
146
|
_redact_secrets(value)
|
|
96
147
|
|
|
97
148
|
|
|
149
|
+
def internal_redact_secrets_with_options(value: Any, redaction_options: RedactionOptions) -> None:
|
|
150
|
+
"""Redact secret fields in-place using explicit redaction options."""
|
|
151
|
+
_apply_redaction_options(value, redaction_options)
|
|
152
|
+
|
|
153
|
+
|
|
98
154
|
def redacted_value(value: Any) -> Any:
|
|
99
155
|
"""Return a JSON-safe copy with default _secret redaction applied."""
|
|
100
156
|
v = _sanitize_for_json(value)
|
|
@@ -109,18 +165,38 @@ def redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any:
|
|
|
109
165
|
return v
|
|
110
166
|
|
|
111
167
|
|
|
168
|
+
def redacted_value_with_options(value: Any, redaction_options: RedactionOptions) -> Any:
|
|
169
|
+
"""Return a JSON-safe copy with explicit redaction options applied."""
|
|
170
|
+
v = _sanitize_for_json(value)
|
|
171
|
+
_apply_redaction_options(v, redaction_options)
|
|
172
|
+
return v
|
|
173
|
+
|
|
174
|
+
|
|
112
175
|
def _apply_redaction_policy(value: Any, redaction_policy: RedactionPolicy) -> None:
|
|
176
|
+
_apply_redaction_policy_with_names(value, redaction_policy, frozenset())
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _apply_redaction_options(value: Any, redaction_options: RedactionOptions) -> None:
|
|
180
|
+
secret_names = _secret_name_set(redaction_options.secret_names)
|
|
181
|
+
_apply_redaction_policy_with_names(value, redaction_options.policy, secret_names)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _apply_redaction_policy_with_names(
|
|
185
|
+
value: Any,
|
|
186
|
+
redaction_policy: RedactionPolicy | None,
|
|
187
|
+
secret_names: frozenset[str],
|
|
188
|
+
) -> None:
|
|
113
189
|
if redaction_policy == RedactionPolicy.RedactionTraceOnly:
|
|
114
190
|
if isinstance(value, dict) and "trace" in value:
|
|
115
|
-
_redact_secrets(value["trace"])
|
|
191
|
+
_redact_secrets(value["trace"], secret_names)
|
|
116
192
|
return
|
|
117
193
|
if redaction_policy == RedactionPolicy.RedactionNone:
|
|
118
194
|
return
|
|
119
195
|
if redaction_policy == RedactionPolicy.RedactionStrict:
|
|
120
|
-
_redact_secrets_strict(value)
|
|
196
|
+
_redact_secrets_strict(value, secret_names)
|
|
121
197
|
return
|
|
122
|
-
#
|
|
123
|
-
_redact_secrets(value)
|
|
198
|
+
# Empty/unknown policy falls back to default full redaction.
|
|
199
|
+
_redact_secrets(value, secret_names)
|
|
124
200
|
|
|
125
201
|
|
|
126
202
|
def parse_size(s: str) -> int | None:
|
|
@@ -208,31 +284,43 @@ def _sanitize_for_json(value: Any, stack: set[int] | None = None) -> Any:
|
|
|
208
284
|
return f"<unsupported:{type(value).__name__}>"
|
|
209
285
|
|
|
210
286
|
|
|
211
|
-
def
|
|
287
|
+
def _secret_name_set(secret_names: Sequence[str]) -> frozenset[str]:
|
|
288
|
+
return frozenset(secret_names)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _key_has_secret_suffix(key: str) -> bool:
|
|
292
|
+
return key.endswith("_secret") or key.endswith("_SECRET")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _is_secret_key(key: str, secret_names: frozenset[str]) -> bool:
|
|
296
|
+
return _key_has_secret_suffix(key) or key in secret_names
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _redact_secrets(value: Any, secret_names: frozenset[str] = frozenset()) -> None:
|
|
212
300
|
if isinstance(value, dict):
|
|
213
301
|
for k in list(value.keys()):
|
|
214
|
-
if k
|
|
302
|
+
if _is_secret_key(k, secret_names):
|
|
215
303
|
if isinstance(value[k], (dict, list)):
|
|
216
|
-
_redact_secrets(value[k])
|
|
304
|
+
_redact_secrets(value[k], secret_names)
|
|
217
305
|
else:
|
|
218
306
|
value[k] = "***"
|
|
219
307
|
else:
|
|
220
|
-
_redact_secrets(value[k])
|
|
308
|
+
_redact_secrets(value[k], secret_names)
|
|
221
309
|
elif isinstance(value, list):
|
|
222
310
|
for item in value:
|
|
223
|
-
_redact_secrets(item)
|
|
311
|
+
_redact_secrets(item, secret_names)
|
|
224
312
|
|
|
225
313
|
|
|
226
|
-
def _redact_secrets_strict(value: Any) -> None:
|
|
314
|
+
def _redact_secrets_strict(value: Any, secret_names: frozenset[str] = frozenset()) -> None:
|
|
227
315
|
if isinstance(value, dict):
|
|
228
316
|
for k in list(value.keys()):
|
|
229
|
-
if k
|
|
317
|
+
if _is_secret_key(k, secret_names):
|
|
230
318
|
value[k] = "***"
|
|
231
319
|
else:
|
|
232
|
-
_redact_secrets_strict(value[k])
|
|
320
|
+
_redact_secrets_strict(value[k], secret_names)
|
|
233
321
|
elif isinstance(value, list):
|
|
234
322
|
for item in value:
|
|
235
|
-
_redact_secrets_strict(item)
|
|
323
|
+
_redact_secrets_strict(item, secret_names)
|
|
236
324
|
|
|
237
325
|
|
|
238
326
|
# ═══════════════════════════════════════════
|
|
@@ -546,6 +634,53 @@ def _render_yaml_processed(value: Any, indent: int, lines: list[str]) -> None:
|
|
|
546
634
|
lines.append(f"{prefix}{display_key}: {_yaml_scalar(v)}")
|
|
547
635
|
|
|
548
636
|
|
|
637
|
+
def _render_yaml_raw(value: Any, indent: int, lines: list[str]) -> None:
|
|
638
|
+
prefix = " " * indent
|
|
639
|
+
if isinstance(value, dict):
|
|
640
|
+
for key in _sorted_object_keys(value):
|
|
641
|
+
_render_yaml_field_raw(prefix, key, value[key], indent, lines)
|
|
642
|
+
elif isinstance(value, list):
|
|
643
|
+
_render_yaml_array_raw(value, indent, lines)
|
|
644
|
+
else:
|
|
645
|
+
lines.append(f"{prefix}{_yaml_scalar(value)}")
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _render_yaml_field_raw(prefix: str, key: str, value: Any, indent: int, lines: list[str]) -> None:
|
|
649
|
+
if isinstance(value, dict):
|
|
650
|
+
if value:
|
|
651
|
+
lines.append(f"{prefix}{key}:")
|
|
652
|
+
_render_yaml_raw(value, indent + 1, lines)
|
|
653
|
+
else:
|
|
654
|
+
lines.append(f"{prefix}{key}: {{}}")
|
|
655
|
+
elif isinstance(value, list):
|
|
656
|
+
if value:
|
|
657
|
+
lines.append(f"{prefix}{key}:")
|
|
658
|
+
_render_yaml_array_raw(value, indent + 1, lines)
|
|
659
|
+
else:
|
|
660
|
+
lines.append(f"{prefix}{key}: []")
|
|
661
|
+
else:
|
|
662
|
+
lines.append(f"{prefix}{key}: {_yaml_scalar(value)}")
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _render_yaml_array_raw(arr: list[Any], indent: int, lines: list[str]) -> None:
|
|
666
|
+
prefix = " " * indent
|
|
667
|
+
for item in arr:
|
|
668
|
+
if isinstance(item, dict):
|
|
669
|
+
if item:
|
|
670
|
+
lines.append(f"{prefix}-")
|
|
671
|
+
_render_yaml_raw(item, indent + 1, lines)
|
|
672
|
+
else:
|
|
673
|
+
lines.append(f"{prefix}- {{}}")
|
|
674
|
+
elif isinstance(item, list):
|
|
675
|
+
if item:
|
|
676
|
+
lines.append(f"{prefix}-")
|
|
677
|
+
_render_yaml_array_raw(item, indent + 1, lines)
|
|
678
|
+
else:
|
|
679
|
+
lines.append(f"{prefix}- []")
|
|
680
|
+
else:
|
|
681
|
+
lines.append(f"{prefix}- {_yaml_scalar(item)}")
|
|
682
|
+
|
|
683
|
+
|
|
549
684
|
def _escape_yaml_str(s: str) -> str:
|
|
550
685
|
return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
|
|
551
686
|
|
|
@@ -586,6 +721,23 @@ def _collect_plain_pairs(value: Any, prefix: str, pairs: list[tuple[str, str]])
|
|
|
586
721
|
pairs.append((full_key, _plain_scalar(v)))
|
|
587
722
|
|
|
588
723
|
|
|
724
|
+
def _collect_plain_pairs_raw(value: Any, prefix: str, pairs: list[tuple[str, str]]) -> None:
|
|
725
|
+
if not isinstance(value, dict):
|
|
726
|
+
return
|
|
727
|
+
for key in _sorted_object_keys(value):
|
|
728
|
+
v = value[key]
|
|
729
|
+
full_key = f"{prefix}.{key}" if prefix else key
|
|
730
|
+
if isinstance(v, dict):
|
|
731
|
+
_collect_plain_pairs_raw(v, full_key, pairs)
|
|
732
|
+
elif isinstance(v, list):
|
|
733
|
+
joined = ",".join(_plain_scalar_raw(item) for item in v)
|
|
734
|
+
pairs.append((full_key, joined))
|
|
735
|
+
elif v is None:
|
|
736
|
+
pairs.append((full_key, ""))
|
|
737
|
+
else:
|
|
738
|
+
pairs.append((full_key, _plain_scalar(v)))
|
|
739
|
+
|
|
740
|
+
|
|
589
741
|
def _plain_scalar(value: Any) -> str:
|
|
590
742
|
if isinstance(value, str):
|
|
591
743
|
return value
|
|
@@ -598,6 +750,12 @@ def _plain_scalar(value: Any) -> str:
|
|
|
598
750
|
return str(value)
|
|
599
751
|
|
|
600
752
|
|
|
753
|
+
def _plain_scalar_raw(value: Any) -> str:
|
|
754
|
+
if isinstance(value, (dict, list)):
|
|
755
|
+
return json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
|
|
756
|
+
return _plain_scalar(value)
|
|
757
|
+
|
|
758
|
+
|
|
601
759
|
def _quote_logfmt_value(value: str) -> str:
|
|
602
760
|
if value == "":
|
|
603
761
|
return ""
|
|
@@ -612,3 +770,7 @@ def _quote_logfmt_value(value: str) -> str:
|
|
|
612
770
|
.replace("\t", "\\t")
|
|
613
771
|
)
|
|
614
772
|
return f'"{escaped}"'
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _sorted_object_keys(d: dict) -> list[str]:
|
|
776
|
+
return sorted(d.keys(), key=lambda k: k.encode("utf-16-be"))
|
agent_first_data-0.8.0/README.md → agent_first_data-0.9.0/agent_first_data.egg-info/PKG-INFO
RENAMED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-first-data
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: A naming convention that lets AI agents understand your data without being told what it means.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
1
10
|
# agent-first-data
|
|
2
11
|
|
|
3
12
|
**Agent-First Data (AFDATA)** — Suffix-driven output formatting and protocol templates for AI agents.
|
|
@@ -65,7 +74,7 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
65
74
|
|
|
66
75
|
## API Reference
|
|
67
76
|
|
|
68
|
-
|
|
77
|
+
Public APIs are grouped by role: protocol builders, redaction helpers, output formatters, internal redaction tools, utility/CLI helpers, types, and AFDATA logging integration.
|
|
69
78
|
|
|
70
79
|
### Protocol Builders (returns dict)
|
|
71
80
|
|
|
@@ -82,13 +91,14 @@ build_json_error(message: str, hint: str = None, trace: Any = None) -> dict
|
|
|
82
91
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
83
92
|
```
|
|
84
93
|
|
|
85
|
-
###
|
|
94
|
+
### Redaction Helpers (returns Any)
|
|
86
95
|
|
|
87
96
|
Use these before raw HTTP/MCP/SSE serializers that do not call `output_json`.
|
|
88
97
|
|
|
89
98
|
```python
|
|
90
99
|
redacted_value(value: Any) -> Any
|
|
91
100
|
redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any
|
|
101
|
+
redacted_value_with_options(value: Any, redaction_options: RedactionOptions) -> Any
|
|
92
102
|
```
|
|
93
103
|
|
|
94
104
|
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
@@ -129,15 +139,18 @@ not_found = build_json(
|
|
|
129
139
|
)
|
|
130
140
|
```
|
|
131
141
|
|
|
132
|
-
###
|
|
142
|
+
### Output Formatters (returns str)
|
|
133
143
|
|
|
134
|
-
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies.
|
|
144
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. Use `OutputOptions` to pass legacy secret names such as `api_key` or request schema-preserving YAML/plain rendering with `OutputStyle.Raw`.
|
|
135
145
|
|
|
136
146
|
```python
|
|
137
147
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
138
148
|
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
149
|
+
output_json_with_options(value: Any, output_options: OutputOptions) -> str
|
|
139
150
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
151
|
+
output_yaml_with_options(value: Any, output_options: OutputOptions) -> str
|
|
140
152
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
153
|
+
output_plain_with_options(value: Any, output_options: OutputOptions) -> str
|
|
141
154
|
```
|
|
142
155
|
|
|
143
156
|
```python
|
|
@@ -145,8 +158,18 @@ class RedactionPolicy(enum.Enum):
|
|
|
145
158
|
RedactionTraceOnly = "RedactionTraceOnly"
|
|
146
159
|
RedactionNone = "RedactionNone"
|
|
147
160
|
RedactionStrict = "RedactionStrict"
|
|
161
|
+
|
|
162
|
+
RedactionOptions(policy: RedactionPolicy | None = None, secret_names: Sequence[str] = ())
|
|
163
|
+
|
|
164
|
+
class OutputStyle(enum.Enum):
|
|
165
|
+
Readable = "Readable"
|
|
166
|
+
Raw = "Raw"
|
|
167
|
+
|
|
168
|
+
OutputOptions(redaction: RedactionOptions = RedactionOptions(), style: OutputStyle = OutputStyle.Readable)
|
|
148
169
|
```
|
|
149
170
|
|
|
171
|
+
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching. `OutputOptions` combines `RedactionOptions` with `OutputStyle.Readable` (default suffix stripping and value formatting) or `OutputStyle.Raw` (no suffix stripping or value formatting).
|
|
172
|
+
|
|
150
173
|
**Example:**
|
|
151
174
|
```python
|
|
152
175
|
from agent_first_data import *
|
|
@@ -179,6 +202,7 @@ print(output_plain(data))
|
|
|
179
202
|
|
|
180
203
|
```python
|
|
181
204
|
internal_redact_secrets(value: Any) -> None # Manually redact secrets in-place
|
|
205
|
+
internal_redact_secrets_with_options(value: Any, redaction_options: RedactionOptions) -> None
|
|
182
206
|
```
|
|
183
207
|
|
|
184
208
|
Most users don't need this. Output functions automatically protect secrets.
|
|
@@ -210,6 +234,7 @@ class OutputFormat(enum.Enum): # JSON="json", YAML="yaml", PLAIN="plain"
|
|
|
210
234
|
cli_parse_output(s: str) -> OutputFormat # Parse --output flag; raises ValueError on unknown
|
|
211
235
|
cli_parse_log_filters(entries: list[str]) -> list[str] # Normalize --log: trim, lowercase, dedup, remove empty
|
|
212
236
|
cli_output(value: Any, format: OutputFormat) -> str # Dispatch to output_json/yaml/plain
|
|
237
|
+
cli_output_with_options(value: Any, format: OutputFormat, output_options: OutputOptions) -> str
|
|
213
238
|
build_cli_error(message: str, hint: str = None) -> dict # {code:"error", error_code:"invalid_request", hint?, retryable:False, trace:{duration_ms:0}}
|
|
214
239
|
```
|
|
215
240
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agent-first-data"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.9.0"
|
|
4
|
+
description = "A naming convention that lets AI agents understand your data without being told what it means."
|
|
5
5
|
license = "MIT"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.9"
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import pytest
|
|
3
3
|
from agent_first_data import (
|
|
4
4
|
OutputFormat,
|
|
5
|
+
OutputStyle,
|
|
6
|
+
OutputOptions,
|
|
5
7
|
cli_parse_output,
|
|
6
8
|
cli_parse_log_filters,
|
|
7
9
|
cli_output,
|
|
10
|
+
cli_output_with_options,
|
|
8
11
|
build_cli_error,
|
|
9
12
|
output_json,
|
|
10
13
|
)
|
|
@@ -106,3 +109,14 @@ def test_cli_output_dispatches_plain():
|
|
|
106
109
|
out = cli_output(v, OutputFormat.PLAIN)
|
|
107
110
|
assert "\n" not in out
|
|
108
111
|
assert "code=ok" in out
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_cli_output_with_options_dispatches_raw_yaml():
|
|
115
|
+
v = {"size_bytes": 1024}
|
|
116
|
+
out = cli_output_with_options(
|
|
117
|
+
v,
|
|
118
|
+
OutputFormat.YAML,
|
|
119
|
+
OutputOptions(style=OutputStyle.Raw),
|
|
120
|
+
)
|
|
121
|
+
assert "size_bytes: 1024" in out
|
|
122
|
+
assert "size:" not in out
|
|
@@ -8,13 +8,21 @@ from agent_first_data import (
|
|
|
8
8
|
build_json_error,
|
|
9
9
|
build_json,
|
|
10
10
|
RedactionPolicy,
|
|
11
|
+
RedactionOptions,
|
|
12
|
+
OutputStyle,
|
|
13
|
+
OutputOptions,
|
|
11
14
|
internal_redact_secrets,
|
|
15
|
+
internal_redact_secrets_with_options,
|
|
12
16
|
redacted_value,
|
|
13
17
|
redacted_value_with,
|
|
18
|
+
redacted_value_with_options,
|
|
14
19
|
output_json,
|
|
15
20
|
output_json_with,
|
|
21
|
+
output_json_with_options,
|
|
16
22
|
output_yaml,
|
|
23
|
+
output_yaml_with_options,
|
|
17
24
|
output_plain,
|
|
25
|
+
output_plain_with_options,
|
|
18
26
|
)
|
|
19
27
|
from agent_first_data.format import (
|
|
20
28
|
_format_bytes_human,
|
|
@@ -31,6 +39,12 @@ def _load(name):
|
|
|
31
39
|
return json.load(f)
|
|
32
40
|
|
|
33
41
|
|
|
42
|
+
def _redaction_options(case):
|
|
43
|
+
opts = case.get("options", {})
|
|
44
|
+
policy = RedactionPolicy(opts["policy"]) if "policy" in opts else None
|
|
45
|
+
return RedactionOptions(policy=policy, secret_names=opts.get("secret_names", ()))
|
|
46
|
+
|
|
47
|
+
|
|
34
48
|
# --- Redact fixtures ---
|
|
35
49
|
|
|
36
50
|
|
|
@@ -42,6 +56,31 @@ def test_redact_fixtures():
|
|
|
42
56
|
assert inp == case["expected"], f"[redact/{name}] got {inp}"
|
|
43
57
|
|
|
44
58
|
|
|
59
|
+
def test_redaction_options_fixtures():
|
|
60
|
+
for case in _load("redaction_options.json"):
|
|
61
|
+
name = case["name"]
|
|
62
|
+
options = _redaction_options(case)
|
|
63
|
+
output_options = OutputOptions(redaction=options, style=OutputStyle.Readable)
|
|
64
|
+
expected = case["expected"]
|
|
65
|
+
|
|
66
|
+
got = redacted_value_with_options(case["input"], options)
|
|
67
|
+
assert got == expected, f"[redaction_options/{name}] value mismatch: {got}"
|
|
68
|
+
|
|
69
|
+
inp = json.loads(json.dumps(case["input"]))
|
|
70
|
+
internal_redact_secrets_with_options(inp, options)
|
|
71
|
+
assert inp == expected, f"[redaction_options/{name}] in-place mismatch: {inp}"
|
|
72
|
+
|
|
73
|
+
got_json = json.loads(output_json_with_options(case["input"], output_options))
|
|
74
|
+
assert got_json == expected, f"[redaction_options/{name}] json mismatch: {got_json}"
|
|
75
|
+
|
|
76
|
+
if "expected_yaml" in case:
|
|
77
|
+
got_yaml = output_yaml_with_options(case["input"], output_options)
|
|
78
|
+
assert got_yaml == case["expected_yaml"], f"[redaction_options/{name}] yaml mismatch: {got_yaml!r}"
|
|
79
|
+
if "expected_plain" in case:
|
|
80
|
+
got_plain = output_plain_with_options(case["input"], output_options)
|
|
81
|
+
assert got_plain == case["expected_plain"], f"[redaction_options/{name}] plain mismatch: {got_plain!r}"
|
|
82
|
+
|
|
83
|
+
|
|
45
84
|
# --- Protocol fixtures ---
|
|
46
85
|
|
|
47
86
|
|
|
@@ -111,6 +150,57 @@ def test_output_format_fixtures():
|
|
|
111
150
|
assert got_plain == case["expected_plain"], f"[output/{name}] plain mismatch: {got_plain!r}"
|
|
112
151
|
|
|
113
152
|
|
|
153
|
+
def test_output_yaml_raw_keeps_suffix_keys_and_structure():
|
|
154
|
+
options = OutputOptions(
|
|
155
|
+
redaction=RedactionOptions(policy=RedactionPolicy.RedactionTraceOnly),
|
|
156
|
+
style=OutputStyle.Raw,
|
|
157
|
+
)
|
|
158
|
+
out = output_yaml_with_options(
|
|
159
|
+
{
|
|
160
|
+
"code": "result",
|
|
161
|
+
"rows": [{"api_key_secret": "sk-live-1", "duration_ms": 42}],
|
|
162
|
+
"trace": {"request_secret": "top-secret"},
|
|
163
|
+
},
|
|
164
|
+
options,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
assert "rows:\n -" in out
|
|
168
|
+
assert 'api_key_secret: "sk-live-1"' in out
|
|
169
|
+
assert "duration_ms: 42" in out
|
|
170
|
+
assert 'request_secret: "***"' in out
|
|
171
|
+
assert 'duration: "42ms"' not in out
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_output_plain_raw_keeps_suffix_keys_and_redacts_trace():
|
|
175
|
+
options = OutputOptions(
|
|
176
|
+
redaction=RedactionOptions(policy=RedactionPolicy.RedactionTraceOnly),
|
|
177
|
+
style=OutputStyle.Raw,
|
|
178
|
+
)
|
|
179
|
+
out = output_plain_with_options(
|
|
180
|
+
{
|
|
181
|
+
"duration_ms": 42,
|
|
182
|
+
"trace": {"request_secret": "top-secret"},
|
|
183
|
+
},
|
|
184
|
+
options,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
assert "duration_ms=42" in out
|
|
188
|
+
assert "trace.request_secret=***" in out
|
|
189
|
+
assert "duration=42ms" not in out
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_output_with_options_defaults_to_readable_style():
|
|
193
|
+
out = output_yaml_with_options(
|
|
194
|
+
{"duration_ms": 42},
|
|
195
|
+
OutputOptions(
|
|
196
|
+
redaction=RedactionOptions(policy=RedactionPolicy.RedactionNone),
|
|
197
|
+
style=OutputStyle.Readable,
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
assert 'duration: "42ms"' in out
|
|
201
|
+
assert "duration_ms:" not in out
|
|
202
|
+
|
|
203
|
+
|
|
114
204
|
def test_output_json_exception_field_is_readable():
|
|
115
205
|
out = output_json({"error": Exception("timeout")})
|
|
116
206
|
parsed = json.loads(out)
|
|
File without changes
|
|
File without changes
|
{agent_first_data-0.8.0 → agent_first_data-0.9.0}/agent_first_data.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|