ssc_codegen 0.9.9__tar.gz → 0.10.1__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.
- ssc_codegen-0.10.1/PKG-INFO +253 -0
- ssc_codegen-0.10.1/README.md +216 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/pyproject.toml +2 -1
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/__init__.py +29 -3
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/__init__.py +7 -3
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/base.py +42 -1
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/nodes_array.py +5 -1
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/nodes_cast.py +15 -2
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/nodes_core.py +71 -1
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/nodes_selectors.py +22 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/nodes_string.py +15 -7
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/nodes_validate.py +2 -2
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_build/main.py +128 -38
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_build/utils.py +8 -1
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/cli/consts.py +10 -1
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/cli/main.py +70 -5
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/compiler.py +65 -1
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/base.py +40 -8
- ssc_codegen-0.10.1/ssc_codegen/converters/go_goquery.py +1416 -0
- ssc_codegen-0.10.1/ssc_codegen/converters/helpers.py +251 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/js_pure.py +270 -214
- ssc_codegen-0.10.1/ssc_codegen/converters/lua_htmlparser.py +1396 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/py_base.py +269 -162
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/py_bs4.py +53 -14
- ssc_codegen-0.10.1/ssc_codegen/converters/py_lxml.py +303 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/py_parsel.py +65 -18
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/py_selectolax.py +50 -13
- ssc_codegen-0.10.1/ssc_codegen/converters/templates/go_goquery.py +737 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/templates/js_pure.py +54 -7
- ssc_codegen-0.10.1/ssc_codegen/converters/templates/lua_base.py +710 -0
- ssc_codegen-0.10.1/ssc_codegen/converters/templates/lua_css_compat.py +157 -0
- ssc_codegen-0.10.1/ssc_codegen/converters/templates/lua_re_compat.py +517 -0
- ssc_codegen-0.10.1/ssc_codegen/converters/templates/py_base.py +53 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/document.py +704 -137
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/document_utlis.py +31 -0
- ssc_codegen-0.10.1/ssc_codegen/json_struct.py +272 -0
- ssc_codegen-0.10.1/ssc_codegen/json_to_scc.py +179 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/schema.py +89 -9
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/static_checker/callbacks.py +11 -3
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/tokens.py +66 -36
- ssc_codegen-0.9.9/PKG-INFO +0 -251
- ssc_codegen-0.9.9/README.md +0 -215
- ssc_codegen-0.9.9/ssc_codegen/consts.py +0 -55
- ssc_codegen-0.9.9/ssc_codegen/converters/go_goquery.py +0 -1270
- ssc_codegen-0.9.9/ssc_codegen/converters/helpers.py +0 -58
- ssc_codegen-0.9.9/ssc_codegen/converters/templates/go_goquery.py +0 -450
- ssc_codegen-0.9.9/ssc_codegen/json_struct.py +0 -162
- ssc_codegen-0.9.9/ssc_codegen/json_to_scc.py +0 -80
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/.gitignore +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/LICENSE +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_/nodes_filter.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_build/__init__.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_grep_rules/js_rules.yml +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/ast_grep_rules/py_rules.yml +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/cli/ast_grep.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/converters/templates/__init__.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/logs.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/pseudo_selectors.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/static_checker/__init__.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/static_checker/base.py +0 -0
- {ssc_codegen-0.9.9 → ssc_codegen-0.10.1}/ssc_codegen/str_utils.py +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ssc_codegen
|
|
3
|
+
Version: 0.10.1
|
|
4
|
+
Summary: Python-dsl code converter to html parser for web scraping
|
|
5
|
+
Project-URL: Documentation, https://github.com/vypivshiy/selector_schema_codegen#readme
|
|
6
|
+
Project-URL: Issues, https://github.com/vypivshiy/selector_schema_codegen/issues
|
|
7
|
+
Project-URL: Source, https://github.com/vypivshiy/selector_schema_codegen
|
|
8
|
+
Project-URL: Examples, https://github.com/vypivshiy/selector_schema_codegen/examples
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
19
|
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: ast-grep-cli>=0.38.1
|
|
23
|
+
Requires-Dist: bs4>=0.0.2
|
|
24
|
+
Requires-Dist: click<8.2.0
|
|
25
|
+
Requires-Dist: colorama>=0.4.6; sys_platform == 'win32'
|
|
26
|
+
Requires-Dist: cssselect>=1.2.0
|
|
27
|
+
Requires-Dist: httpx>=0.28.1
|
|
28
|
+
Requires-Dist: ichrome>=4.0.4
|
|
29
|
+
Requires-Dist: jinja2>=3.1.6
|
|
30
|
+
Requires-Dist: lxml>=5.3.0
|
|
31
|
+
Requires-Dist: parsel>=1.10.0
|
|
32
|
+
Requires-Dist: soupsieve>=2.6
|
|
33
|
+
Requires-Dist: tinycss2>=1.4.0
|
|
34
|
+
Requires-Dist: typer>=0.15.1
|
|
35
|
+
Requires-Dist: typing-extensions; python_version < '3.11'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# Selector Schema codegen
|
|
39
|
+
|
|
40
|
+
## Introduction
|
|
41
|
+
|
|
42
|
+
ssc-gen - a python-based DSL to describe parsers for html documents, which is translated into a standalone parsing module
|
|
43
|
+
|
|
44
|
+
### For a better experience using this library, you should know:
|
|
45
|
+
|
|
46
|
+
- HTML CSS selectors (CSS3 standard min), Xpath
|
|
47
|
+
- regular expressions (PCRE)
|
|
48
|
+
|
|
49
|
+
### Project solving next problems:
|
|
50
|
+
|
|
51
|
+
- designed for SSR (server-side-render) html pages parsers, **NOT FOR REST-API, GRAPHQL ENDPOINTS**
|
|
52
|
+
- decrease boilerplate code
|
|
53
|
+
- generates independent modules from the project that can be reused.
|
|
54
|
+
- generates docstring documentation and the signature of the parser output.
|
|
55
|
+
- for a better IDE experience, generates a typedefs, type annotations (if the target programming language supports it).
|
|
56
|
+
- support annotation and parsing of JSON-like strings from a document
|
|
57
|
+
- AST API codegen for developing a converter for parsing
|
|
58
|
+
|
|
59
|
+
## Support converters
|
|
60
|
+
|
|
61
|
+
Current support converters
|
|
62
|
+
|
|
63
|
+
| Language | HTML parser lib + dependencies | XPath | CSS3 | CSS4 | Generated annotations, types, structs | formatter dependency |
|
|
64
|
+
| ---------------------------------------- | -------------------------------------- | ----- | ---- | ---- | ------------------------------------- | -------------------- |
|
|
65
|
+
| Python (3.8+) | bs4, lxml | N | Y | Y | TypedDict`1`, list, dict | ruff |
|
|
66
|
+
| ... | parsel | Y | Y | N | ... | ... |
|
|
67
|
+
| ... | selectolax (lexbor) | N | Y | N | ... | ... |
|
|
68
|
+
| ... | lxml | Y | Y | N | ... | ... |
|
|
69
|
+
| js (ES6)`2` | pure (firefox/chrome extension/nodejs) | Y | Y | Y | Array, Map`3` | prettier |
|
|
70
|
+
| go (1.10+) **(UNSTABLE)** | goquery, gjson (`4`) | N | Y | N | struct(+json anchors), array, map | gofmt |
|
|
71
|
+
| lua (5.2+), luajit(2+) **(UNSTABLE)**`5` | lua-htmlparser, lrexlib(opt), dkjson | N | Y | N | EmmyLua | LuaFormatter |
|
|
72
|
+
|
|
73
|
+
- **CSS3** means support next selectors:
|
|
74
|
+
- basic: (`tag`, `.class`, `#id`, `tag1,tag2`)
|
|
75
|
+
- combined: (`div p`, `ul > li`, `h2 +p`, `title ~head`)
|
|
76
|
+
- attribute: (`a[href]`, `input[type='text']`, `a[href*='...']`, ...)
|
|
77
|
+
- CSS3 pseudo classes: (`:nth-child(n)`, `:first-child`, `:last-child`)
|
|
78
|
+
- **CSS4** means support next selectors:
|
|
79
|
+
- `:nth-of-type()`, `:where()`, `:is()`, `:not()` etc
|
|
80
|
+
- `1`this annotation type was deliberately chosen as a compromise reasons:
|
|
81
|
+
Python has many ways of serialization: `namedtuple, dataclass, attrs, pydantic, msgspec, etc`
|
|
82
|
+
- TypedDict is like a build-in dict, but with IDE and linter hint support, and you can easily implement an adapter for the required structure.
|
|
83
|
+
- `2`ES8 standart required if needed use PCRE `re.S | re.DOTALL` flag
|
|
84
|
+
- `3`js exclude build-in serialization methods, used standard Array and Map types. Focus on the singanutur documentation!
|
|
85
|
+
- `4`golang has not been tested much, there may be issues
|
|
86
|
+
- **formatter dependency** - optional dependency for prettify and fix codestyle
|
|
87
|
+
|
|
88
|
+
- `5`lua
|
|
89
|
+
- Experimental Research PoC, performance and stability are not guaranteed
|
|
90
|
+
- Priority on generation to pure lua without C-libs dependencies. using [mva/htmlparser](https://luarocks.org/modules/mva/htmlparser) and [dhkolf/dkjson](https://luarocks.org/modules/dhkolf/dkjson)
|
|
91
|
+
- Translates unsupported CSS3 selectors into the equivalent in the form of function calls:
|
|
92
|
+
- for example, `div +p` is equivalent to `CssExt.combine_plus(root:select("div"), "p")`
|
|
93
|
+
- Translates PCRE regex to [string pattern matching](https://www.lua.org/manual/5.4/manual.html#6.4.1) (with restrictions) for more information in [lua_re_compat.py ](ssc_codegen/converters/templates/lua_re_compat.py)
|
|
94
|
+
|
|
95
|
+
### Limitations
|
|
96
|
+
|
|
97
|
+
For maximum portability of the configuration to the target language:
|
|
98
|
+
|
|
99
|
+
- If possible, use CSS selectors: they are guaranteed to be converted to XPATH
|
|
100
|
+
- Unlike javascript, most html parse libs implement [CSS3 selectors standard](https://www.w3.org/TR/selectors-3/). They may not fully implement the functionality!
|
|
101
|
+
Check the html parser lib documentation aboud CSS selectors before implement code. Examples:
|
|
102
|
+
1. Several libs not support `+` operations (eg: [selectolax(modest)](https://github.com/rushter/selectolax), [dart.universal_html](https://pub.dev/packages/universal_html))
|
|
103
|
+
2. For research purpose, lua_htmlparser include converter for unsupported CSS3 query syntax
|
|
104
|
+
2. HTML parser libs maybe not supports attribute selectors: `*=`, `~=`, `|=`, `^=`, `$=`
|
|
105
|
+
3. Several libs not support pseudo classes (eg: standard [dart.html](https://dart.dev/libraries/dart-html) lib miss this feature).
|
|
106
|
+
|
|
107
|
+
## Getting started
|
|
108
|
+
|
|
109
|
+
ssc_gen required python 3.10 version or higher
|
|
110
|
+
|
|
111
|
+
### Install
|
|
112
|
+
|
|
113
|
+
pip:
|
|
114
|
+
|
|
115
|
+
```shell
|
|
116
|
+
pip install ssc_codegen
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
uv:
|
|
120
|
+
|
|
121
|
+
```shell
|
|
122
|
+
uv pip install ssc_codegen
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Example
|
|
126
|
+
|
|
127
|
+
### Create a file `schema.py` with:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from ssc_codegen import ItemSchema, D
|
|
131
|
+
|
|
132
|
+
class HelloWorld(ItemSchema):
|
|
133
|
+
title = D().css('title').text()
|
|
134
|
+
a_hrefs = D().css_all('a').attr('href')
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### try it in cli
|
|
138
|
+
|
|
139
|
+
> [!note]
|
|
140
|
+
> this tools developed for testing purposes, not for web-scraping tasks
|
|
141
|
+
|
|
142
|
+
### eval from file
|
|
143
|
+
|
|
144
|
+
Download any html file and pass as argument:
|
|
145
|
+
|
|
146
|
+
```shell
|
|
147
|
+
ssc-gen parse-from-file index.html -t schema.py:HelloWorld
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Short options descriptions:
|
|
151
|
+
|
|
152
|
+
- `-t --target` - config schema file and class from where to start the parser
|
|
153
|
+
|
|
154
|
+

|
|
155
|
+
|
|
156
|
+
### send GET request to url and parse response
|
|
157
|
+
|
|
158
|
+
```shell
|
|
159
|
+
ssc-gen parse-from-url https://example.com -t schema.py:HelloWorld
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+

|
|
163
|
+
|
|
164
|
+
### send request via Chromium browser (CDP protocol)
|
|
165
|
+
|
|
166
|
+
```shell
|
|
167
|
+
ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
> [!note]
|
|
171
|
+
> if script cannot found chrome executable - provide it manually:
|
|
172
|
+
|
|
173
|
+
```shell
|
|
174
|
+
ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld -sc /usr/bin/chromium
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Convert to code
|
|
178
|
+
|
|
179
|
+
Convert to code for use in projects:
|
|
180
|
+
|
|
181
|
+
> [!note]
|
|
182
|
+
> for example, used js: it can be fast test in developer console
|
|
183
|
+
|
|
184
|
+
```shell
|
|
185
|
+
ssc-gen js schema.py -o .
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Code output looks like this:
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
// autogenerated by ssc-gen DO NOT_EDIT
|
|
192
|
+
/***
|
|
193
|
+
*
|
|
194
|
+
* {
|
|
195
|
+
* "title": "String",
|
|
196
|
+
* "a_hrefs": "Array<String>"
|
|
197
|
+
* }*/
|
|
198
|
+
class HelloWorld {
|
|
199
|
+
constructor(doc) {
|
|
200
|
+
if (typeof doc === "string") {
|
|
201
|
+
this._doc = new DOMParser().parseFromString(doc, "text/html");
|
|
202
|
+
} else if (doc instanceof Document || doc instanceof Element) {
|
|
203
|
+
this._doc = doc;
|
|
204
|
+
} else {
|
|
205
|
+
throw new Error("Invalid input: Expected a Document, Element, or string");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_parseTitle(v) {
|
|
210
|
+
let v0 = v.querySelector("title");
|
|
211
|
+
return typeof v0.textContent === "undefined"
|
|
212
|
+
? v0.documentElement.textContent
|
|
213
|
+
: v0.textContent;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
_parseAHrefs(v) {
|
|
217
|
+
let v0 = Array.from(v.querySelectorAll("a"));
|
|
218
|
+
return v0.map((e) => e.getAttribute("href"));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
parse() {
|
|
222
|
+
return {
|
|
223
|
+
title: this._parseTitle(this._doc),
|
|
224
|
+
a_hrefs: this._parseAHrefs(this._doc),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Copy code output and past to developer console:
|
|
231
|
+
|
|
232
|
+
Print output:
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
alert(JSON.stringify(new HelloWorld(document).parse()));
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+

|
|
239
|
+
|
|
240
|
+
You can use any html source:
|
|
241
|
+
|
|
242
|
+
- parse from html files
|
|
243
|
+
- parse from http responses
|
|
244
|
+
- parse from browsers: playwright, selenium, chrome-cdp, etc.
|
|
245
|
+
- call curl in shell and parse STDIN
|
|
246
|
+
- use in STDIN pipelines with third-party tools like [projectdiscovery/httpx](https://github.com/projectdiscovery/httpx)
|
|
247
|
+
|
|
248
|
+
## See also
|
|
249
|
+
|
|
250
|
+
- [Brief](docs/briefing.md) about css selectors and regular expressions.
|
|
251
|
+
- [Quickstart](docs/quickstart.md) about css selectors and regular expressions.
|
|
252
|
+
- [Tutorial](docs/tutorial.md) basic usage ssc-gen
|
|
253
|
+
- [AST reference](docs/ast_reference.md) about generation code from AST
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Selector Schema codegen
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
ssc-gen - a python-based DSL to describe parsers for html documents, which is translated into a standalone parsing module
|
|
6
|
+
|
|
7
|
+
### For a better experience using this library, you should know:
|
|
8
|
+
|
|
9
|
+
- HTML CSS selectors (CSS3 standard min), Xpath
|
|
10
|
+
- regular expressions (PCRE)
|
|
11
|
+
|
|
12
|
+
### Project solving next problems:
|
|
13
|
+
|
|
14
|
+
- designed for SSR (server-side-render) html pages parsers, **NOT FOR REST-API, GRAPHQL ENDPOINTS**
|
|
15
|
+
- decrease boilerplate code
|
|
16
|
+
- generates independent modules from the project that can be reused.
|
|
17
|
+
- generates docstring documentation and the signature of the parser output.
|
|
18
|
+
- for a better IDE experience, generates a typedefs, type annotations (if the target programming language supports it).
|
|
19
|
+
- support annotation and parsing of JSON-like strings from a document
|
|
20
|
+
- AST API codegen for developing a converter for parsing
|
|
21
|
+
|
|
22
|
+
## Support converters
|
|
23
|
+
|
|
24
|
+
Current support converters
|
|
25
|
+
|
|
26
|
+
| Language | HTML parser lib + dependencies | XPath | CSS3 | CSS4 | Generated annotations, types, structs | formatter dependency |
|
|
27
|
+
| ---------------------------------------- | -------------------------------------- | ----- | ---- | ---- | ------------------------------------- | -------------------- |
|
|
28
|
+
| Python (3.8+) | bs4, lxml | N | Y | Y | TypedDict`1`, list, dict | ruff |
|
|
29
|
+
| ... | parsel | Y | Y | N | ... | ... |
|
|
30
|
+
| ... | selectolax (lexbor) | N | Y | N | ... | ... |
|
|
31
|
+
| ... | lxml | Y | Y | N | ... | ... |
|
|
32
|
+
| js (ES6)`2` | pure (firefox/chrome extension/nodejs) | Y | Y | Y | Array, Map`3` | prettier |
|
|
33
|
+
| go (1.10+) **(UNSTABLE)** | goquery, gjson (`4`) | N | Y | N | struct(+json anchors), array, map | gofmt |
|
|
34
|
+
| lua (5.2+), luajit(2+) **(UNSTABLE)**`5` | lua-htmlparser, lrexlib(opt), dkjson | N | Y | N | EmmyLua | LuaFormatter |
|
|
35
|
+
|
|
36
|
+
- **CSS3** means support next selectors:
|
|
37
|
+
- basic: (`tag`, `.class`, `#id`, `tag1,tag2`)
|
|
38
|
+
- combined: (`div p`, `ul > li`, `h2 +p`, `title ~head`)
|
|
39
|
+
- attribute: (`a[href]`, `input[type='text']`, `a[href*='...']`, ...)
|
|
40
|
+
- CSS3 pseudo classes: (`:nth-child(n)`, `:first-child`, `:last-child`)
|
|
41
|
+
- **CSS4** means support next selectors:
|
|
42
|
+
- `:nth-of-type()`, `:where()`, `:is()`, `:not()` etc
|
|
43
|
+
- `1`this annotation type was deliberately chosen as a compromise reasons:
|
|
44
|
+
Python has many ways of serialization: `namedtuple, dataclass, attrs, pydantic, msgspec, etc`
|
|
45
|
+
- TypedDict is like a build-in dict, but with IDE and linter hint support, and you can easily implement an adapter for the required structure.
|
|
46
|
+
- `2`ES8 standart required if needed use PCRE `re.S | re.DOTALL` flag
|
|
47
|
+
- `3`js exclude build-in serialization methods, used standard Array and Map types. Focus on the singanutur documentation!
|
|
48
|
+
- `4`golang has not been tested much, there may be issues
|
|
49
|
+
- **formatter dependency** - optional dependency for prettify and fix codestyle
|
|
50
|
+
|
|
51
|
+
- `5`lua
|
|
52
|
+
- Experimental Research PoC, performance and stability are not guaranteed
|
|
53
|
+
- Priority on generation to pure lua without C-libs dependencies. using [mva/htmlparser](https://luarocks.org/modules/mva/htmlparser) and [dhkolf/dkjson](https://luarocks.org/modules/dhkolf/dkjson)
|
|
54
|
+
- Translates unsupported CSS3 selectors into the equivalent in the form of function calls:
|
|
55
|
+
- for example, `div +p` is equivalent to `CssExt.combine_plus(root:select("div"), "p")`
|
|
56
|
+
- Translates PCRE regex to [string pattern matching](https://www.lua.org/manual/5.4/manual.html#6.4.1) (with restrictions) for more information in [lua_re_compat.py ](ssc_codegen/converters/templates/lua_re_compat.py)
|
|
57
|
+
|
|
58
|
+
### Limitations
|
|
59
|
+
|
|
60
|
+
For maximum portability of the configuration to the target language:
|
|
61
|
+
|
|
62
|
+
- If possible, use CSS selectors: they are guaranteed to be converted to XPATH
|
|
63
|
+
- Unlike javascript, most html parse libs implement [CSS3 selectors standard](https://www.w3.org/TR/selectors-3/). They may not fully implement the functionality!
|
|
64
|
+
Check the html parser lib documentation aboud CSS selectors before implement code. Examples:
|
|
65
|
+
1. Several libs not support `+` operations (eg: [selectolax(modest)](https://github.com/rushter/selectolax), [dart.universal_html](https://pub.dev/packages/universal_html))
|
|
66
|
+
2. For research purpose, lua_htmlparser include converter for unsupported CSS3 query syntax
|
|
67
|
+
2. HTML parser libs maybe not supports attribute selectors: `*=`, `~=`, `|=`, `^=`, `$=`
|
|
68
|
+
3. Several libs not support pseudo classes (eg: standard [dart.html](https://dart.dev/libraries/dart-html) lib miss this feature).
|
|
69
|
+
|
|
70
|
+
## Getting started
|
|
71
|
+
|
|
72
|
+
ssc_gen required python 3.10 version or higher
|
|
73
|
+
|
|
74
|
+
### Install
|
|
75
|
+
|
|
76
|
+
pip:
|
|
77
|
+
|
|
78
|
+
```shell
|
|
79
|
+
pip install ssc_codegen
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
uv:
|
|
83
|
+
|
|
84
|
+
```shell
|
|
85
|
+
uv pip install ssc_codegen
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Example
|
|
89
|
+
|
|
90
|
+
### Create a file `schema.py` with:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from ssc_codegen import ItemSchema, D
|
|
94
|
+
|
|
95
|
+
class HelloWorld(ItemSchema):
|
|
96
|
+
title = D().css('title').text()
|
|
97
|
+
a_hrefs = D().css_all('a').attr('href')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### try it in cli
|
|
101
|
+
|
|
102
|
+
> [!note]
|
|
103
|
+
> this tools developed for testing purposes, not for web-scraping tasks
|
|
104
|
+
|
|
105
|
+
### eval from file
|
|
106
|
+
|
|
107
|
+
Download any html file and pass as argument:
|
|
108
|
+
|
|
109
|
+
```shell
|
|
110
|
+
ssc-gen parse-from-file index.html -t schema.py:HelloWorld
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Short options descriptions:
|
|
114
|
+
|
|
115
|
+
- `-t --target` - config schema file and class from where to start the parser
|
|
116
|
+
|
|
117
|
+

|
|
118
|
+
|
|
119
|
+
### send GET request to url and parse response
|
|
120
|
+
|
|
121
|
+
```shell
|
|
122
|
+
ssc-gen parse-from-url https://example.com -t schema.py:HelloWorld
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+

|
|
126
|
+
|
|
127
|
+
### send request via Chromium browser (CDP protocol)
|
|
128
|
+
|
|
129
|
+
```shell
|
|
130
|
+
ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> [!note]
|
|
134
|
+
> if script cannot found chrome executable - provide it manually:
|
|
135
|
+
|
|
136
|
+
```shell
|
|
137
|
+
ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld -sc /usr/bin/chromium
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Convert to code
|
|
141
|
+
|
|
142
|
+
Convert to code for use in projects:
|
|
143
|
+
|
|
144
|
+
> [!note]
|
|
145
|
+
> for example, used js: it can be fast test in developer console
|
|
146
|
+
|
|
147
|
+
```shell
|
|
148
|
+
ssc-gen js schema.py -o .
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Code output looks like this:
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
// autogenerated by ssc-gen DO NOT_EDIT
|
|
155
|
+
/***
|
|
156
|
+
*
|
|
157
|
+
* {
|
|
158
|
+
* "title": "String",
|
|
159
|
+
* "a_hrefs": "Array<String>"
|
|
160
|
+
* }*/
|
|
161
|
+
class HelloWorld {
|
|
162
|
+
constructor(doc) {
|
|
163
|
+
if (typeof doc === "string") {
|
|
164
|
+
this._doc = new DOMParser().parseFromString(doc, "text/html");
|
|
165
|
+
} else if (doc instanceof Document || doc instanceof Element) {
|
|
166
|
+
this._doc = doc;
|
|
167
|
+
} else {
|
|
168
|
+
throw new Error("Invalid input: Expected a Document, Element, or string");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_parseTitle(v) {
|
|
173
|
+
let v0 = v.querySelector("title");
|
|
174
|
+
return typeof v0.textContent === "undefined"
|
|
175
|
+
? v0.documentElement.textContent
|
|
176
|
+
: v0.textContent;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_parseAHrefs(v) {
|
|
180
|
+
let v0 = Array.from(v.querySelectorAll("a"));
|
|
181
|
+
return v0.map((e) => e.getAttribute("href"));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
parse() {
|
|
185
|
+
return {
|
|
186
|
+
title: this._parseTitle(this._doc),
|
|
187
|
+
a_hrefs: this._parseAHrefs(this._doc),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Copy code output and past to developer console:
|
|
194
|
+
|
|
195
|
+
Print output:
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
alert(JSON.stringify(new HelloWorld(document).parse()));
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+

|
|
202
|
+
|
|
203
|
+
You can use any html source:
|
|
204
|
+
|
|
205
|
+
- parse from html files
|
|
206
|
+
- parse from http responses
|
|
207
|
+
- parse from browsers: playwright, selenium, chrome-cdp, etc.
|
|
208
|
+
- call curl in shell and parse STDIN
|
|
209
|
+
- use in STDIN pipelines with third-party tools like [projectdiscovery/httpx](https://github.com/projectdiscovery/httpx)
|
|
210
|
+
|
|
211
|
+
## See also
|
|
212
|
+
|
|
213
|
+
- [Brief](docs/briefing.md) about css selectors and regular expressions.
|
|
214
|
+
- [Quickstart](docs/quickstart.md) about css selectors and regular expressions.
|
|
215
|
+
- [Tutorial](docs/tutorial.md) basic usage ssc-gen
|
|
216
|
+
- [AST reference](docs/ast_reference.md) about generation code from AST
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ssc_codegen"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.10.1"
|
|
4
4
|
description = "Python-dsl code converter to html parser for web scraping "
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -20,6 +20,7 @@ dependencies = [
|
|
|
20
20
|
# https://github.com/fastapi/typer/pull/1145
|
|
21
21
|
"click<8.2.0",
|
|
22
22
|
"ast-grep-cli>=0.38.1",
|
|
23
|
+
"tinycss2>=1.4.0",
|
|
23
24
|
]
|
|
24
25
|
|
|
25
26
|
classifiers = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from ssc_codegen.document import HTMLDocument, StringDocument, ArrayDocument, AssertDocument, NestedDocument, \
|
|
1
|
+
from ssc_codegen.document import HTMLDocument, ClassVarDocument, StringDocument, ArrayDocument, AssertDocument, NestedDocument, \
|
|
2
2
|
DefaultDocument, \
|
|
3
3
|
NumericDocument, JsonDocument, BooleanDocument, DocumentFilter
|
|
4
4
|
from ssc_codegen.json_struct import Json
|
|
@@ -7,7 +7,7 @@ from ssc_codegen.schema import ItemSchema, DictSchema, ListSchema, FlatListSchem
|
|
|
7
7
|
|
|
8
8
|
setup_logger()
|
|
9
9
|
|
|
10
|
-
VERSION = "0.
|
|
10
|
+
VERSION = "0.10.1"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class __MISSING(object):
|
|
@@ -63,4 +63,30 @@ def R(default: None | str | int | float | list | __MISSING = _NO_DEFAULT) -> Doc
|
|
|
63
63
|
|
|
64
64
|
def F() -> DocumentFilter:
|
|
65
65
|
"""Shortcut as a DocumentFilter() object"""
|
|
66
|
-
return DocumentFilter()
|
|
66
|
+
return DocumentFilter()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def CV(value, self_cls_path: str | None = None, *, returns: bool = False) -> ClassVarDocument:
|
|
70
|
+
"""Shortcut as ClassVarDocument(value, self_cls, parse_returns) object
|
|
71
|
+
|
|
72
|
+
pass `self_cls_path` argument if need use this var inner schema context:
|
|
73
|
+
|
|
74
|
+
self_cls_path use case example:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
class B(ItemSchema):
|
|
78
|
+
# dont need pass class and attr names
|
|
79
|
+
C = CV("test {{}}")
|
|
80
|
+
|
|
81
|
+
class A(ItemShema):
|
|
82
|
+
# current impl cannot be extract class and field names
|
|
83
|
+
# need pass it manually
|
|
84
|
+
C1 = CV("title", "A.C1")
|
|
85
|
+
|
|
86
|
+
title = D().css(C1).text().fmt(C)
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
return ClassVarDocument(value, self_cls_path, parse_returns=returns)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .base import BaseAstNode
|
|
2
2
|
from .nodes_array import ExprIndex, ExprToListLength, ExprListStringJoin, ExprListUnique
|
|
3
|
-
from .nodes_cast import ExprNested, ExprJsonify, ExprToInt, ExprToFloat, ExprToListInt, ExprToListFloat, ExprToBool
|
|
3
|
+
from .nodes_cast import ExprNested, ExprJsonify, ExprToInt, ExprToFloat, ExprToListInt, ExprToListFloat, ExprToBool, ExprJsonifyDynamic
|
|
4
4
|
from .nodes_core import (ExprReturn,
|
|
5
5
|
ExprNoReturn,
|
|
6
6
|
ExprDefaultValueEnd,
|
|
@@ -19,13 +19,17 @@ from .nodes_core import (ExprReturn,
|
|
|
19
19
|
JsonStructField,
|
|
20
20
|
TypeDef,
|
|
21
21
|
TypeDefField,
|
|
22
|
-
ExprCallStructMethod
|
|
22
|
+
ExprCallStructMethod,
|
|
23
|
+
ExprClassVar,
|
|
24
|
+
ExprCallStructClassVar,
|
|
25
|
+
CodeStart,
|
|
26
|
+
CodeEnd)
|
|
23
27
|
from .nodes_filter import FilterOr, FilterAnd, FilterNot, FilterNotEqual, FilterEqual, FilterStrIn, \
|
|
24
28
|
FilterStrStarts, FilterStrEnds, FilterStrRe, ExprFilter, FilterStrLenEq, FilterStrLenNe, FilterStrLenLt, \
|
|
25
29
|
FilterStrLenLe, FilterStrLenGt, FilterStrLenGe
|
|
26
30
|
|
|
27
31
|
from .nodes_selectors import ExprCss, ExprCssAll, ExprXpathAll, ExprXpath, ExprGetHtmlText, ExprGetHtmlRaw, \
|
|
28
|
-
ExprGetHtmlAttr, ExprGetHtmlAttrAll, ExprGetHtmlRawAll, ExprGetHtmlTextAll, ExprMapAttrs, ExprMapAttrsAll
|
|
32
|
+
ExprGetHtmlAttr, ExprGetHtmlAttrAll, ExprGetHtmlRawAll, ExprGetHtmlTextAll, ExprMapAttrs, ExprMapAttrsAll, ExprCssElementRemove, ExprXpathElementRemove
|
|
29
33
|
from .nodes_string import ExprStringTrim, ExprStringRegex, ExprStringSplit, ExprStringReplace, ExprStringLeftTrim, \
|
|
30
34
|
ExprStringFormat, ExprListStringTrim, ExprStringRegexAll, ExprStringRegexSub, ExprStringRightTrim, \
|
|
31
35
|
ExprListStringFormat, ExprListStringReplace, ExprListStringRightTrim, ExprListStringLeftTrim, \
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from typing import (
|
|
3
|
+
Callable,
|
|
3
4
|
ClassVar,
|
|
4
5
|
Mapping,
|
|
5
6
|
TypeVar,
|
|
@@ -9,10 +10,14 @@ from typing import (
|
|
|
9
10
|
Optional,
|
|
10
11
|
MutableSequence,
|
|
11
12
|
Sequence,
|
|
13
|
+
TYPE_CHECKING,
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
from ssc_codegen.tokens import TokenType, VariableType
|
|
15
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ssc_codegen.ast_.nodes_core import ExprClassVar
|
|
20
|
+
|
|
16
21
|
T_ARGUMENTS = TypeVar("T_ARGUMENTS")
|
|
17
22
|
T_MAPPING_FIELD = TypeVar("T_MAPPING_FIELD", bound=Mapping[str, Any])
|
|
18
23
|
T_EMPTY_KWARGS = TypedDict("T_EMPTY_KWARGS", {})
|
|
@@ -31,7 +36,10 @@ class BaseAstNode(Generic[T_MAPPING_FIELD, T_ARGUMENTS]):
|
|
|
31
36
|
# used for type check expressions
|
|
32
37
|
accept_type: VariableType = VariableType.ANY
|
|
33
38
|
ret_type: VariableType = VariableType.ANY
|
|
34
|
-
exclude_types: Sequence[VariableType] = field(
|
|
39
|
+
exclude_types: Sequence[VariableType] = field(
|
|
40
|
+
default_factory=tuple, repr=False
|
|
41
|
+
)
|
|
42
|
+
classvar_hooks: dict[str, "ExprClassVar"] = field(default_factory=dict)
|
|
35
43
|
|
|
36
44
|
def unpack_args(self) -> T_ARGUMENTS:
|
|
37
45
|
"""extract all values from kwargs field wout keys"""
|
|
@@ -67,6 +75,39 @@ class BaseAstNode(Generic[T_MAPPING_FIELD, T_ARGUMENTS]):
|
|
|
67
75
|
return self.kwargs[name]
|
|
68
76
|
raise e
|
|
69
77
|
|
|
78
|
+
def find_node(
|
|
79
|
+
self, func: Callable[["BaseAstNode"], bool]
|
|
80
|
+
) -> Optional["BaseAstNode"]:
|
|
81
|
+
"""find nodes in body by func filter
|
|
82
|
+
|
|
83
|
+
return None if node not founded or node.body is empty
|
|
84
|
+
"""
|
|
85
|
+
if len(self.body) == 0:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
for n in self.body:
|
|
89
|
+
if func(n):
|
|
90
|
+
return n
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def find_node_all(
|
|
94
|
+
self, func: Callable[["BaseAstNode"], bool]
|
|
95
|
+
) -> "BaseAstNode":
|
|
96
|
+
"""find nodes in body by func filter"""
|
|
97
|
+
if len(self.body) == 0:
|
|
98
|
+
return []
|
|
99
|
+
return [i for i in self.body if i.kind == func(i)]
|
|
100
|
+
|
|
101
|
+
def find_node_by_token(
|
|
102
|
+
self, token_type: TokenType
|
|
103
|
+
) -> Optional["BaseAstNode"]:
|
|
104
|
+
"""find node by TokenType"""
|
|
105
|
+
return self.find_node(lambda n: n.kind == token_type)
|
|
106
|
+
|
|
107
|
+
def find_nodes_by_token(self, token_type: TokenType) -> list["BaseAstNode"]:
|
|
108
|
+
"""find nodes by TokenType"""
|
|
109
|
+
return self.find_nodes_by_token(lambda n: n.kind == token_type)
|
|
110
|
+
|
|
70
111
|
|
|
71
112
|
@dataclass(kw_only=True)
|
|
72
113
|
class _DisableReprBody(BaseAstNode):
|
|
@@ -33,8 +33,12 @@ class ExprToListLength(BaseAstNode[T_EMPTY_KWARGS, tuple]):
|
|
|
33
33
|
ret_type: VariableType = VariableType.INT
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
KW_UNIQUE = TypedDict("KW_UNIQUE", {"keep_order": bool})
|
|
37
|
+
ARGS_UNIQUE = tuple[bool]
|
|
38
|
+
|
|
39
|
+
|
|
36
40
|
@dataclass(kw_only=True)
|
|
37
|
-
class ExprListUnique(BaseAstNode[
|
|
41
|
+
class ExprListUnique(BaseAstNode[KW_UNIQUE, ARGS_UNIQUE]):
|
|
38
42
|
# TODO: support LIST_INT, LIST_FLOAT
|
|
39
43
|
kind: ClassVar[TokenType] = TokenType.EXPR_LIST_UNIQUE
|
|
40
44
|
accept_type: VariableType = VariableType.LIST_STRING
|
|
@@ -58,9 +58,9 @@ class ExprNested(BaseAstNode[KW_EXPR_NESTED, EXPR_NESTED_ARGS]):
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
KW_EXPR_JSONIFY = TypedDict(
|
|
61
|
-
"KW_EXPR_JSONIFY", {"json_struct_name": str, "is_array": bool}
|
|
61
|
+
"KW_EXPR_JSONIFY", {"json_struct_name": str, "is_array": bool, "query": str}
|
|
62
62
|
)
|
|
63
|
-
ARGS_EXPR_JSONIFY = tuple[str, bool]
|
|
63
|
+
ARGS_EXPR_JSONIFY = tuple[str, bool, str]
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
@dataclass(kw_only=True)
|
|
@@ -68,3 +68,16 @@ class ExprJsonify(BaseAstNode[KW_EXPR_JSONIFY, ARGS_EXPR_JSONIFY]):
|
|
|
68
68
|
kind: ClassVar[TokenType] = TokenType.TO_JSON
|
|
69
69
|
accept_type: VariableType = VariableType.STRING
|
|
70
70
|
ret_type: VariableType = VariableType.JSON
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
KW_EXPR_JSONIFY_DYNAMIC = TypedDict("KW_EXPR_JSONIFY_DYNAMIC", {"query": str})
|
|
74
|
+
ARGS_EXPR_JSONIFY_DYNAMIC = tuple[str]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(kw_only=True)
|
|
78
|
+
class ExprJsonifyDynamic(
|
|
79
|
+
BaseAstNode[KW_EXPR_JSONIFY_DYNAMIC, ARGS_EXPR_JSONIFY_DYNAMIC]
|
|
80
|
+
):
|
|
81
|
+
kind: ClassVar[TokenType] = TokenType.TO_JSON_DYNAMIC
|
|
82
|
+
accept_type: VariableType = VariableType.STRING
|
|
83
|
+
ret_type: VariableType = VariableType.ANY # STUBBED
|