Skip to content

Commit 255fc51

Browse files
PHPCraftdreamclaude
andcommitted
examples + READMEs: typed parse + walk + build/render flow
Old example showcased a single config and round-tripped it. New example matches the cross-binding template (java/golang/csharp/python): * Parse — typed reads (port -> int, ratio -> float, nested db). * Walk — `isinstance` chain across the seven Python value types, with the `bool`-before-`int` gotcha called out. * Build & render — construct a config dict in code (with nested upstreams) and render to Ktav text. README quickstart (en/ru/zh) reorganised into matching three blocks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 27eba1f commit 255fc51

4 files changed

Lines changed: 241 additions & 85 deletions

File tree

README.md

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,77 @@ source distribution and compiles it locally — you need a Rust toolchain
3636

3737
## Quick start
3838

39+
### Parse — read typed fields straight off the dict
40+
3941
```python
4042
import ktav
4143

42-
text = """
44+
src = """
45+
service: web
4346
port:i 8080
44-
45-
upstreams: [
46-
{
47-
host: a.example
48-
port:i 1080
49-
}
50-
{
51-
host: b.example
52-
port:i 1080
53-
}
47+
ratio:f 0.75
48+
tls: true
49+
tags: [
50+
prod
51+
eu-west-1
5452
]
53+
db.host: primary.internal
54+
db.timeout:i 30
5555
"""
5656

57-
cfg = ktav.loads(text)
58-
# {'port': 8080, 'upstreams': [
59-
# {'host': 'a.example', 'port': 1080},
60-
# {'host': 'b.example', 'port': 1080},
61-
# ]}
57+
cfg = ktav.loads(src)
58+
59+
service: str = cfg["service"]
60+
port: int = cfg["port"]
61+
ratio: float = cfg["ratio"]
62+
tls: bool = cfg["tls"]
63+
tags: list[str] = cfg["tags"]
64+
db_host: str = cfg["db"]["host"]
65+
db_timeout: int = cfg["db"]["timeout"]
66+
```
67+
68+
### Walk — dispatch on the runtime type
6269

63-
back = ktav.dumps(cfg)
64-
assert ktav.loads(back) == cfg
70+
```python
71+
for k, v in cfg.items():
72+
if v is None: kind = "null"
73+
elif isinstance(v, bool): kind = f"bool={v}" # bool first — True is also an int!
74+
elif isinstance(v, int): kind = f"int={v}"
75+
elif isinstance(v, float): kind = f"float={v}"
76+
elif isinstance(v, str): kind = f"str={v!r}"
77+
elif isinstance(v, list): kind = f"array({len(v)})"
78+
elif isinstance(v, dict): kind = f"object({len(v)})"
79+
print(f"{k} -> {kind}")
6580
```
6681

82+
### Build & render — construct a document in code
83+
84+
```python
85+
doc = {
86+
"name": "frontend",
87+
"port": 8443,
88+
"tls": True,
89+
"ratio": 0.95,
90+
"upstreams": [
91+
{"host": "a.example", "port": 1080},
92+
{"host": "b.example", "port": 1080},
93+
],
94+
"notes": None,
95+
}
96+
text = ktav.dumps(doc)
97+
# name: frontend
98+
# port:i 8443
99+
# tls: true
100+
# ratio:f 0.95
101+
# upstreams: [
102+
# { host: a.example port:i 1080 }
103+
# { host: b.example port:i 1080 }
104+
# ]
105+
# notes: null
106+
```
107+
108+
A complete runnable version lives in [`examples/basic.py`](examples/basic.py).
109+
67110
Four entry points mirror the standard library `json` module:
68111

69112
| Function | Purpose |

README.ru.md

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,68 @@ pip install ktav
3636

3737
## Быстрый старт
3838

39+
### Парсинг — типизированно читаем поля
40+
3941
```python
4042
import ktav
4143

42-
text = """
44+
src = """
45+
service: web
4346
port:i 8080
44-
45-
upstreams: [
46-
{
47-
host: a.example
48-
port:i 1080
49-
}
50-
{
51-
host: b.example
52-
port:i 1080
53-
}
47+
ratio:f 0.75
48+
tls: true
49+
tags: [
50+
prod
51+
eu-west-1
5452
]
53+
db.host: primary.internal
54+
db.timeout:i 30
5555
"""
5656

57-
cfg = ktav.loads(text)
58-
# {'port': 8080, 'upstreams': [
59-
# {'host': 'a.example', 'port': 1080},
60-
# {'host': 'b.example', 'port': 1080},
61-
# ]}
57+
cfg = ktav.loads(src)
58+
59+
service: str = cfg["service"]
60+
port: int = cfg["port"]
61+
ratio: float = cfg["ratio"]
62+
tls: bool = cfg["tls"]
63+
tags: list[str] = cfg["tags"]
64+
db_host: str = cfg["db"]["host"]
65+
db_timeout: int = cfg["db"]["timeout"]
66+
```
67+
68+
### Обход — диспатч по runtime-типу
6269

