agent-first-data 0.12.2__tar.gz → 0.13.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.13.0/PKG-INFO +55 -0
- agent_first_data-0.13.0/README.md +46 -0
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/agent_first_data/__init__.py +6 -4
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/agent_first_data/afdata_logging.py +64 -24
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/agent_first_data/cli.py +1 -15
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/agent_first_data/format.py +196 -85
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/agent_first_data/skill.py +157 -40
- agent_first_data-0.13.0/agent_first_data.egg-info/PKG-INFO +55 -0
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/pyproject.toml +1 -1
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/tests/test_afdata_logging.py +82 -7
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/tests/test_cli.py +3 -3
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/tests/test_format.py +10 -9
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/tests/test_skill.py +110 -5
- agent_first_data-0.12.2/PKG-INFO +0 -591
- agent_first_data-0.12.2/README.md +0 -582
- agent_first_data-0.12.2/agent_first_data.egg-info/PKG-INFO +0 -591
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/agent_first_data.egg-info/SOURCES.txt +0 -0
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/agent_first_data.egg-info/dependency_links.txt +0 -0
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/agent_first_data.egg-info/top_level.txt +0 -0
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/setup.cfg +0 -0
- {agent_first_data-0.12.2 → agent_first_data-0.13.0}/tests/test_no_stderr_policy.py +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-first-data
|
|
3
|
+
Version: 0.13.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
|
+
|
|
10
|
+
# Agent-First Data for Python
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install agent-first-data
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from agent_first_data import output_json, output_plain
|
|
18
|
+
|
|
19
|
+
value = {
|
|
20
|
+
"code": "ok",
|
|
21
|
+
"result": {
|
|
22
|
+
"api_key_secret": "sk-123",
|
|
23
|
+
"latency_ms": 1280,
|
|
24
|
+
"db_url": "postgres://user:p@ss@db/app?token_secret=abc",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
print(output_json(value))
|
|
29
|
+
print(output_plain(value))
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Useful names use Python casing: `output_json`, `output_yaml`, `output_plain`, `output_json_with_options`, `redacted_value`, `redact_secrets_in_place`, `redact_url_secrets`, `parse_size`, `normalize_utc_offset`, `cli_parse_output`, `cli_output`, and `build_cli_error`.
|
|
33
|
+
|
|
34
|
+
Logging is available through `init_logging_json`, `init_logging_plain`, `init_logging_yaml`, `span`, and `get_logger`.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from agent_first_data import init_logging_json
|
|
38
|
+
|
|
39
|
+
init_logging_json("INFO", secret_names=("authorization",))
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Behavior Notes
|
|
43
|
+
|
|
44
|
+
- Default redaction replaces every `_secret` or configured secret-name subtree with `***`, including objects and arrays.
|
|
45
|
+
- `_url` fields scrub userinfo passwords and secret-named query parameters; surrounding whitespace is trimmed and internal whitespace redacts the whole field.
|
|
46
|
+
- YAML/plain quote and escape keys as well as values, sort by UTF-16 code unit order, and render nested objects in arrays as canonical JSON.
|
|
47
|
+
- Logging records use `code: "log"` plus a separate `level` field, so error-level logs are not terminal protocol errors.
|
|
48
|
+
- `build_cli_error(message, hint?)` returns `{code:"error", error: message, hint?}` only.
|
|
49
|
+
|
|
50
|
+
## Reference
|
|
51
|
+
|
|
52
|
+
- Full convention and API groups: [../docs/overview.md](../docs/overview.md)
|
|
53
|
+
- Formal cross-language contract: [../spec/agent-first-data.md](../spec/agent-first-data.md)
|
|
54
|
+
- Conformance fixtures: [../spec/fixtures](../spec/fixtures)
|
|
55
|
+
- Agent skill: [../skills/agent-first-data.md](../skills/agent-first-data.md)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Agent-First Data for Python
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
pip install agent-first-data
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from agent_first_data import output_json, output_plain
|
|
9
|
+
|
|
10
|
+
value = {
|
|
11
|
+
"code": "ok",
|
|
12
|
+
"result": {
|
|
13
|
+
"api_key_secret": "sk-123",
|
|
14
|
+
"latency_ms": 1280,
|
|
15
|
+
"db_url": "postgres://user:p@ss@db/app?token_secret=abc",
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
print(output_json(value))
|
|
20
|
+
print(output_plain(value))
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Useful names use Python casing: `output_json`, `output_yaml`, `output_plain`, `output_json_with_options`, `redacted_value`, `redact_secrets_in_place`, `redact_url_secrets`, `parse_size`, `normalize_utc_offset`, `cli_parse_output`, `cli_output`, and `build_cli_error`.
|
|
24
|
+
|
|
25
|
+
Logging is available through `init_logging_json`, `init_logging_plain`, `init_logging_yaml`, `span`, and `get_logger`.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from agent_first_data import init_logging_json
|
|
29
|
+
|
|
30
|
+
init_logging_json("INFO", secret_names=("authorization",))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Behavior Notes
|
|
34
|
+
|
|
35
|
+
- Default redaction replaces every `_secret` or configured secret-name subtree with `***`, including objects and arrays.
|
|
36
|
+
- `_url` fields scrub userinfo passwords and secret-named query parameters; surrounding whitespace is trimmed and internal whitespace redacts the whole field.
|
|
37
|
+
- YAML/plain quote and escape keys as well as values, sort by UTF-16 code unit order, and render nested objects in arrays as canonical JSON.
|
|
38
|
+
- Logging records use `code: "log"` plus a separate `level` field, so error-level logs are not terminal protocol errors.
|
|
39
|
+
- `build_cli_error(message, hint?)` returns `{code:"error", error: message, hint?}` only.
|
|
40
|
+
|
|
41
|
+
## Reference
|
|
42
|
+
|
|
43
|
+
- Full convention and API groups: [../docs/overview.md](../docs/overview.md)
|
|
44
|
+
- Formal cross-language contract: [../spec/agent-first-data.md](../spec/agent-first-data.md)
|
|
45
|
+
- Conformance fixtures: [../spec/fixtures](../spec/fixtures)
|
|
46
|
+
- Agent skill: [../skills/agent-first-data.md](../skills/agent-first-data.md)
|
|
@@ -15,14 +15,15 @@ from agent_first_data.format import (
|
|
|
15
15
|
output_yaml_with_options,
|
|
16
16
|
output_plain,
|
|
17
17
|
output_plain_with_options,
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
redact_secrets_in_place,
|
|
19
|
+
redact_secrets_in_place_with_options,
|
|
20
20
|
redacted_value,
|
|
21
21
|
redacted_value_with,
|
|
22
22
|
redacted_value_with_options,
|
|
23
23
|
redact_url_secrets,
|
|
24
24
|
redact_url_secrets_with_options,
|
|
25
25
|
parse_size,
|
|
26
|
+
normalize_utc_offset,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
from agent_first_data.afdata_logging import (
|
|
@@ -74,14 +75,15 @@ __all__ = [
|
|
|
74
75
|
"output_yaml_with_options",
|
|
75
76
|
"output_plain",
|
|
76
77
|
"output_plain_with_options",
|
|
77
|
-
"
|
|
78
|
-
"
|
|
78
|
+
"redact_secrets_in_place",
|
|
79
|
+
"redact_secrets_in_place_with_options",
|
|
79
80
|
"redacted_value",
|
|
80
81
|
"redacted_value_with",
|
|
81
82
|
"redacted_value_with_options",
|
|
82
83
|
"redact_url_secrets",
|
|
83
84
|
"redact_url_secrets_with_options",
|
|
84
85
|
"parse_size",
|
|
86
|
+
"normalize_utc_offset",
|
|
85
87
|
"AfdataHandler",
|
|
86
88
|
"init_logging_json",
|
|
87
89
|
"init_logging_plain",
|
|
@@ -21,13 +21,19 @@ Usage:
|
|
|
21
21
|
import logging
|
|
22
22
|
import sys
|
|
23
23
|
from contextvars import ContextVar, Token
|
|
24
|
-
from typing import Any
|
|
24
|
+
from typing import Any, Sequence
|
|
25
25
|
|
|
26
|
-
from agent_first_data.format import
|
|
26
|
+
from agent_first_data.format import (
|
|
27
|
+
OutputOptions,
|
|
28
|
+
RedactionOptions,
|
|
29
|
+
output_json_with_options,
|
|
30
|
+
output_plain_with_options,
|
|
31
|
+
output_yaml_with_options,
|
|
32
|
+
)
|
|
27
33
|
|
|
28
34
|
_span_fields: ContextVar[dict[str, Any]] = ContextVar("afdata_span", default={})
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
_LEVEL_TO_NAME = {
|
|
31
37
|
"CRITICAL": "error",
|
|
32
38
|
"ERROR": "error",
|
|
33
39
|
"WARNING": "warn",
|
|
@@ -44,17 +50,24 @@ class AfdataHandler(logging.Handler):
|
|
|
44
50
|
Formats output using the library's own output_json/output_plain/output_yaml.
|
|
45
51
|
"""
|
|
46
52
|
|
|
47
|
-
def __init__(
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
format: str = "json",
|
|
56
|
+
redaction: RedactionOptions | None = None,
|
|
57
|
+
secret_names: Sequence[str] | None = None,
|
|
58
|
+
) -> None:
|
|
48
59
|
super().__init__()
|
|
49
60
|
if format not in ("json", "plain", "yaml"):
|
|
50
61
|
raise ValueError(f"Unknown format: {format!r}, expected json/plain/yaml")
|
|
51
62
|
self._format = format
|
|
63
|
+
self._redaction = _redaction_options(redaction, secret_names)
|
|
52
64
|
|
|
53
65
|
def emit(self, record: logging.LogRecord) -> None:
|
|
54
66
|
entry: dict[str, Any] = {
|
|
55
67
|
"timestamp_epoch_ms": int(record.created * 1000),
|
|
56
68
|
"message": record.getMessage(),
|
|
57
|
-
"
|
|
69
|
+
"code": "log",
|
|
70
|
+
"level": _LEVEL_TO_NAME.get(record.levelname, "info"),
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
# Span fields (from contextvars, async-safe)
|
|
@@ -62,26 +75,23 @@ class AfdataHandler(logging.Handler):
|
|
|
62
75
|
if span_data:
|
|
63
76
|
entry.update(span_data)
|
|
64
77
|
|
|
65
|
-
# Event fields (passed via extra= in logging calls)
|
|
66
|
-
|
|
78
|
+
# Event fields (passed via extra= in logging calls). The protocol code
|
|
79
|
+
# is always "log"; use event for business categorization.
|
|
67
80
|
extra = getattr(record, "_afdata_fields", None)
|
|
68
81
|
if extra:
|
|
69
82
|
for k, v in extra.items():
|
|
70
83
|
if k == "code":
|
|
71
|
-
|
|
84
|
+
continue
|
|
72
85
|
entry[k] = v
|
|
73
86
|
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
entry["code"] = _LEVEL_TO_CODE.get(record.levelname, "info")
|
|
77
|
-
|
|
78
|
-
# Format using the library's own output functions
|
|
87
|
+
# Format using the library's own output functions.
|
|
88
|
+
options = OutputOptions(redaction=self._redaction)
|
|
79
89
|
if self._format == "plain":
|
|
80
|
-
line =
|
|
90
|
+
line = output_plain_with_options(entry, options)
|
|
81
91
|
elif self._format == "yaml":
|
|
82
|
-
line =
|
|
92
|
+
line = output_yaml_with_options(entry, options)
|
|
83
93
|
else:
|
|
84
|
-
line =
|
|
94
|
+
line = output_json_with_options(entry, options)
|
|
85
95
|
|
|
86
96
|
sys.stdout.write(line + "\n")
|
|
87
97
|
sys.stdout.flush()
|
|
@@ -100,26 +110,56 @@ class _AfdataLoggerAdapter(logging.LoggerAdapter):
|
|
|
100
110
|
return msg, kwargs
|
|
101
111
|
|
|
102
112
|
|
|
103
|
-
def
|
|
104
|
-
|
|
113
|
+
def _redaction_options(
|
|
114
|
+
redaction: RedactionOptions | None,
|
|
115
|
+
secret_names: Sequence[str] | None,
|
|
116
|
+
) -> RedactionOptions:
|
|
117
|
+
if redaction is not None and secret_names is not None:
|
|
118
|
+
raise ValueError("Pass either redaction or secret_names, not both")
|
|
119
|
+
if redaction is not None:
|
|
120
|
+
return redaction
|
|
121
|
+
if secret_names is not None:
|
|
122
|
+
return RedactionOptions(secret_names=tuple(secret_names))
|
|
123
|
+
return RedactionOptions()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _init_with_format(
|
|
127
|
+
format: str,
|
|
128
|
+
level: str = "INFO",
|
|
129
|
+
redaction: RedactionOptions | None = None,
|
|
130
|
+
secret_names: Sequence[str] | None = None,
|
|
131
|
+
) -> None:
|
|
132
|
+
handler = AfdataHandler(format=format, redaction=redaction, secret_names=secret_names)
|
|
105
133
|
root = logging.getLogger()
|
|
106
134
|
root.handlers = [handler]
|
|
107
135
|
root.setLevel(getattr(logging, level.upper(), logging.INFO))
|
|
108
136
|
|
|
109
137
|
|
|
110
|
-
def init_json(
|
|
138
|
+
def init_json(
|
|
139
|
+
level: str = "INFO",
|
|
140
|
+
redaction: RedactionOptions | None = None,
|
|
141
|
+
secret_names: Sequence[str] | None = None,
|
|
142
|
+
) -> None:
|
|
111
143
|
"""Initialize the root logger with AFDATA JSON output to stdout."""
|
|
112
|
-
_init_with_format("json", level)
|
|
144
|
+
_init_with_format("json", level, redaction=redaction, secret_names=secret_names)
|
|
113
145
|
|
|
114
146
|
|
|
115
|
-
def init_plain(
|
|
147
|
+
def init_plain(
|
|
148
|
+
level: str = "INFO",
|
|
149
|
+
redaction: RedactionOptions | None = None,
|
|
150
|
+
secret_names: Sequence[str] | None = None,
|
|
151
|
+
) -> None:
|
|
116
152
|
"""Initialize the root logger with AFDATA plain/logfmt output to stdout."""
|
|
117
|
-
_init_with_format("plain", level)
|
|
153
|
+
_init_with_format("plain", level, redaction=redaction, secret_names=secret_names)
|
|
118
154
|
|
|
119
155
|
|
|
120
|
-
def init_yaml(
|
|
156
|
+
def init_yaml(
|
|
157
|
+
level: str = "INFO",
|
|
158
|
+
redaction: RedactionOptions | None = None,
|
|
159
|
+
secret_names: Sequence[str] | None = None,
|
|
160
|
+
) -> None:
|
|
121
161
|
"""Initialize the root logger with AFDATA YAML output to stdout."""
|
|
122
|
-
_init_with_format("yaml", level)
|
|
162
|
+
_init_with_format("yaml", level, redaction=redaction, secret_names=secret_names)
|
|
123
163
|
|
|
124
164
|
|
|
125
165
|
def get_logger(name: str, **fields: Any) -> logging.LoggerAdapter:
|
|
@@ -98,22 +98,8 @@ def build_cli_error(message: str, hint: str | None = None) -> dict:
|
|
|
98
98
|
|
|
99
99
|
Use when argument parsing fails or a flag value is invalid.
|
|
100
100
|
Print with output_json and exit with code 2.
|
|
101
|
-
|
|
102
|
-
>>> v = build_cli_error("--output: invalid value 'xml'")
|
|
103
|
-
>>> v["code"]
|
|
104
|
-
'error'
|
|
105
|
-
>>> v["error_code"]
|
|
106
|
-
'invalid_request'
|
|
107
|
-
>>> v["retryable"]
|
|
108
|
-
False
|
|
109
101
|
"""
|
|
110
|
-
m: dict = {
|
|
111
|
-
"code": "error",
|
|
112
|
-
"error_code": "invalid_request",
|
|
113
|
-
"error": message,
|
|
114
|
-
"retryable": False,
|
|
115
|
-
"trace": {"duration_ms": 0},
|
|
116
|
-
}
|
|
102
|
+
m: dict = {"code": "error", "error": message}
|
|
117
103
|
if hint is not None:
|
|
118
104
|
m["hint"] = hint
|
|
119
105
|
return m
|