ssc_codegen 0.6.1__tar.gz → 0.7.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.
Files changed (134) hide show
  1. ssc_codegen-0.7.1/PKG-INFO +238 -0
  2. ssc_codegen-0.7.1/README.md +208 -0
  3. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/pyproject.toml +25 -7
  4. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/__init__.py +9 -9
  5. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/ast_build_utils.py +20 -0
  6. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/cli/cli_utils.py +13 -0
  7. ssc_codegen-0.7.1/ssc_codegen/cli/code_callbacks.py +38 -0
  8. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/cli/consts.py +7 -3
  9. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/cli/main.py +227 -5
  10. ssc_codegen-0.7.1/ssc_codegen/cli/runtime_parse_runners.py +72 -0
  11. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/compiler.py +4 -1
  12. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/dart_universal_html.py +8 -7
  13. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/js_pure.py +4 -2
  14. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/json_to_schema.py +1 -1
  15. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/py_parsel.py +6 -4
  16. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/py_selectolax.py +1 -1
  17. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/templates/dart.py +2 -1
  18. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/templates/js.py +14 -26
  19. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/document.py +25 -14
  20. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/document_utlis.py +12 -5
  21. ssc_codegen-0.7.1/ssc_codegen/str_utils.py +182 -0
  22. ssc_codegen-0.6.1/.idea/.gitignore +0 -8
  23. ssc_codegen-0.6.1/.idea/dbnavigator.xml +0 -404
  24. ssc_codegen-0.6.1/.idea/discord.xml +0 -7
  25. ssc_codegen-0.6.1/.idea/inspectionProfiles/profiles_settings.xml +0 -6
  26. ssc_codegen-0.6.1/.idea/libraries/Dart_SDK.xml +0 -29
  27. ssc_codegen-0.6.1/.idea/misc.xml +0 -7
  28. ssc_codegen-0.6.1/.idea/modules.xml +0 -8
  29. ssc_codegen-0.6.1/.idea/poetry.xml +0 -10
  30. ssc_codegen-0.6.1/.idea/selector_schema_codegen.iml +0 -28
  31. ssc_codegen-0.6.1/.idea/vcs.xml +0 -6
  32. ssc_codegen-0.6.1/.idea/workspace.xml +0 -759
  33. ssc_codegen-0.6.1/PKG-INFO +0 -223
  34. ssc_codegen-0.6.1/README.md +0 -207
  35. ssc_codegen-0.6.1/docs/index.md +0 -13
  36. ssc_codegen-0.6.1/examples/booksToScrape/README.md +0 -39
  37. ssc_codegen-0.6.1/examples/booksToScrape/dart/booksToScrape.dart +0 -279
  38. ssc_codegen-0.6.1/examples/booksToScrape/dart/main.dart +0 -19
  39. ssc_codegen-0.6.1/examples/booksToScrape/generate_code.sh +0 -5
  40. ssc_codegen-0.6.1/examples/booksToScrape/go/booksToScrape.go +0 -326
  41. ssc_codegen-0.6.1/examples/booksToScrape/go/main.go +0 -41
  42. ssc_codegen-0.6.1/examples/booksToScrape/js/booksToScrape.js +0 -173
  43. ssc_codegen-0.6.1/examples/booksToScrape/js/main.js +0 -7
  44. ssc_codegen-0.6.1/examples/booksToScrape/manual_make.py +0 -33
  45. ssc_codegen-0.6.1/examples/booksToScrape/python/booksToScrape.py +0 -243
  46. ssc_codegen-0.6.1/examples/booksToScrape/python/main.py +0 -10
  47. ssc_codegen-0.6.1/examples/booksToScrape/python/main2.py +0 -7
  48. ssc_codegen-0.6.1/examples/booksToScrape/runtime_compile.py +0 -16
  49. ssc_codegen-0.6.1/examples/booksToScrape/schemas/booksToScrape.py +0 -65
  50. ssc_codegen-0.6.1/examples/quotesToScrapeJs/index.html +0 -193
  51. ssc_codegen-0.6.1/examples/quotesToScrapeJs/main.py +0 -72
  52. ssc_codegen-0.6.1/examples/quotesToScrapeJs/parser_schema.py +0 -75
  53. ssc_codegen-0.6.1/examples/quotesToScrapeJs/schema.py +0 -27
  54. ssc_codegen-0.6.1/examples/tutorial/1_basic/index.html +0 -30
  55. ssc_codegen-0.6.1/examples/tutorial/1_basic/main.py +0 -16
  56. ssc_codegen-0.6.1/examples/tutorial/1_basic/parser_schema.py +0 -91
  57. ssc_codegen-0.6.1/examples/tutorial/1_basic/schema.py +0 -33
  58. ssc_codegen-0.6.1/examples/tutorial/2_string_operations/index.html +0 -17
  59. ssc_codegen-0.6.1/examples/tutorial/2_string_operations/main.py +0 -26
  60. ssc_codegen-0.6.1/examples/tutorial/2_string_operations/parser_schema.py +0 -164
  61. ssc_codegen-0.6.1/examples/tutorial/2_string_operations/schema.py +0 -35
  62. ssc_codegen-0.6.1/examples/tutorial/3_default_values/index.html +0 -10
  63. ssc_codegen-0.6.1/examples/tutorial/3_default_values/main.py +0 -16
  64. ssc_codegen-0.6.1/examples/tutorial/3_default_values/parser_schema.py +0 -86
  65. ssc_codegen-0.6.1/examples/tutorial/3_default_values/schema.py +0 -24
  66. ssc_codegen-0.6.1/examples/tutorial/4_validation/bad_index.html +0 -10
  67. ssc_codegen-0.6.1/examples/tutorial/4_validation/index.html +0 -10
  68. ssc_codegen-0.6.1/examples/tutorial/4_validation/main.py +0 -18
  69. ssc_codegen-0.6.1/examples/tutorial/4_validation/parser_schema.py +0 -69
  70. ssc_codegen-0.6.1/examples/tutorial/4_validation/schema.py +0 -33
  71. ssc_codegen-0.6.1/examples/tutorial/5_schema_types/index.html +0 -39
  72. ssc_codegen-0.6.1/examples/tutorial/5_schema_types/main.py +0 -19
  73. ssc_codegen-0.6.1/examples/tutorial/5_schema_types/parser_schema.py +0 -170
  74. ssc_codegen-0.6.1/examples/tutorial/5_schema_types/schema.py +0 -41
  75. ssc_codegen-0.6.1/examples/tutorial/6_nested_schemas/index.html +0 -33
  76. ssc_codegen-0.6.1/examples/tutorial/6_nested_schemas/main.py +0 -70
  77. ssc_codegen-0.6.1/examples/tutorial/6_nested_schemas/parser_schema.py +0 -345
  78. ssc_codegen-0.6.1/examples/tutorial/6_nested_schemas/schema.py +0 -46
  79. ssc_codegen-0.6.1/examples/tutorial/7_json_extract/index.html +0 -15
  80. ssc_codegen-0.6.1/examples/tutorial/7_json_extract/main.py +0 -25
  81. ssc_codegen-0.6.1/examples/tutorial/7_json_extract/parser_schema.py +0 -47
  82. ssc_codegen-0.6.1/examples/tutorial/7_json_extract/raw_json.json +0 -1
  83. ssc_codegen-0.6.1/examples/tutorial/7_json_extract/schema.py +0 -23
  84. ssc_codegen-0.6.1/examples/tutorial/7_json_extract/template_json_schema.py +0 -10
  85. ssc_codegen-0.6.1/examples/tutorial/readme.md +0 -27
  86. ssc_codegen-0.6.1/main.py +0 -5
  87. ssc_codegen-0.6.1/scripts/check.sh +0 -15
  88. ssc_codegen-0.6.1/scripts/fix.sh +0 -12
  89. ssc_codegen-0.6.1/scripts/ver_patch.sh +0 -10
  90. ssc_codegen-0.6.1/scripts/version.py +0 -82
  91. ssc_codegen-0.6.1/ssc_codegen/str_utils.py +0 -81
  92. ssc_codegen-0.6.1/tests/converter_tools/test_str_utils.py +0 -83
  93. ssc_codegen-0.6.1/tests/converter_tools/test_variable.py +0 -32
  94. ssc_codegen-0.6.1/tests/converters/helpers_converters.py +0 -58
  95. ssc_codegen-0.6.1/tests/converters/test_buildin_converters.py +0 -127
  96. ssc_codegen-0.6.1/tests/converters/test_json_converter.py +0 -16
  97. ssc_codegen-0.6.1/tests/document/test_document_array.py +0 -33
  98. ssc_codegen-0.6.1/tests/document/test_document_assert.py +0 -25
  99. ssc_codegen-0.6.1/tests/document/test_document_invalid_expr.py +0 -47
  100. ssc_codegen-0.6.1/tests/document/test_document_regex.py +0 -32
  101. ssc_codegen-0.6.1/tests/document/test_document_select.py +0 -46
  102. ssc_codegen-0.6.1/tests/document/test_document_text.py +0 -30
  103. ssc_codegen-0.6.1/tests/document/test_nested.py +0 -33
  104. ssc_codegen-0.6.1/tests/document/test_selector_converters.py +0 -133
  105. ssc_codegen-0.6.1/tests/schema/helpers.py +0 -34
  106. ssc_codegen-0.6.1/tests/schema/test_default_expr.py +0 -25
  107. ssc_codegen-0.6.1/tests/schema/test_sc_magic_methods.py +0 -174
  108. ssc_codegen-0.6.1/tests/schema/test_sc_mro.py +0 -31
  109. ssc_codegen-0.6.1/tests/utils/test_convert_query.py +0 -24
  110. ssc_codegen-0.6.1/tests/utils/test_go_unimport.py +0 -146
  111. ssc_codegen-0.6.1/tests/utils/test_validate_query.py +0 -43
  112. ssc_codegen-0.6.1/uv.lock +0 -597
  113. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/.gitignore +0 -0
  114. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/LICENSE +0 -0
  115. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/_compat.py +0 -0
  116. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/ast_builder.py +0 -0
  117. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/ast_ssc.py +0 -0
  118. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/cli/__init__.py +0 -0
  119. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/cli/cli_callbacks.py +0 -0
  120. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/consts.py +0 -0
  121. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/__init__.py +0 -0
  122. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/ast_utils.py +0 -0
  123. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/base.py +0 -0
  124. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/go_goquery.py +0 -0
  125. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/py_base.py +0 -0
  126. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/py_bs4.py +0 -0
  127. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/py_scrapy.py +0 -0
  128. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/templates/go.py +0 -0
  129. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/templates/py.py +0 -0
  130. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/converters/templates/template_bindings.py +0 -0
  131. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/json_struct.py +0 -0
  132. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/schema.py +0 -0
  133. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/selector_utils.py +0 -0
  134. {ssc_codegen-0.6.1 → ssc_codegen-0.7.1}/ssc_codegen/tokens.py +0 -0