63-
back = ktav.dumps(cfg)
64-
assert ktav.loads(back) == cfg
70+
```python
71+
for k, v in cfg.items():
72+
if v is None: kind = "null"
73+
elif isinstance(v, bool): kind = f"bool={v}" # bool первым — True это int!
74+
elif isinstance(v, int): kind = f"int={v}"
75+
elif isinstance(v, float): kind = f"float={v}"
76+
elif isinstance(v, str): kind = f"str={v!r}"
77+
elif isinstance(v, list): kind = f"array({len(v)})"
78+
elif isinstance(v, dict): kind = f"object({len(v)})"
79+
print(f"{k} -> {kind}")
6580
```
6681

82+
### Билд + рендер — собираем документ в коде
83+
84+
```python
85+
doc = {
86+
"name": "frontend",
87+
"port": 8443,
88+
"tls": True,
89+
"ratio": 0.95,
90+
"upstreams": [
91+
{"host": "a.example", "port": 1080},
92+
{"host": "b.example", "port": 1080},
93+
],
94+
"notes": None,
95+
}
96+
text = ktav.dumps(doc)
97+
```
98+
99+
Полный запускаемый пример — в [`examples/basic.py`](examples/basic.py).
100+
67101
Четыре публичные функции — форма повторяет стандартный `json`:
68102

69103
| Функция | Назначение |

README.zh.md

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,68 @@ wheel 即可覆盖所有受支持的 CPython 版本。
3232

3333
## 快速开始
3434

35+
### 解析 —— 直接从 dict 按类型读取字段
36+
3537
```python
3638
import ktav
3739

38-
text = """
40+
src = """
41+
service: web
3942
port:i 8080
40-
41-
upstreams: [
42-
{
43-
host: a.example
44-
port:i 1080
45-
}
43+
ratio:f 0.75
44+
tls: true
45+
tags: [
46+
prod
47+
eu-west-1
4648
]
49+
db.host: primary.internal
50+
db.timeout:i 30
4751
"""
4852

49-
cfg = ktav.loads(text)
50-
back = ktav.dumps(cfg)
51-
assert ktav.loads(back) == cfg
53+
cfg = ktav.loads(src)
54+
55+
service: str = cfg["service"]
56+
port: int = cfg["port"]
57+
ratio: float = cfg["ratio"]
58+
tls: bool = cfg["tls"]
59+
tags: list[str] = cfg["tags"]
60+
db_host: str = cfg["db"]["host"]
61+
db_timeout: int = cfg["db"]["timeout"]
62+
```
63+
64+
### 遍历 —— 按运行时类型分派
65+
66+
```python
67+
for k, v in cfg.items():
68+
if v is None: kind = "null"
69+
elif isinstance(v, bool): kind = f"bool={v}" # bool 先判断 —— True 也是 int!
70+
elif isinstance(v, int): kind = f"int={v}"
71+
elif isinstance(v, float): kind = f"float={v}"
72+
elif isinstance(v, str): kind = f"str={v!r}"
73+
elif isinstance(v, list): kind = f"array({len(v)})"
74+
elif isinstance(v, dict): kind = f"object({len(v)})"
75+
print(f"{k} -> {kind}")
76+
```
77+
78+
### 构建并渲染 —— 用代码搭建文档
79+
80+
```python
81+
doc = {
82+
"name": "frontend",
83+
"port": 8443,
84+
"tls": True,
85+
"ratio": 0.95,
86+
"upstreams": [
87+
{"host": "a.example", "port": 1080},
88+
{"host": "b.example", "port": 1080},
89+
],
90+
"notes": None,
91+
}
92+
text = ktav.dumps(doc)
5293
```
5394

95+
完整可运行示例:[`examples/basic.py`](examples/basic.py)
96+
5497
四个入口函数对应标准库 `json` 模块:
5598

