yaqpy 0.6.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.
- yaqpy-0.6.0/LICENSE +21 -0
- yaqpy-0.6.0/NOTICE +17 -0
- yaqpy-0.6.0/PKG-INFO +165 -0
- yaqpy-0.6.0/README.md +132 -0
- yaqpy-0.6.0/pyproject.toml +94 -0
- yaqpy-0.6.0/src/yaqpy/__init__.py +40 -0
- yaqpy-0.6.0/src/yaqpy/__main__.py +8 -0
- yaqpy-0.6.0/src/yaqpy/api.py +189 -0
- yaqpy-0.6.0/src/yaqpy/app/__init__.py +11 -0
- yaqpy-0.6.0/src/yaqpy/app/dto.py +58 -0
- yaqpy-0.6.0/src/yaqpy/app/examples.py +142 -0
- yaqpy-0.6.0/src/yaqpy/app/local.py +57 -0
- yaqpy-0.6.0/src/yaqpy/app/ports.py +75 -0
- yaqpy-0.6.0/src/yaqpy/app/printer.py +156 -0
- yaqpy-0.6.0/src/yaqpy/app/recipe_service.py +181 -0
- yaqpy-0.6.0/src/yaqpy/app/recipe_text.py +89 -0
- yaqpy-0.6.0/src/yaqpy/app/selfdoc.py +357 -0
- yaqpy-0.6.0/src/yaqpy/app/service.py +274 -0
- yaqpy-0.6.0/src/yaqpy/cli/__init__.py +5 -0
- yaqpy-0.6.0/src/yaqpy/cli/args.py +189 -0
- yaqpy-0.6.0/src/yaqpy/cli/describe_cli.py +40 -0
- yaqpy-0.6.0/src/yaqpy/cli/main.py +167 -0
- yaqpy-0.6.0/src/yaqpy/cli/parser.py +252 -0
- yaqpy-0.6.0/src/yaqpy/cli/recipe_cli.py +164 -0
- yaqpy-0.6.0/src/yaqpy/core/__init__.py +1 -0
- yaqpy-0.6.0/src/yaqpy/core/engine/__init__.py +7 -0
- yaqpy-0.6.0/src/yaqpy/core/engine/context.py +90 -0
- yaqpy-0.6.0/src/yaqpy/core/engine/helpers.py +149 -0
- yaqpy-0.6.0/src/yaqpy/core/engine/limits.py +33 -0
- yaqpy-0.6.0/src/yaqpy/core/engine/navigator.py +62 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/__init__.py +11 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/ast.py +92 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/lex_rules.py +476 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/lexer.py +116 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/parser.py +83 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/postfix.py +93 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/prefs.py +85 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/specs.py +143 -0
- yaqpy-0.6.0/src/yaqpy/core/lang/tokens.py +41 -0
- yaqpy-0.6.0/src/yaqpy/core/model/__init__.py +6 -0
- yaqpy-0.6.0/src/yaqpy/core/model/convert.py +85 -0
- yaqpy-0.6.0/src/yaqpy/core/model/datetime_util.py +573 -0
- yaqpy-0.6.0/src/yaqpy/core/model/depth.py +19 -0
- yaqpy-0.6.0/src/yaqpy/core/model/leading.py +38 -0
- yaqpy-0.6.0/src/yaqpy/core/model/node.py +444 -0
- yaqpy-0.6.0/src/yaqpy/core/model/tags.py +126 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/__init__.py +32 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/anchors.py +189 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/arithmetic.py +307 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/assign.py +52 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/basic.py +124 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/codecs.py +203 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/collections.py +540 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/datetime_ops.py +157 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/documents.py +17 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/logic.py +230 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/meta.py +220 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/multiply.py +140 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/prune.py +55 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/regex.py +220 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/registry.py +91 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/schema.py +206 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/sequences.py +252 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/slice.py +45 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/strings.py +359 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/structure.py +199 -0
- yaqpy-0.6.0/src/yaqpy/core/operators/traverse.py +205 -0
- yaqpy-0.6.0/src/yaqpy/errors.py +144 -0
- yaqpy-0.6.0/src/yaqpy/formats/__init__.py +5 -0
- yaqpy-0.6.0/src/yaqpy/formats/base.py +30 -0
- yaqpy-0.6.0/src/yaqpy/formats/csv_codec.py +294 -0
- yaqpy-0.6.0/src/yaqpy/formats/json_codec.py +207 -0
- yaqpy-0.6.0/src/yaqpy/formats/props_codec.py +402 -0
- yaqpy-0.6.0/src/yaqpy/formats/registry.py +205 -0
- yaqpy-0.6.0/src/yaqpy/formats/sniff.py +89 -0
- yaqpy-0.6.0/src/yaqpy/formats/toml_codec.py +804 -0
- yaqpy-0.6.0/src/yaqpy/formats/toon_codec.py +829 -0
- yaqpy-0.6.0/src/yaqpy/formats/xml_codec.py +601 -0
- yaqpy-0.6.0/src/yaqpy/formats/xml_tokens.py +480 -0
- yaqpy-0.6.0/src/yaqpy/formats/yaml/__init__.py +8 -0
- yaqpy-0.6.0/src/yaqpy/formats/yaml/codec.py +144 -0
- yaqpy-0.6.0/src/yaqpy/formats/yaml/emitter.py +404 -0
- yaqpy-0.6.0/src/yaqpy/formats/yaml/parser.py +1335 -0
- yaqpy-0.6.0/src/yaqpy/formats/yaml/resolver.py +31 -0
- yaqpy-0.6.0/src/yaqpy/gui/__init__.py +12 -0
- yaqpy-0.6.0/src/yaqpy/gui/_di.py +60 -0
- yaqpy-0.6.0/src/yaqpy/gui/_prefs.py +38 -0
- yaqpy-0.6.0/src/yaqpy/gui/_run.py +180 -0
- yaqpy-0.6.0/src/yaqpy/gui/_upload.py +145 -0
- yaqpy-0.6.0/src/yaqpy/gui/_web.py +126 -0
- yaqpy-0.6.0/src/yaqpy/gui/app.py +190 -0
- yaqpy-0.6.0/src/yaqpy/gui/assets/web/favicon.png +0 -0
- yaqpy-0.6.0/src/yaqpy/gui/assets/web/icons/loading-animation.png +0 -0
- yaqpy-0.6.0/src/yaqpy/gui/assets/yaqpy-logo.ico +0 -0
- yaqpy-0.6.0/src/yaqpy/gui/errors_ja.py +96 -0
- yaqpy-0.6.0/src/yaqpy/gui/intake.py +92 -0
- yaqpy-0.6.0/src/yaqpy/gui/logo.py +21 -0
- yaqpy-0.6.0/src/yaqpy/gui/pages/__init__.py +1 -0
- yaqpy-0.6.0/src/yaqpy/gui/pages/main_page.py +868 -0
- yaqpy-0.6.0/src/yaqpy/gui/pages/settings_page.py +156 -0
- yaqpy-0.6.0/src/yaqpy/gui/paths.py +111 -0
- yaqpy-0.6.0/src/yaqpy/gui/presenter.py +575 -0
- yaqpy-0.6.0/src/yaqpy/gui/state.py +217 -0
- yaqpy-0.6.0/src/yaqpy/gui/texts.py +300 -0
- yaqpy-0.6.0/src/yaqpy/gui/web_config.py +123 -0
- yaqpy-0.6.0/src/yaqpy/options.py +164 -0
- yaqpy-0.6.0/src/yaqpy/py.typed +0 -0
- yaqpy-0.6.0/src/yaqpy/recipes/__init__.py +17 -0
- yaqpy-0.6.0/src/yaqpy/recipes/analysis.py +83 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/anthropic-messages-request.schema.json +194 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/anthropic-to-gemini.recipe.yaml +229 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/anthropic-to-gemini.yaqpy +19 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/anthropic-to-openai.recipe.yaml +215 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/anthropic-to-openai.yaqpy +21 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/gemini-generate-content-request.schema.json +185 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/gemini-to-anthropic.recipe.yaml +243 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/gemini-to-anthropic.yaqpy +22 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/gemini-to-openai.recipe.yaml +239 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/gemini-to-openai.yaqpy +28 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/openai-chat-request.schema.json +404 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/openai-to-anthropic.recipe.yaml +247 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/openai-to-anthropic.yaqpy +25 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/openai-to-gemini.recipe.yaml +245 -0
- yaqpy-0.6.0/src/yaqpy/recipes/builtin/openai-to-gemini.yaqpy +36 -0
- yaqpy-0.6.0/src/yaqpy/recipes/catalog.py +63 -0
- yaqpy-0.6.0/src/yaqpy/recipes/conform.py +170 -0
- yaqpy-0.6.0/src/yaqpy/recipes/diff.py +108 -0
- yaqpy-0.6.0/src/yaqpy/recipes/loader.py +158 -0
- yaqpy-0.6.0/src/yaqpy/recipes/model.py +58 -0
- yaqpy-0.6.0/src/yaqpy/recipes/paths.py +156 -0
yaqpy-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sgtao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
yaqpy-0.6.0/NOTICE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
yaqpy
|
|
2
|
+
=====
|
|
3
|
+
|
|
4
|
+
yaqpy is an independent, pure-Python re-implementation of the expression
|
|
5
|
+
language and core behaviour of yq (https://github.com/mikefarah/yq), a Go
|
|
6
|
+
program by Mike Farah and contributors, released under the MIT License.
|
|
7
|
+
|
|
8
|
+
The design of the operator set, the precedence table, the lexer rule order and
|
|
9
|
+
the test scenarios under tests/golden/operators/ were derived from the yq
|
|
10
|
+
source tree (v4.53.6). The extracted scenarios remain subject to the yq MIT
|
|
11
|
+
License:
|
|
12
|
+
|
|
13
|
+
MIT License
|
|
14
|
+
Copyright (c) 2017 Mike Farah
|
|
15
|
+
|
|
16
|
+
No code from yq is compiled into or linked with yaqpy; yaqpy has zero runtime
|
|
17
|
+
dependencies beyond the Python 3.13 standard library.
|
yaqpy-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yaqpy
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: YAML and more—Query editor in Python: query, update and convert YAML / JSON / XML / CSV / TOML / properties / TOON with the mikefarah/yq expression language, in pure Python (stdlib only)
|
|
5
|
+
Keywords: yaml,json,xml,csv,toml,toon,yq,jq,query,cli,json-schema
|
|
6
|
+
Author: sgtao
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Dist: flet>=1.0,<2 ; extra == 'gui'
|
|
23
|
+
Requires-Dist: flet[web]>=1.0,<2 ; extra == 'web'
|
|
24
|
+
Requires-Python: >=3.13
|
|
25
|
+
Project-URL: Homepage, https://github.com/sgtao/yaqpy
|
|
26
|
+
Project-URL: Repository, https://github.com/sgtao/yaqpy
|
|
27
|
+
Project-URL: Documentation, https://github.com/sgtao/yaqpy/blob/main/USAGE.ja.md
|
|
28
|
+
Project-URL: Changelog, https://github.com/sgtao/yaqpy/blob/main/CHANGELOG.md
|
|
29
|
+
Project-URL: Issues, https://github.com/sgtao/yaqpy/issues
|
|
30
|
+
Provides-Extra: gui
|
|
31
|
+
Provides-Extra: web
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
**English** | [日本語](https://github.com/sgtao/yaqpy/blob/main/README.ja.md)
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="https://raw.githubusercontent.com/sgtao/yaqpy/main/logo.svg" alt="yaqpy — YAML and more, Query editor in Python" width="600">
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
[](https://github.com/sgtao/yaqpy/blob/main/LICENSE)
|
|
41
|
+
[](https://www.python.org/downloads/)
|
|
42
|
+
[](https://github.com/sgtao/yaqpy/releases)
|
|
43
|
+
|
|
44
|
+
# yaqpy — YAML and more, Query editor in Python
|
|
45
|
+
|
|
46
|
+
A lightweight tool to **query, update and convert** YAML, JSON and more with expressions, from the command line or from Python.
|
|
47
|
+
|
|
48
|
+
- It follows the expression language of the popular CLI tool [mikefarah/yq](https://github.com/mikefarah/yq) (Go, v4.53.6)
|
|
49
|
+
- It is re-implemented with **nothing but the Python standard library**
|
|
50
|
+
- The name stands for **Y**AML **A**nd more, **Q**uery editor in **PY**thon
|
|
51
|
+
|
|
52
|
+
```console
|
|
53
|
+
$ yaqpy '.server.port' config.yaml
|
|
54
|
+
8080
|
|
55
|
+
$ yaqpy -i '.server.port = 9090' config.yaml # comments and key order are kept
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Keeps your formatting**: comments, key order, anchors (`&` / `*`) and the way numbers and quotes were written (`0x1F`, `1.50`, `'yes'`) survive an update
|
|
61
|
+
- **No dependencies**: all it needs is Python 3.13 or later. The GUI is an optional extra (Flet); the web version adds flet-web
|
|
62
|
+
- **Four ways to use it**: the `yaqpy` command, the Python library (`import yaqpy`), a desktop GUI (`yaqpy-gui`) and the same GUI in a web browser (`yaqpy-web`)
|
|
63
|
+
- **Formats**: YAML, JSON, XML, CSV / TSV, TOML, properties and TOON (a token-saving format for LLMs), both in and out. TOML comments are not kept, so `-i` on TOML is refused by default
|
|
64
|
+
- **Compatible with yq**: 1,091 test scenarios of the Go version are run as compatibility tests (1,047 of the 1,051 comparable ones match). Every operator works except `load` and friends, `eval`, `envsubst`, `system` and `error`
|
|
65
|
+
- **Beyond yq** (not in the Go version):
|
|
66
|
+
- the input format is detected from the content when the file extension does not tell it
|
|
67
|
+
- `yaqpy --schema data.yaml` prints a JSON Schema (Draft 2020-12) of the data, as JSON or YAML
|
|
68
|
+
- `yaqpy --recipe openai-to-gemini request.json` converts request bodies between OpenAI, Gemini and Anthropic, and reports what was dropped, added, or does not fit the target schema (results go to stdout; files are written only with `--apply --out-dir`; no API is called)
|
|
69
|
+
- yaqpy describes itself for people and AI: `--print-spec` (the operators that work and those that do not), `--example`, `--guide-prompt` (a prompt that lets an AI write yaqpy expressions) and `--skill-md` (a Claude Code skill)
|
|
70
|
+
- **Safe defaults**: as a library, operators that read files or environment variables or run commands are all off. The CLI, like the Go version, allows environment variables and file reads (commands stay off). The web version always turns them off
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
Python 3.13 or later is required.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install yaqpy # the yaqpy command and the library (no dependencies)
|
|
78
|
+
pip install "yaqpy[gui]" # + the desktop GUI (adds Flet)
|
|
79
|
+
pip install "yaqpy[web]" # + the GUI in a web browser (adds Flet and flet-web)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
With [uv](https://docs.astral.sh/uv/):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv tool install yaqpy # install the yaqpy command
|
|
86
|
+
uv tool install "yaqpy[gui,web]" # ... with what yaqpy-gui and yaqpy-web need
|
|
87
|
+
uvx yaqpy '.server.port' config.yaml # run it once without installing
|
|
88
|
+
uv add yaqpy # use it as a library in a uv project
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**From GitHub Releases** (for example, a version that is not on PyPI): pick a version on [Releases](https://github.com/sgtao/yaqpy/releases) and replace `0.6.0` / `v0.6.0` below with it.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pip install "yaqpy[gui] @ https://github.com/sgtao/yaqpy/releases/download/v0.6.0/yaqpy-0.6.0-py3-none-any.whl"
|
|
95
|
+
pip install "yaqpy[gui] @ git+https://github.com/sgtao/yaqpy@v0.6.0"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To work on the source, see [DEVELOPMENT.md](https://github.com/sgtao/yaqpy/blob/main/DEVELOPMENT.md) (Japanese).
|
|
99
|
+
|
|
100
|
+
## Quick start
|
|
101
|
+
|
|
102
|
+
Take this `config.yaml`:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
# server settings
|
|
106
|
+
server:
|
|
107
|
+
port: 8080 # dev
|
|
108
|
+
hosts: [a, b]
|
|
109
|
+
items:
|
|
110
|
+
- {name: pen, price: 120}
|
|
111
|
+
- {name: book, price: 980}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Command line**
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
yaqpy '.server.port' config.yaml # read -> 8080
|
|
118
|
+
yaqpy '.items[] | select(.price > 500) | .name' config.yaml # filter -> book
|
|
119
|
+
yaqpy -i '.server.port = 9090' config.yaml # update (comments and key order are kept)
|
|
120
|
+
yaqpy -o json '.server' config.yaml # convert (yaml / json / xml / csv / tsv / toml / props / toon)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Python library**
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import yaqpy
|
|
127
|
+
|
|
128
|
+
yaqpy.evaluate(".server.port", "server:\n port: 8080\n") # '8080\n'
|
|
129
|
+
yaqpy.query(".items[] | select(.price > 500)", {"items": [{"price": 1}, {"price": 900}]}) # [{'price': 900}]
|
|
130
|
+
yaqpy.update(".server.port = 9090", {"server": {"port": 8080}}) # {'server': {'port': 9090}}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**GUI**
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
yaqpy-gui # desktop window (or: yaqpy --gui)
|
|
137
|
+
yaqpy-web # the same screens in your browser at http://127.0.0.1:8550/ (or: yaqpy --web)
|
|
138
|
+
yaqpy-web --port 9000 # another port; see yaqpy-web --help for the options
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The web version listens on this PC only by default and has no authentication. Files are uploaded from the browser and results come back as downloads.
|
|
142
|
+
|
|
143
|
+
## Documentation
|
|
144
|
+
|
|
145
|
+
The detailed guides are written in **Japanese**.
|
|
146
|
+
|
|
147
|
+
| Contents | File |
|
|
148
|
+
|---|---|
|
|
149
|
+
| This README in Japanese | [README.ja.md](https://github.com/sgtao/yaqpy/blob/main/README.ja.md) |
|
|
150
|
+
| The command, each format, JSON Schema output, recipes, the library, the operators, and the differences from Go yq | [USAGE.ja.md](https://github.com/sgtao/yaqpy/blob/main/USAGE.ja.md) |
|
|
151
|
+
| The GUI (desktop and web) | [USAGE-GUI.ja.md](https://github.com/sgtao/yaqpy/blob/main/USAGE-GUI.ja.md) |
|
|
152
|
+
| For developers (setup, design, tests, repository layout) | [DEVELOPMENT.md](https://github.com/sgtao/yaqpy/blob/main/DEVELOPMENT.md) |
|
|
153
|
+
| Changes in each version (features, known limitations) | [CHANGELOG.md](https://github.com/sgtao/yaqpy/blob/main/CHANGELOG.md) |
|
|
154
|
+
|
|
155
|
+
`yaqpy --help`, `yaqpy-gui --help` and `yaqpy-web --help` are in English.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
[MIT License](https://github.com/sgtao/yaqpy/blob/main/LICENSE)
|
|
160
|
+
|
|
161
|
+
The design and the test scenarios draw on Go yq (MIT); see [NOTICE](https://github.com/sgtao/yaqpy/blob/main/NOTICE).
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
🤖 Built with [Claude Code](https://claude.com/claude-code)
|
yaqpy-0.6.0/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
**English** | [日本語](https://github.com/sgtao/yaqpy/blob/main/README.ja.md)
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/sgtao/yaqpy/main/logo.svg" alt="yaqpy — YAML and more, Query editor in Python" width="600">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
[](https://github.com/sgtao/yaqpy/blob/main/LICENSE)
|
|
8
|
+
[](https://www.python.org/downloads/)
|
|
9
|
+
[](https://github.com/sgtao/yaqpy/releases)
|
|
10
|
+
|
|
11
|
+
# yaqpy — YAML and more, Query editor in Python
|
|
12
|
+
|
|
13
|
+
A lightweight tool to **query, update and convert** YAML, JSON and more with expressions, from the command line or from Python.
|
|
14
|
+
|
|
15
|
+
- It follows the expression language of the popular CLI tool [mikefarah/yq](https://github.com/mikefarah/yq) (Go, v4.53.6)
|
|
16
|
+
- It is re-implemented with **nothing but the Python standard library**
|
|
17
|
+
- The name stands for **Y**AML **A**nd more, **Q**uery editor in **PY**thon
|
|
18
|
+
|
|
19
|
+
```console
|
|
20
|
+
$ yaqpy '.server.port' config.yaml
|
|
21
|
+
8080
|
|
22
|
+
$ yaqpy -i '.server.port = 9090' config.yaml # comments and key order are kept
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **Keeps your formatting**: comments, key order, anchors (`&` / `*`) and the way numbers and quotes were written (`0x1F`, `1.50`, `'yes'`) survive an update
|
|
28
|
+
- **No dependencies**: all it needs is Python 3.13 or later. The GUI is an optional extra (Flet); the web version adds flet-web
|
|
29
|
+
- **Four ways to use it**: the `yaqpy` command, the Python library (`import yaqpy`), a desktop GUI (`yaqpy-gui`) and the same GUI in a web browser (`yaqpy-web`)
|
|
30
|
+
- **Formats**: YAML, JSON, XML, CSV / TSV, TOML, properties and TOON (a token-saving format for LLMs), both in and out. TOML comments are not kept, so `-i` on TOML is refused by default
|
|
31
|
+
- **Compatible with yq**: 1,091 test scenarios of the Go version are run as compatibility tests (1,047 of the 1,051 comparable ones match). Every operator works except `load` and friends, `eval`, `envsubst`, `system` and `error`
|
|
32
|
+
- **Beyond yq** (not in the Go version):
|
|
33
|
+
- the input format is detected from the content when the file extension does not tell it
|
|
34
|
+
- `yaqpy --schema data.yaml` prints a JSON Schema (Draft 2020-12) of the data, as JSON or YAML
|
|
35
|
+
- `yaqpy --recipe openai-to-gemini request.json` converts request bodies between OpenAI, Gemini and Anthropic, and reports what was dropped, added, or does not fit the target schema (results go to stdout; files are written only with `--apply --out-dir`; no API is called)
|
|
36
|
+
- yaqpy describes itself for people and AI: `--print-spec` (the operators that work and those that do not), `--example`, `--guide-prompt` (a prompt that lets an AI write yaqpy expressions) and `--skill-md` (a Claude Code skill)
|
|
37
|
+
- **Safe defaults**: as a library, operators that read files or environment variables or run commands are all off. The CLI, like the Go version, allows environment variables and file reads (commands stay off). The web version always turns them off
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Python 3.13 or later is required.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install yaqpy # the yaqpy command and the library (no dependencies)
|
|
45
|
+
pip install "yaqpy[gui]" # + the desktop GUI (adds Flet)
|
|
46
|
+
pip install "yaqpy[web]" # + the GUI in a web browser (adds Flet and flet-web)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
With [uv](https://docs.astral.sh/uv/):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv tool install yaqpy # install the yaqpy command
|
|
53
|
+
uv tool install "yaqpy[gui,web]" # ... with what yaqpy-gui and yaqpy-web need
|
|
54
|
+
uvx yaqpy '.server.port' config.yaml # run it once without installing
|
|
55
|
+
uv add yaqpy # use it as a library in a uv project
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**From GitHub Releases** (for example, a version that is not on PyPI): pick a version on [Releases](https://github.com/sgtao/yaqpy/releases) and replace `0.6.0` / `v0.6.0` below with it.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install "yaqpy[gui] @ https://github.com/sgtao/yaqpy/releases/download/v0.6.0/yaqpy-0.6.0-py3-none-any.whl"
|
|
62
|
+
pip install "yaqpy[gui] @ git+https://github.com/sgtao/yaqpy@v0.6.0"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To work on the source, see [DEVELOPMENT.md](https://github.com/sgtao/yaqpy/blob/main/DEVELOPMENT.md) (Japanese).
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
Take this `config.yaml`:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
# server settings
|
|
73
|
+
server:
|
|
74
|
+
port: 8080 # dev
|
|
75
|
+
hosts: [a, b]
|
|
76
|
+
items:
|
|
77
|
+
- {name: pen, price: 120}
|
|
78
|
+
- {name: book, price: 980}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Command line**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
yaqpy '.server.port' config.yaml # read -> 8080
|
|
85
|
+
yaqpy '.items[] | select(.price > 500) | .name' config.yaml # filter -> book
|
|
86
|
+
yaqpy -i '.server.port = 9090' config.yaml # update (comments and key order are kept)
|
|
87
|
+
yaqpy -o json '.server' config.yaml # convert (yaml / json / xml / csv / tsv / toml / props / toon)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Python library**
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import yaqpy
|
|
94
|
+
|
|
95
|
+
yaqpy.evaluate(".server.port", "server:\n port: 8080\n") # '8080\n'
|
|
96
|
+
yaqpy.query(".items[] | select(.price > 500)", {"items": [{"price": 1}, {"price": 900}]}) # [{'price': 900}]
|
|
97
|
+
yaqpy.update(".server.port = 9090", {"server": {"port": 8080}}) # {'server': {'port': 9090}}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**GUI**
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
yaqpy-gui # desktop window (or: yaqpy --gui)
|
|
104
|
+
yaqpy-web # the same screens in your browser at http://127.0.0.1:8550/ (or: yaqpy --web)
|
|
105
|
+
yaqpy-web --port 9000 # another port; see yaqpy-web --help for the options
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The web version listens on this PC only by default and has no authentication. Files are uploaded from the browser and results come back as downloads.
|
|
109
|
+
|
|
110
|
+
## Documentation
|
|
111
|
+
|
|
112
|
+
The detailed guides are written in **Japanese**.
|
|
113
|
+
|
|
114
|
+
| Contents | File |
|
|
115
|
+
|---|---|
|
|
116
|
+
| This README in Japanese | [README.ja.md](https://github.com/sgtao/yaqpy/blob/main/README.ja.md) |
|
|
117
|
+
| The command, each format, JSON Schema output, recipes, the library, the operators, and the differences from Go yq | [USAGE.ja.md](https://github.com/sgtao/yaqpy/blob/main/USAGE.ja.md) |
|
|
118
|
+
| The GUI (desktop and web) | [USAGE-GUI.ja.md](https://github.com/sgtao/yaqpy/blob/main/USAGE-GUI.ja.md) |
|
|
119
|
+
| For developers (setup, design, tests, repository layout) | [DEVELOPMENT.md](https://github.com/sgtao/yaqpy/blob/main/DEVELOPMENT.md) |
|
|
120
|
+
| Changes in each version (features, known limitations) | [CHANGELOG.md](https://github.com/sgtao/yaqpy/blob/main/CHANGELOG.md) |
|
|
121
|
+
|
|
122
|
+
`yaqpy --help`, `yaqpy-gui --help` and `yaqpy-web --help` are in English.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
[MIT License](https://github.com/sgtao/yaqpy/blob/main/LICENSE)
|
|
127
|
+
|
|
128
|
+
The design and the test scenarios draw on Go yq (MIT); see [NOTICE](https://github.com/sgtao/yaqpy/blob/main/NOTICE).
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
🤖 Built with [Claude Code](https://claude.com/claude-code)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "yaqpy"
|
|
3
|
+
version = "0.6.0"
|
|
4
|
+
description = "YAML and more—Query editor in Python: query, update and convert YAML / JSON / XML / CSV / TOML / properties / TOON with the mikefarah/yq expression language, in pure Python (stdlib only)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE", "NOTICE"]
|
|
8
|
+
authors = [{ name = "sgtao" }]
|
|
9
|
+
keywords = ["yaml", "json", "xml", "csv", "toml", "toon", "yq", "jq", "query", "cli", "json-schema"]
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
dependencies = []
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Environment :: Web Environment",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: System Administrators",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Text Processing :: Markup",
|
|
23
|
+
"Topic :: Utilities",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/sgtao/yaqpy"
|
|
29
|
+
Repository = "https://github.com/sgtao/yaqpy"
|
|
30
|
+
Documentation = "https://github.com/sgtao/yaqpy/blob/main/USAGE.ja.md"
|
|
31
|
+
Changelog = "https://github.com/sgtao/yaqpy/blob/main/CHANGELOG.md"
|
|
32
|
+
Issues = "https://github.com/sgtao/yaqpy/issues"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
yaqpy = "yaqpy.cli.main:main"
|
|
36
|
+
yaqpy-gui = "yaqpy.gui.app:cli_entry"
|
|
37
|
+
yaqpy-web = "yaqpy.gui.app:web_cli_entry"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
gui = [
|
|
41
|
+
"flet>=1.0,<2",
|
|
42
|
+
]
|
|
43
|
+
web = [
|
|
44
|
+
"flet[web]>=1.0,<2",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[dependency-groups]
|
|
48
|
+
dev = [
|
|
49
|
+
"pytest>=9.1.1",
|
|
50
|
+
"pytest-cov>=7.1.0",
|
|
51
|
+
"pytest-timeout>=2.4.0",
|
|
52
|
+
"pytest-xdist>=3.8.0",
|
|
53
|
+
"python-toon>=0.1.3",
|
|
54
|
+
"tzdata>=2026.4",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[build-system]
|
|
58
|
+
requires = ["uv_build>=0.11.17,<0.12.0"]
|
|
59
|
+
build-backend = "uv_build"
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
target-version = "py313"
|
|
63
|
+
line-length = 100
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
minversion = "9.0"
|
|
67
|
+
testpaths = ["tests"]
|
|
68
|
+
python_files = ["test_*.py"]
|
|
69
|
+
# The test classes are named XxxTests (and a few TestXxx); helper base classes such as CliTestCase
|
|
70
|
+
# match neither, so they are not collected.
|
|
71
|
+
python_classes = ["Test*", "*Tests"]
|
|
72
|
+
addopts = ["-ra", "--strict-markers", "--strict-config"]
|
|
73
|
+
timeout = 300
|
|
74
|
+
markers = [
|
|
75
|
+
"golden: scenarios taken from the Go yq tests (tests/golden)",
|
|
76
|
+
"acceptance: runs the CLI in a subprocess (tests/acceptance)",
|
|
77
|
+
"gui: needs the optional Flet package",
|
|
78
|
+
"slow: takes several seconds",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[tool.coverage.run]
|
|
82
|
+
source = ["yaqpy"]
|
|
83
|
+
branch = true
|
|
84
|
+
patch = ["subprocess"]
|
|
85
|
+
|
|
86
|
+
[tool.coverage.report]
|
|
87
|
+
show_missing = true
|
|
88
|
+
skip_empty = true
|
|
89
|
+
exclude_also = [
|
|
90
|
+
"if TYPE_CHECKING:",
|
|
91
|
+
"raise NotImplementedError",
|
|
92
|
+
"if __name__ == .__main__.:",
|
|
93
|
+
'^\s*\.\.\.$',
|
|
94
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""yaqpy - a pure-Python (standard library only) implementation of mikefarah/yq.
|
|
2
|
+
|
|
3
|
+
>>> import yaqpy
|
|
4
|
+
>>> yaqpy.evaluate(".a.b", "a:\\n b: 3\\n")
|
|
5
|
+
'3\\n'
|
|
6
|
+
>>> yaqpy.query(".items[] | select(. > 1)", {"items": [1, 2, 3]})
|
|
7
|
+
[2, 3]
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from yaqpy.api import (
|
|
11
|
+
Yq, apply_recipe, compile, detect_format, dump, evaluate, evaluate_all, list_recipes, load,
|
|
12
|
+
query, update,
|
|
13
|
+
)
|
|
14
|
+
from yaqpy.app.recipe_service import RecipeRun
|
|
15
|
+
from yaqpy.core.lang.parser import Expression
|
|
16
|
+
from yaqpy.core.model.node import Kind, Node, Style
|
|
17
|
+
from yaqpy.errors import (
|
|
18
|
+
EvaluationError, EvaluationLimitError, ExpressionSyntaxError, FormatError, RecipeError,
|
|
19
|
+
SecurityError, UnknownFormatError, YamlSyntaxError, YqError,
|
|
20
|
+
)
|
|
21
|
+
from yaqpy.options import (
|
|
22
|
+
CsvOptions, JsonOptions, Limits, Options, PropertiesOptions, SchemaOptions, SecurityPolicy,
|
|
23
|
+
TomlOptions, ToonOptions, XmlOptions, YamlOptions,
|
|
24
|
+
)
|
|
25
|
+
from yaqpy.recipes import Recipe, build_recipe
|
|
26
|
+
from yaqpy.recipes.analysis import RecipeReport
|
|
27
|
+
|
|
28
|
+
__version__ = "0.6.0"
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"Yq", "compile", "dump", "evaluate", "evaluate_all", "load", "query", "update",
|
|
32
|
+
"apply_recipe", "list_recipes", "build_recipe", "Recipe", "RecipeReport", "RecipeRun",
|
|
33
|
+
"detect_format",
|
|
34
|
+
"Expression", "Kind", "Node", "Style",
|
|
35
|
+
"EvaluationError", "EvaluationLimitError", "ExpressionSyntaxError", "FormatError", "RecipeError",
|
|
36
|
+
"SecurityError", "UnknownFormatError", "YamlSyntaxError", "YqError",
|
|
37
|
+
"CsvOptions", "JsonOptions", "Limits", "Options", "PropertiesOptions", "SchemaOptions",
|
|
38
|
+
"SecurityPolicy", "TomlOptions", "ToonOptions", "XmlOptions", "YamlOptions",
|
|
39
|
+
"__version__",
|
|
40
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Public library API (design doc section 10)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from yaqpy.app.dto import EvalMode, EvaluateRequest, InputSource
|
|
10
|
+
from yaqpy.app.ports import SandboxFileSystem, StaticEnvironment
|
|
11
|
+
from yaqpy.app.printer import MemorySink
|
|
12
|
+
from yaqpy.app.recipe_service import RecipeRun, RecipeService
|
|
13
|
+
from yaqpy.app.service import YqService
|
|
14
|
+
from yaqpy.core.lang.parser import Expression
|
|
15
|
+
from yaqpy.core.model.convert import from_python, to_python
|
|
16
|
+
from yaqpy.core.model.node import Node
|
|
17
|
+
from yaqpy.core.operators import OperatorRegistry, builtin_registry
|
|
18
|
+
from yaqpy.formats.registry import FormatRegistry, builtin_formats
|
|
19
|
+
from yaqpy.options import Options
|
|
20
|
+
from yaqpy.recipes import Recipe, builtin_recipes
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Yq:
|
|
24
|
+
"""Stateless facade. Safe to share between threads."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
options: Options | None = None,
|
|
29
|
+
*,
|
|
30
|
+
operators: OperatorRegistry | None = None,
|
|
31
|
+
formats: FormatRegistry | None = None,
|
|
32
|
+
environ: Mapping[str, str] | None = None,
|
|
33
|
+
clock: Callable[[], datetime] | None = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
self.options = options or Options()
|
|
36
|
+
if environ is None:
|
|
37
|
+
import os
|
|
38
|
+
|
|
39
|
+
environ = os.environ
|
|
40
|
+
self._service = YqService(
|
|
41
|
+
SandboxFileSystem(), StaticEnvironment(environ),
|
|
42
|
+
operators=operators or builtin_registry(), formats=formats or builtin_formats(),
|
|
43
|
+
clock=clock,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# ------------------------------------------------------------------ compile
|
|
47
|
+
|
|
48
|
+
def compile(self, expression: str) -> Expression:
|
|
49
|
+
return self._service.compile(expression)
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------ text in / text out
|
|
52
|
+
|
|
53
|
+
def _request(self, expression: str | Expression, texts: Iterable[str], mode: EvalMode,
|
|
54
|
+
options: Options | None) -> EvaluateRequest:
|
|
55
|
+
source = expression.source if isinstance(expression, Expression) else expression
|
|
56
|
+
inputs = tuple(InputSource("<text>", text) for text in texts)
|
|
57
|
+
return EvaluateRequest(expression=source, inputs=inputs, mode=mode,
|
|
58
|
+
options=options or self.options)
|
|
59
|
+
|
|
60
|
+
def evaluate(self, expression: str | Expression, text: str = "", *,
|
|
61
|
+
options: Options | None = None) -> str:
|
|
62
|
+
request = self._request(expression, [text] if text != "" else [], EvalMode.STREAM, options)
|
|
63
|
+
result = self._service.evaluate(request, MemorySink())
|
|
64
|
+
return result.output or ""
|
|
65
|
+
|
|
66
|
+
def evaluate_all(self, expression: str | Expression, texts: Iterable[str], *,
|
|
67
|
+
options: Options | None = None) -> str:
|
|
68
|
+
request = self._request(expression, texts, EvalMode.ALL, options)
|
|
69
|
+
result = self._service.evaluate(request, MemorySink())
|
|
70
|
+
return result.output or ""
|
|
71
|
+
|
|
72
|
+
# ------------------------------------------------------------------ nodes / python objects
|
|
73
|
+
|
|
74
|
+
def evaluate_nodes(self, expression: str | Expression, documents: Sequence[Node], *,
|
|
75
|
+
options: Options | None = None) -> list[Node]:
|
|
76
|
+
return self._service.evaluate_nodes(expression, documents, options or self.options)
|
|
77
|
+
|
|
78
|
+
def iter_results(self, expression: str | Expression, text: str, *,
|
|
79
|
+
options: Options | None = None) -> Iterator[Node]:
|
|
80
|
+
options = options or self.options
|
|
81
|
+
decoder = self._service.formats.decoder_for(options.input_format, options)
|
|
82
|
+
for doc in decoder.decode_documents(text):
|
|
83
|
+
yield from self.evaluate_nodes(expression, [doc], options=options)
|
|
84
|
+
|
|
85
|
+
def query(self, expression: str | Expression, data: Any, *,
|
|
86
|
+
options: Options | None = None) -> list[Any]:
|
|
87
|
+
root = from_python(data)
|
|
88
|
+
root.evaluate_together = True
|
|
89
|
+
results = self.evaluate_nodes(expression, [root], options=options)
|
|
90
|
+
return [to_python(n) for n in results]
|
|
91
|
+
|
|
92
|
+
def update(self, expression: str | Expression, data: Any, *,
|
|
93
|
+
options: Options | None = None) -> Any:
|
|
94
|
+
root = from_python(data)
|
|
95
|
+
root.evaluate_together = True
|
|
96
|
+
self.evaluate_nodes(expression, [root], options=options)
|
|
97
|
+
return to_python(root)
|
|
98
|
+
|
|
99
|
+
def load(self, text: str, *, format: str = "yaml", options: Options | None = None) -> list[Node]:
|
|
100
|
+
options = options or self.options
|
|
101
|
+
decoder = self._service.formats.decoder_for(format, options)
|
|
102
|
+
return list(decoder.decode_documents(text))
|
|
103
|
+
|
|
104
|
+
def dump(self, documents: Iterable[Node], *, format: str = "yaml",
|
|
105
|
+
options: Options | None = None) -> str:
|
|
106
|
+
from yaqpy.app.printer import ResultPrinter
|
|
107
|
+
|
|
108
|
+
options = options or self.options
|
|
109
|
+
spec = self._service.formats.get(format)
|
|
110
|
+
unwrap = options.unwrap_scalar if options.unwrap_scalar is not None else spec.unwrap_scalar_default
|
|
111
|
+
encoder = self._service.formats.encoder_for(format, options, unwrap)
|
|
112
|
+
sink = MemorySink()
|
|
113
|
+
ResultPrinter(encoder, sink).print_results(list(documents))
|
|
114
|
+
return sink.finish() or ""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ------------------------------------------------------------------ recipes (a yaqpy extension)
|
|
118
|
+
|
|
119
|
+
def apply_recipe(self, recipe: str | Recipe, text: str, *, input_format: str = "json",
|
|
120
|
+
output_format: str = "json", prune_null: bool = False, prune_empty: bool = False,
|
|
121
|
+
options: Options | None = None) -> RecipeRun:
|
|
122
|
+
"""Convert ``text`` with a recipe: a bundled one by name, or a ``Recipe`` you built.
|
|
123
|
+
|
|
124
|
+
Never reads files or environment variables, whatever the options say. The result holds the
|
|
125
|
+
converted text (``output``) and a ``report`` of what was dropped, added or does not fit the
|
|
126
|
+
target schema.
|
|
127
|
+
"""
|
|
128
|
+
service = RecipeService(self._service)
|
|
129
|
+
if isinstance(recipe, str):
|
|
130
|
+
recipe = service.load(recipe)
|
|
131
|
+
return service.run(recipe, InputSource("<text>", text), options or self.options,
|
|
132
|
+
input_format=service.input_format_for(recipe, "", input_format),
|
|
133
|
+
output_format=service.output_format_for(recipe, output_format),
|
|
134
|
+
prune_null=prune_null, prune_empty=prune_empty)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
_DEFAULT = Yq()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def compile(expression: str) -> Expression: # noqa: A001 - mirrors the design doc
|
|
141
|
+
return _DEFAULT.compile(expression)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def evaluate(expression: str, text: str = "", *, options: Options | None = None) -> str:
|
|
145
|
+
return _DEFAULT.evaluate(expression, text, options=options)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def evaluate_all(expression: str, texts: Iterable[str], *, options: Options | None = None) -> str:
|
|
149
|
+
return _DEFAULT.evaluate_all(expression, texts, options=options)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def query(expression: str, data: Any, *, options: Options | None = None) -> list[Any]:
|
|
153
|
+
return _DEFAULT.query(expression, data, options=options)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def update(expression: str, data: Any, *, options: Options | None = None) -> Any:
|
|
157
|
+
return _DEFAULT.update(expression, data, options=options)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def load(text: str, *, format: str = "yaml", options: Options | None = None) -> list[Node]:
|
|
161
|
+
return _DEFAULT.load(text, format=format, options=options)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def dump(documents: Iterable[Node], *, format: str = "yaml", options: Options | None = None) -> str:
|
|
165
|
+
return _DEFAULT.dump(documents, format=format, options=options)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def detect_format(text: str) -> str:
|
|
169
|
+
"""Guess the format of ``text`` from its content alone (a yaqpy extension; Go yq has no such
|
|
170
|
+
thing - it only ever looks at a file's extension).
|
|
171
|
+
|
|
172
|
+
Meant for text with no filename to go by, or none whose extension names a format: the same
|
|
173
|
+
guess ``Options(input_format="auto")`` falls back to once a filename's extension gives no
|
|
174
|
+
answer. Returns a format name ``Options(input_format=...)`` accepts; "yaml" is the fallback
|
|
175
|
+
when nothing in the content looks confident enough (never an error).
|
|
176
|
+
"""
|
|
177
|
+
return builtin_formats().guess("", text).name
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def list_recipes() -> dict[str, Recipe]:
|
|
181
|
+
"""The bundled recipes by name."""
|
|
182
|
+
return dict(builtin_recipes())
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def apply_recipe(recipe: str | Recipe, text: str, *, input_format: str = "json",
|
|
186
|
+
output_format: str = "json", prune_null: bool = False, prune_empty: bool = False,
|
|
187
|
+
options: Options | None = None) -> RecipeRun:
|
|
188
|
+
return _DEFAULT.apply_recipe(recipe, text, input_format=input_format, output_format=output_format,
|
|
189
|
+
prune_null=prune_null, prune_empty=prune_empty, options=options)
|