@@ -0,0 +1,238 @@
1
+ Metadata-Version: 2.4
2
+ Name: ssc_codegen
3
+ Version: 0.7.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: cssselect>=1.2.0
23
+ Requires-Dist: httpx>=0.28.1
24
+ Requires-Dist: ichrome>=4.0.4
25
+ Requires-Dist: lxml>=5.3.0
26
+ Requires-Dist: parsel>=1.10.0
27
+ Requires-Dist: typer>=0.15.1
28
+ Requires-Dist: typing-extensions; python_version < '3.11'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Selector Schema codegen
32
+
33
+ ## Introduction
34
+
35
+ ssc-gen - based-python DSL language for writing html parsers in dataclass style for converting to targeting language.
36
+
37
+ Project solving next problems:
38
+
39
+ - boilerplate code
40
+ - create types (type annotations) and documentation
41
+ - simplify code support
42
+ - portability to other languages
43
+
44
+ ## Support converters
45
+
46
+ Current support converters
47
+
48
+
49
+ | Language | Library (html parser backend) | XPath Support | CSS Support | Generated types | Code formatter |
50
+ |---------------|--------------------------------------------------------------|---------------|-------------|------------------------------------------|----------------|
51
+ | Python (3.8+) | bs4 | N | Y | TypedDict*, list, dict | ruff |
52
+ | ... | parsel | Y | Y | ... | - |
53
+ | ... | selectolax (modest) | N | Y | ... | - |
54
+ | ... | scrapy (possibly use parsel - pass Response.selector object) | Y | Y | ... | - |
55
+ | Dart (3) | universal_html | N | Y | record, List, Map | dart format |
56
+ | js (ES6) | pure (firefox/chrome) | Y | Y | Array, Map** | - |
57
+ | go (1.10+) | goquery | N | Y | struct(json anchors include), array, map | gofmt |
58
+
59
+ - *this annotation type was deliberately chosen as a compromise reasons.
60
+ Python has many ways of serialization: `dataclass, namedtuple, attrs, pydantic`
61
+ - 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.
62
+ - **js not exists build-in serialization methods
63
+
64
+ ### Limitations
65
+
66
+ For maximum portability of the configuration to the target language:
67
+
68
+ - Use CSS selectors: they are guaranteed to be converted to XPATH
69
+ - Unlike javascript, most html parse libs implement [CSS3 selectors standard](https://www.w3.org/TR/selectors-3/)
70
+ - basic selectors: (`tag`, `.class`, `#id`)
71
+ - combined: (`div p`, `ul > li`, `h2 +p`\[1])
72
+ - attribute: (`a[href]`, `input[type='text']`)\[2]
73
+ - pseudo classes: (`:nth-child(n)`, `:first-child`, `:last-child`)\[3]
74
+ - **often, not support more complex, dynamic styles**: (`:has()`, `:nth-of-type()`, `:where()`, `:is()`)
75
+
76
+ 1. Several libs not support `+` operations (eg: [selectolax(modest)](https://github.com/rushter/selectolax), [dart.universal_html](https://pub.dev/packages/universal_html))
77
+ 2. Often, web scraping libs not supports attribute operations like `*=`, `~=`, `|=`, `^=` and `$=`
78
+ 3. Several libs not support pseudo classes (eg: standard [dart.html](https://dart.dev/libraries/dart-html) lib miss this feature).
79
+ This project will not implement converters with such a cons
80
+
81
+ ## Getting started
82
+
83
+ ssc_gen required python 3.10 version or higher
84
+
85
+ ### Install
86
+
87
+ pip:
88
+
89
+ ```shell
90
+ pip install ssc_codegen
91
+ ```
92
+
93
+ uv:
94
+
95
+ ```shell
96
+ uv pip install ssc_codegen
97
+ ```
98
+
99
+ as cli converter tool:
100
+
101
+ | package manager | command |
102
+ |-----------------|-------------------------------|
103
+ | pipx | `pipx install ssc_codegen` |
104
+ | uv | `uv tool install ssc_codegen` |
105
+
106
+ ## Example
107
+
108
+ ### Create a file `schema.py` with:
109
+
110
+ ```python
111
+ from ssc_codegen import ItemSchema, D
112
+
113
+ class HelloWorld(ItemSchema):
114
+ title = D().css('title').text()
115
+ a_hrefs = D().css_all('a').attr('href')
116
+ ```
117
+
118
+ ### try it in cli
119
+
120
+ >[!note]
121
+ > this tools developed for testing purposes, not for web-scraping
122
+
123
+ ### from file
124
+
125
+ >[!warning]
126
+ > DO NOT PASS CONFIGS FROM UNKNOWN SOURCES:
127
+ >
128
+ > PYTHON CODE FROM CONFIGS COMPILE IN RUNTIME WOUT SECURITY CHECKS!!!
129
+
130
+ Download any html file and pass as argument:
131
+
132
+ ```shell
133
+ ssc-gen parse-from-file index.html -t schema.py:HelloWorld
134
+ ```
135
+
136
+ Short options descriptions:
137
+
138
+ - `-t --target` - config schema file and class from where to start the parser
139
+
140
+ ![out1](docs/assets/parse_from_file.gif)
141
+
142
+ ### from url
143
+
144
+ ```shell
145
+ ssc-gen parse-from-url https://example.com -t schema.py:HelloWorld
146
+ ```
147
+
148
+ ![out1](docs/assets/parse_from_url.gif)
149
+ ### from Chromium browser (CDP protocol)
150
+
151
+
152
+ ```shell
153
+ ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld
154
+ ```
155
+
156
+ >[!note]
157
+ > if script cannot found chrome executable - provide it manually:
158
+
159
+ ```shell
160
+ ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld -sc /usr/bin/chromium
161
+ ```
162
+
163
+
164
+ ### Convert to code
165
+
166
+ Convert to code for use in projects:
167
+
168
+ >![note]
169
+ > for example, used js: it can be fast test in developer console
170
+
171
+
172
+ ```shell
173
+ ssc-gen js schema.py -o .
174
+ ```
175
+
176
+ Code output looks like this (code formatted by IDE):
177
+
178
+ ```javascript
179
+ // autogenerated by ssc-gen DO NOT_EDIT
180
+ /**
181
+ *
182
+ *
183
+ * {
184
+ * "title": "String",
185
+ * "a_hrefs": "Array<String>"
186
+ * }
187
+ */
188
+ class HelloWorld {
189
+ constructor(doc) {
190
+ if (typeof doc === 'string') {
191
+ this._doc = new DOMParser().parseFromString(doc, 'text/html');
192
+ } else if (doc instanceof Document || doc instanceof Element) {
193
+ this._doc = doc;
194
+ } else {
195
+ throw new Error("Invalid input: Expected a Document, Element, or string");
196
+ }
197
+ }
198
+
199
+ _parseTitle(value) {
200
+ let value1 = value.querySelector('title');
201
+ return typeof value1.textContent === "undefined" ? value1.documentElement.textContent : value1.textContent;
202
+ }
203
+
204
+ _parseAHrefs(value) {
205
+ let value1 = Array.from(value.querySelectorAll('a'));
206
+ return value1.map(e => e.getAttribute('href'));
207
+ }
208
+
209
+ parse() {
210
+ return {title: this._parseTitle(this._doc), a_hrefs: this._parseAHrefs(this._doc)};
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### copy code output and past to developer console:
216
+
217
+ Print output:
218
+
219
+ ```javascript
220
+ alert(JSON.stringify((new HelloWorld(document).parse())))
221
+ ```
222
+
223
+ ![example](assets/example.png)
224
+
225
+
226
+ You can use any html source:
227
+
228
+ - read from html file
229
+ - get from http request
230
+ - get from browser (playwright, selenium, chrome-cdp)
231
+ - paste code to developer console (js)
232
+ - or call curl in shell and parse stdin
233
+
234
+
235
+ ## See also
236
+ - [Brief](docs/brief.md) about css selectors and regular expressions.
237
+ - [Tutorial](docs/tutorial.md) how to use ssc-gen
238
+ - [Reference](docs/reference.md) about high-level API
@@ -0,0 +1,208 @@
1
+ # Selector Schema codegen
2
+
3
+ ## Introduction
4
+
5
+ ssc-gen - based-python DSL language for writing html parsers in dataclass style for converting to targeting language.
6
+
7
+ Project solving next problems:
8
+
9
+ - boilerplate code
10
+ - create types (type annotations) and documentation
11
+ - simplify code support
12
+ - portability to other languages
13
+
14
+ ## Support converters
15
+
16
+ Current support converters
17
+
18
+
19
+ | Language | Library (html parser backend) | XPath Support | CSS Support | Generated types | Code formatter |
20
+ |---------------|--------------------------------------------------------------|---------------|-------------|------------------------------------------|----------------|
21
+ | Python (3.8+) | bs4 | N | Y | TypedDict*, list, dict | ruff |
22
+ | ... | parsel | Y | Y | ... | - |
23
+ | ... | selectolax (modest) | N | Y | ... | - |
24
+ | ... | scrapy (possibly use parsel - pass Response.selector object) | Y | Y | ... | - |
25
+ | Dart (3) | universal_html | N | Y | record, List, Map | dart format |
26
+ | js (ES6) | pure (firefox/chrome) | Y | Y | Array, Map** | - |
27
+ | go (1.10+) | goquery | N | Y | struct(json anchors include), array, map | gofmt |
28
+
29
+ - *this annotation type was deliberately chosen as a compromise reasons.
30
+ Python has many ways of serialization: `dataclass, namedtuple, attrs, pydantic`
31
+ - 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.
32
+ - **js not exists build-in serialization methods
33
+
34
+ ### Limitations
35
+
36
+ For maximum portability of the configuration to the target language:
37
+
38
+ - Use CSS selectors: they are guaranteed to be converted to XPATH
39
+ - Unlike javascript, most html parse libs implement [CSS3 selectors standard](https://www.w3.org/TR/selectors-3/)
40
+ - basic selectors: (`tag`, `.class`, `#id`)
41
+ - combined: (`div p`, `ul > li`, `h2 +p`\[1])
42
+ - attribute: (`a[href]`, `input[type='text']`)\[2]
43
+ - pseudo classes: (`:nth-child(n)`, `:first-child`, `:last-child`)\[3]
44
+ - **often, not support more complex, dynamic styles**: (`:has()`, `:nth-of-type()`, `:where()`, `:is()`)
45
+
46
+ 1. Several libs not support `+` operations (eg: [selectolax(modest)](https://github.com/rushter/selectolax), [dart.universal_html](https://pub.dev/packages/universal_html))
47
+ 2. Often, web scraping libs not supports attribute operations like `*=`, `~=`, `|=`, `^=` and `$=`
48
+ 3. Several libs not support pseudo classes (eg: standard [dart.html](https://dart.dev/libraries/dart-html) lib miss this feature).
49
+ This project will not implement converters with such a cons
50
+
51
+ ## Getting started
52
+
53
+ ssc_gen required python 3.10 version or higher
54
+
55
+ ### Install
56
+
57
+ pip:
58
+
59
+ ```shell
60
+ pip install ssc_codegen
61
+ ```
62
+
63
+ uv:
64
+
65
+ ```shell
66
+ uv pip install ssc_codegen
67
+ ```
68
+
69
+ as cli converter tool:
70
+
71
+ | package manager | command |
72
+ |-----------------|-------------------------------|
73
+ | pipx | `pipx install ssc_codegen` |
74
+ | uv | `uv tool install ssc_codegen` |
75
+
76
+ ## Example
77
+
78
+ ### Create a file `schema.py` with:
79
+
80
+ ```python
81
+ from ssc_codegen import ItemSchema, D
82
+
83
+ class HelloWorld(ItemSchema):
84
+ title = D().css('title').text()
85
+ a_hrefs = D().css_all('a').attr('href')
86
+ ```
87
+
88
+ ### try it in cli
89
+
90
+ >[!note]
91
+ > this tools developed for testing purposes, not for web-scraping
92
+
93
+ ### from file
94
+
95
+ >[!warning]
96
+ > DO NOT PASS CONFIGS FROM UNKNOWN SOURCES:
97
+ >
98
+ > PYTHON CODE FROM CONFIGS COMPILE IN RUNTIME WOUT SECURITY CHECKS!!!
99
+
100
+ Download any html file and pass as argument:
101
+
102
+ ```shell
103
+ ssc-gen parse-from-file index.html -t schema.py:HelloWorld
104
+ ```
105
+
106
+ Short options descriptions:
107
+
108
+ - `-t --target` - config schema file and class from where to start the parser
109
+
110
+ ![out1](docs/assets/parse_from_file.gif)
111
+
112
+ ### from url
113
+
114
+ ```shell
115
+ ssc-gen parse-from-url https://example.com -t schema.py:HelloWorld
116
+ ```
117
+
118
+ ![out1](docs/assets/parse_from_url.gif)
119
+ ### from Chromium browser (CDP protocol)
120
+
121
+
122
+ ```shell
123
+ ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld
124
+ ```
125
+
126
+ >[!note]
127
+ > if script cannot found chrome executable - provide it manually:
128
+
129
+ ```shell
130
+ ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld -sc /usr/bin/chromium
131
+ ```
132
+
133
+
134
+ ### Convert to code
135
+
136
+ Convert to code for use in projects:
137
+
138
+ >![note]
139
+ > for example, used js: it can be fast test in developer console
140
+
141
+
142
+ ```shell
143
+ ssc-gen js schema.py -o .
144
+ ```
145
+
146
+ Code output looks like this (code formatted by IDE):
147
+
148
+ ```javascript
149
+ // autogenerated by ssc-gen DO NOT_EDIT
150
+ /**
151
+ *
152
+ *
153
+ * {
154
+ * "title": "String",
155
+ * "a_hrefs": "Array<String>"
156
+ * }
157
+ */
158
+ class HelloWorld {
159
+ constructor(doc) {
160
+ if (typeof doc === 'string') {
161
+ this._doc = new DOMParser().parseFromString(doc, 'text/html');
162
+ } else if (doc instanceof Document || doc instanceof Element) {
163
+ this._doc = doc;
164
+ } else {
165
+ throw new Error("Invalid input: Expected a Document, Element, or string");
166
+ }
167
+ }
168
+
169
+ _parseTitle(value) {
170
+ let value1 = value.querySelector('title');
171
+ return typeof value1.textContent === "undefined" ? value1.documentElement.textContent : value1.textContent;
172
+ }
173
+
174
+ _parseAHrefs(value) {
175
+ let value1 = Array.from(value.querySelectorAll('a'));
176
+ return value1.map(e => e.getAttribute('href'));
177
+ }
178
+
179
+ parse() {
180
+ return {title: this._parseTitle(this._doc), a_hrefs: this._parseAHrefs(this._doc)};
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### copy code output and past to developer console:
186
+
187
+ Print output:
188
+
189
+ ```javascript
190
+ alert(JSON.stringify((new HelloWorld(document).parse())))
191
+ ```
192
+
193
+ ![example](assets/example.png)
194
+
195
+
196
+ You can use any html source:
197
+
198
+ - read from html file
199
+ - get from http request
200
+ - get from browser (playwright, selenium, chrome-cdp)
201
+ - paste code to developer console (js)
202
+ - or call curl in shell and parse stdin
203
+
204
+
205
+ ## See also
206
+ - [Brief](docs/brief.md) about css selectors and regular expressions.
207
+ - [Tutorial](docs/tutorial.md) how to use ssc-gen
208
+ - [Reference](docs/reference.md) about high-level API
@@ -1,14 +1,31 @@
1
1
  [project]
2
2
  name = "ssc_codegen"
3
- version = "0.6.1"
4
- description = "Add your description here"
3
+ version = "0.7.1"
4
+ description = "Python-dsl code converter to html parser for web scraping "
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
8
  "cssselect>=1.2.0",
9
+ "httpx>=0.28.1",
10
+ "ichrome>=4.0.4",
9
11
  "lxml>=5.3.0",
12
+ "parsel>=1.10.0",
10
13
  "typer>=0.15.1",
11
- "typing_extensions;python_version<'3.11'"
14
+ "typing_extensions;python_version<'3.11'",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Topic :: Software Development :: Code Generators",
22
+ "Topic :: Text Processing :: Markup :: HTML",
23
+ "Topic :: Utilities",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+
12
29
  ]
13
30
 
14
31
  [project.urls]
@@ -25,12 +42,14 @@ ssc-gen = 'ssc_codegen.cli.main:main'
25
42
  requires = ["hatchling"]
26
43
  build-backend = "hatchling.build"
27
44
 
28
- [tool.setuptools]
29
- license-files = []
30
- packages=["ssc_codegen"]
45
+ [tool.hatch.build.targets.sdist]
46
+ include = [
47
+ "ssc_codegen/*",
48
+ ]
31
49
 
32
50
  [dependency-groups]
33
51
  dev = [
52
+ "coverage>=7.6.12",
34
53
  "httpx>=0.28.1",
35
54
  "mypy>=1.14.1",
36
55
  "pytest>=8.3.4",
@@ -40,7 +59,6 @@ dev = [
40
59
  tests = [
41
60
  "bs4>=0.0.2",
42
61
  "parsel>=1.10.0",
43
- "pytest>=8.3.4",
44
62
  "selectolax>=0.3.27",
45
63
  ]
46
64
 
@@ -3,7 +3,7 @@ from .document import HTMLDocument, StringDocument, ArrayDocument, AssertDocumen
3
3
  from .json_struct import Json
4
4
  from .schema import ItemSchema, DictSchema, ListSchema, FlatListSchema
5
5
 
6
- VERSION = "0.6.1"
6
+ VERSION = "0.7.1"
7
7
 
8
8
  class __MISSING(object):
9
9
  pass
@@ -18,14 +18,14 @@ class Nested(HTMLDocument, NestedDocument, ArrayDocument, AssertDocument):
18
18
  pass
19
19
 
20
20
 
21
- def D(default_value: None | str | int | float | __MISSING =_NO_DEFAULT) -> Document: # noqa
21
+ def D(default: None | str | int | float | __MISSING =_NO_DEFAULT) -> Document: # noqa
22
22
  """Shortcut as a Document() object
23
23
 
24
- :param default_value: .default() operator shortcut
24
+ :param default: .default() operator shortcut
25
25
  """
26
- if default_value==_NO_DEFAULT:
26
+ if default==_NO_DEFAULT:
27
27
  return Document()
28
- return Document().default(value=default_value) # type: ignore
28
+ return Document().default(value=default) # type: ignore
29
29
 
30
30
 
31
31
  def N() -> Nested: # noqa
@@ -33,12 +33,12 @@ def N() -> Nested: # noqa
33
33
  return Nested()
34
34
 
35
35
 
36
- def R(default_value: None | str | int | float | __MISSING =_NO_DEFAULT) -> Document: # noqa
36
+ def R(default: None | str | int | float | __MISSING =_NO_DEFAULT) -> Document: # noqa
37
37
  """Shortcut as a Document().raw() object.
38
38
  For regex and format string operations
39
39
 
40
- :param default_value: .default() operator shortcut
40
+ :param default: .default() operator shortcut
41
41
  """
42
- if default_value==_NO_DEFAULT:
42
+ if default==_NO_DEFAULT:
43
43
  return Document().raw()
44
- return Document().default(default_value).raw() # type: ignore
44
+ return Document().default(default).raw() # type: ignore
@@ -52,11 +52,31 @@ def check_schema_required_fields(schema: Type[BaseSchema]) -> Type[BaseSchema]:
52
52
  _check_required_attributes(
53
53
  schema, "__SPLIT_DOC__", "__KEY__", "__VALUE__"
54
54
  )
55
+ # delete non required fields
56
+ not_required_fields: list[str] = []
57
+ for k, v in schema.__get_mro_fields__().items():
58
+ if k not in {"__SPLIT_DOC__", "__KEY__", "__VALUE__"}:
59
+ msg = f"{schema.__name__}.{k} not required, remove"
60
+ warnings.warn(msg, category=SyntaxWarning)
61
+ not_required_fields.append(k)
62
+ schema = _remove_non_required_attributes(
63
+ schema, *not_required_fields
64
+ )
55
65
  case StructType.FLAT_LIST:
56
66
  schema = _remove_non_required_attributes(
57
67
  schema, "__KEY__", "__VALUE__"
58
68
  )
59
69
  _check_required_attributes(schema, "__SPLIT_DOC__", "__ITEM__")
70
+
71
+ not_required_fields: list[str] = [] # type: ignore
72
+ for k, v in schema.__get_mro_fields__().items():
73
+ if k not in {"__SPLIT_DOC__", "__ITEM__"}:
74
+ msg = f"{schema.__name__}.{k} not required, remove"
75
+ warnings.warn(msg, category=SyntaxWarning)
76
+ not_required_fields.append(k)
77
+ schema = _remove_non_required_attributes(
78
+ schema, *not_required_fields
79
+ )
60
80
  case _:
61
81
  msg = f"{schema.__name__}: Unknown schema type"
62
82
  raise SyntaxError(msg)
@@ -2,7 +2,9 @@ import importlib
2
2
  import re
3
3
  import warnings
4
4
  from pathlib import Path
5
+ from types import ModuleType
5
6
  from typing import TYPE_CHECKING, Protocol
7
+ import difflib
6
8
 
7
9
  from typer import BadParameter
8
10
 
@@ -77,3 +79,14 @@ def raw_json_check_keys(jsn: str) -> None:
77
79
  raise BadParameter(
78
80
  f"bad json keys count: {len(results)}, keys: {all_keys}"
79
81
  )
82
+
83
+
84
+ def suggest_class_name(module: ModuleType, target_class_name: str) -> str:
85
+ """try to suggest correct class name"""
86
+ class_names = [
87
+ name for name in dir(module) if isinstance(getattr(module, name), type)
88
+ ]
89
+
90
+ return difflib.get_close_matches(
91
+ target_class_name, class_names, n=3, cutoff=0.6
92
+ )[0]
@@ -0,0 +1,38 @@
1
+ """"""
2
+
3
+ from typing import Callable
4
+ from ssc_codegen.str_utils import (
5
+ go_unimport_naive,
6
+ py_optimize_return_naive,
7
+ py_str_format_to_fstring,
8
+ js_pure_optimize_return,
9
+ )
10
+
11
+
12
+ class BaseCodeCallback:
13
+ def __init__(
14
+ self,
15
+ *code_callbacks: Callable[[str], str],
16
+ join_sep: str = "\n",
17
+ remove_empty_lines: bool = False,
18
+ ):
19
+ self.join_sep = join_sep
20
+ self.remove_empty_lines = remove_empty_lines
21
+ self.callbacks = code_callbacks
22
+
23
+ def __call__(self, lines: list[str]) -> str:
24
+ if self.remove_empty_lines:
25
+ code = self.join_sep.join([i for i in lines if i])
26
+ else:
27
+ code = self.join_sep.join(lines)
28
+ for cb in self.callbacks:
29
+ code = cb(code)
30
+ return code
31
+
32
+
33
+ CB_PY_CODE = BaseCodeCallback(
34
+ py_optimize_return_naive, py_str_format_to_fstring
35
+ )
36
+ CB_GO_CODE = BaseCodeCallback(go_unimport_naive, remove_empty_lines=True)
37
+ CB_DART_CODE = BaseCodeCallback(remove_empty_lines=True)
38
+ CB_JS_CODE = BaseCodeCallback(js_pure_optimize_return)
@@ -1,4 +1,4 @@
1
- from enum import StrEnum
1
+ from ssc_codegen._compat import StrEnum
2
2
 
3
3
  CONVERTERS_PATH = "ssc_codegen.converters"
4
4
 
@@ -40,8 +40,12 @@ HELP_JSON_TO_SCHEMA = "convert json response to ssc-gen schema"
40
40
  CMD_VERSION = "Show version and exit"
41
41
  CMD_PY = "generate python modules"
42
42
  CMD_JS = "generate javascript modules"
43
- CMD_DART = "generate dart modules (json not implemented)"
43
+ CMD_DART = "generate dart modules"
44
44
  CMD_GO = "generate golang modules"
45
45
  CMD_JSON_GEN = """generate schema code from json.
46
- entrypoint should be: stats as dict(map) or array[map] object, not contains reserved words and key starts as _[a-z][A-Z] char
46
+ entrypoint should be: map or array object and key starts as _[a-zA-Z] char
47
47
  """
48
+
49
+ DEFAULT_UA = (
50
+ "Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"
51
+ )