5699
- `ktav.loads(s)` — 解析 Ktav 字符串

examples/basic.py

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,88 @@
1-
"""End-to-end example: parse a Ktav document, inspect, serialize back."""
1+
"""End-to-end demo: parse a Ktav document, pull out typed fields, walk
2+
the dynamic shape, then build a fresh document in Python and render it
3+
back to Ktav text.
4+
5+
Run from the repo root:
6+
7+
pip install -e . # or `pip install ktav`
8+
python examples/basic.py
9+
"""
210

311
import ktav
412

5-
SAMPLE = """
6-
# a small service config
13+
SRC = """
14+
service: web
715
port:i 8080
8-
name: frontend
16+
ratio:f 0.75
17+
tls: true
18+
tags: [
19+
prod
20+
eu-west-1
21+
]
22+
db.host: primary.internal
23+
db.timeout:i 30
24+
"""
925

10-
upstreams: [
11-
{
12-
host: a.example
13-
port:i 1080
14-
}
15-
{
16-
host: b.example
17-
port:i 1080
26+
27+
def main() -> None:
28+
cfg = ktav.loads(SRC)
29+
30+
# ── 1. Read typed fields straight off the dict. ────────────────────
31+
service: str = cfg["service"]
32+
port: int = cfg["port"]
33+
ratio: float = cfg["ratio"]
34+
tls: bool = cfg["tls"]
35+
tags: list[str] = cfg["tags"]
36+
db_host: str = cfg["db"]["host"]
37+
db_timeout: int = cfg["db"]["timeout"]
38+
39+
print(f"service={service} port={port} tls={tls} ratio={ratio:.2f}")
40+
print(f"tags={tags}")
41+
print(f"db: {db_host} (timeout={db_timeout}s)\n")
42+
43+
# ── 2. Walk the document, dispatching on the runtime type. ─────────
44+
print("shape:")
45+
for k, v in cfg.items():
46+
print(f" {k:<12} -> {describe(v)}")
47+
48+
# ── 3. Build a config in code, render it as Ktav text. ─────────────
49+
doc = {
50+
"name": "frontend",
51+
"port": 8443,
52+
"tls": True,
53+
"ratio": 0.95,
54+
"upstreams": [
55+
upstream("a.example", 1080),
56+
upstream("b.example", 1080),
57+
upstream("c.example", 1080),
58+
],
59+
"notes": None,
1860
}
19-
]
61+
rendered = ktav.dumps(doc)
62+
print("\n--- rendered ---")
63+
print(rendered, end="")
2064

21-
# Regex needs the literal-string marker so `[` is not parsed as an array.
22-
ip_allowlist:: [::1]
2365

24-
timeouts: {
25-
connect:f 1.5
26-
read:f 30.0
27-
}
28-
"""
66+
def describe(v: object) -> str:
67+
if v is None:
68+
return "null"
69+
if isinstance(v, bool): # bool first — `True` is also an `int`!
70+
return f"bool={v}"
71+
if isinstance(v, int):
72+
return f"int={v}"
73+
if isinstance(v, float):
74+
return f"float={v}"
75+
if isinstance(v, str):
76+
return f"str={v!r}"
77+
if isinstance(v, list):
78+
return f"array({len(v)})"
79+
if isinstance(v, dict):
80+
return f"object({len(v)})"
81+
return type(v).__name__
2982

3083

31-
def main() -> None:
32-
cfg = ktav.loads(SAMPLE)
33-
34-
print("ktav version:", ktav.__version__)
35-
print("spec version:", ktav.__spec_version__)
36-
print()
37-
print(f"Service {cfg['name']!r} on port {cfg['port']}")
38-
print(f" upstreams: {len(cfg['upstreams'])}")
39-
for u in cfg["upstreams"]:
40-
print(f" - {u['host']}:{u['port']}")
41-
print(f" allowlist: {cfg['ip_allowlist']!r}")
42-
print(f" connect timeout: {cfg['timeouts']['connect']}s")
43-
44-
# Round-trip.
45-
back = ktav.dumps(cfg)
46-
assert ktav.loads(back) == cfg
47-
print()
48-
print("--- re-serialised ---")
49-
print(back)
84+
def upstream(host: str, port: int) -> dict:
85+
return {"host": host, "port": port}
5086

5187

5288
if __name__ == "__main__":

0 commit comments

Comments
 (0)