ssc_codegen 0.8.6__tar.gz → 0.8.8__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.

Potentially problematic release.


This version of ssc_codegen might be problematic. Click here for more details.

Files changed (52) hide show
  1. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/PKG-INFO +1 -1
  2. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/pyproject.toml +1 -1
  3. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/__init__.py +1 -1
  4. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_build/main.py +7 -0
  5. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/document.py +44 -18
  6. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/document_utlis.py +52 -37
  7. ssc_codegen-0.8.8/ssc_codegen/pseudo_selectors.py +64 -0
  8. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/.gitignore +0 -0
  9. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/LICENSE +0 -0
  10. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/README.md +0 -0
  11. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/_compat.py +0 -0
  12. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/__init__.py +0 -0
  13. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/base.py +0 -0
  14. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_array.py +0 -0
  15. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_cast.py +0 -0
  16. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_core.py +0 -0
  17. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_selectors.py +0 -0
  18. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_string.py +0 -0
  19. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_validate.py +0 -0
  20. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_build/__init__.py +0 -0
  21. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_build/utils.py +0 -0
  22. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/__init__.py +0 -0
  23. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/cli_callbacks.py +0 -0
  24. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/cli_utils.py +0 -0
  25. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/code_callbacks.py +0 -0
  26. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/consts.py +0 -0
  27. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/main.py +0 -0
  28. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  29. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/compiler.py +0 -0
  30. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/consts.py +0 -0
  31. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/__init__.py +0 -0
  32. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/base.py +0 -0
  33. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/go_goquery.py +0 -0
  34. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/helpers.py +0 -0
  35. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/js_pure.py +0 -0
  36. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/py_base.py +0 -0
  37. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/py_bs4.py +0 -0
  38. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/py_parsel.py +0 -0
  39. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/py_selectolax.py +0 -0
  40. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/templates/__init__.py +0 -0
  41. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/templates/go_goquery.py +0 -0
  42. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/templates/js_pure.py +0 -0
  43. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/json_struct.py +0 -0
  44. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/json_to_scc.py +0 -0
  45. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/logs.py +0 -0
  46. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/schema.py +0 -0
  47. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/selector_utils.py +0 -0
  48. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/static_checker/__init__.py +0 -0
  49. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/static_checker/base.py +0 -0
  50. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/static_checker/callbacks.py +0 -0
  51. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/str_utils.py +0 -0
  52. {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/tokens.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssc_codegen
3
- Version: 0.8.6
3
+ Version: 0.8.8
4
4
  Summary: Python-dsl code converter to html parser for web scraping
5
5
  Project-URL: Documentation, https://github.com/vypivshiy/selector_schema_codegen#readme
6
6
  Project-URL: Issues, https://github.com/vypivshiy/selector_schema_codegen/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ssc_codegen"
3
- version = "0.8.6"
3
+ version = "0.8.8"
4
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 +7,7 @@ from ssc_codegen.schema import ItemSchema, DictSchema, ListSchema, FlatListSchem
7
7
 
8
8
  setup_logger()
9
9
 
10
- VERSION = "0.8.6"
10
+ VERSION = "0.8.8"
11
11
 
12
12
 
13
13
  class __MISSING(object):
@@ -288,6 +288,13 @@ def _unwrap_default_node(
288
288
  default_type = VariableType.OPTIONAL_INT
289
289
  case VariableType.FLOAT:
290
290
  default_type = VariableType.OPTIONAL_FLOAT
291
+ case VariableType.LIST_STRING:
292
+ default_type = VariableType.OPTIONAL_LIST_STRING
293
+ case VariableType.LIST_INT:
294
+ default_type = VariableType.OPTIONAL_LIST_INT
295
+ case VariableType.LIST_FLOAT:
296
+ default_type = VariableType.OPTIONAL_LIST_FLOAT
297
+ # TODO: warning for BOOL type
291
298
  case _:
292
299
  assert_never(ret_type) # type: ignore
293
300
  expr_default_start = ExprDefaultValueStart(kwargs={"value": value})
@@ -4,7 +4,7 @@ import logging
4
4
  from typing import Type, Pattern
5
5
 
6
6
  from cssselect import SelectorSyntaxError
7
- from typing_extensions import Self
7
+ from typing_extensions import Self, assert_never
8
8
 
9
9
  from ssc_codegen.ast_ import (
10
10
  BaseAstNode,
@@ -63,6 +63,11 @@ from ssc_codegen.document_utlis import (
63
63
  unverbosify_regex,
64
64
  is_ignore_case_regex,
65
65
  )
66
+ from ssc_codegen.pseudo_selectors import (
67
+ parse_pseudo_xpath_query,
68
+ parse_pseudo_css_query,
69
+ PseudoAction,
70
+ )
66
71
  from ssc_codegen.json_struct import Json
67
72
  from ssc_codegen.schema import BaseSchema
68
73
  from ssc_codegen.selector_utils import validate_css_query, validate_xpath_query
@@ -72,6 +77,8 @@ LOGGER = logging.getLogger("ssc_gen")
72
77
 
73
78
 
74
79
  class BaseDocument:
80
+ LOGGER = LOGGER
81
+
75
82
  def __init__(self) -> None:
76
83
  self._stack: list[BaseAstNode] = []
77
84
 
@@ -98,14 +105,6 @@ class BaseDocument:
98
105
  return VariableType.DOCUMENT
99
106
  return self.stack[-1].ret_type
100
107
 
101
- @staticmethod
102
- def _raise_wrong_type_error(
103
- type_: VariableType, *expected: VariableType
104
- ) -> None:
105
- fmt_types = "(" + ",".join(i.name for i in expected) + ")"
106
- msg = f"Expected type(s): {fmt_types}, got {type_.name}"
107
- raise SyntaxError(msg)
108
-
109
108
  def _add(self, expr: BaseAstNode) -> None:
110
109
  self._stack.append(expr)
111
110
 
@@ -140,6 +139,7 @@ class HTMLDocument(BaseDocument):
140
139
  - accept: DOCUMENT, return DOCUMENT
141
140
  """
142
141
  query = " ".join(query.splitlines())
142
+ query, action = parse_pseudo_css_query(query)
143
143
  try:
144
144
  validate_css_query(query)
145
145
  except SelectorSyntaxError:
@@ -153,13 +153,29 @@ class HTMLDocument(BaseDocument):
153
153
  )
154
154
 
155
155
  self._add(ExprCss(kwargs={"query": query}))
156
+ self._pseudo_query_action_to_expr(action)
156
157
  return self
157
158
 
159
+ def _pseudo_query_action_to_expr(
160
+ self, action: tuple[PseudoAction | None, str | None]
161
+ ) -> None:
162
+ if action[0]:
163
+ match action[0]:
164
+ case "text":
165
+ self.text()
166
+ case "raw":
167
+ self.raw()
168
+ case "attr":
169
+ self.attr(action[1]) # type: ignore
170
+ case _:
171
+ assert_never(action[0])
172
+
158
173
  def xpath(self, query: str) -> Self:
159
174
  """Xpath query. returns first founded element
160
175
  - accept: DOCUMENT, return DOCUMENT
161
176
  """
162
177
  query = " ".join(query.splitlines())
178
+ query, action = parse_pseudo_xpath_query(query)
163
179
  try:
164
180
  validate_xpath_query(query)
165
181
  except SelectorSyntaxError:
@@ -172,22 +188,15 @@ class HTMLDocument(BaseDocument):
172
188
  self.stack_last_ret.name,
173
189
  )
174
190
  self._add(ExprXpath(kwargs={"query": query}))
191
+ self._pseudo_query_action_to_expr(action)
175
192
  return self
176
- # match self.stack_last_ret:
177
- # case VariableType.DOCUMENT:
178
- # self._add(HtmlXpathExpression(query=query))
179
- # case VariableType.ANY:
180
- # self._add(HtmlXpathExpression(query=query))
181
- # case _:
182
- # self._raise_wrong_type_error(
183
- # self.stack_last_ret, VariableType.DOCUMENT
184
- # )
185
193
 
186
194
  def css_all(self, query: str) -> Self:
187
195
  """Css query. returns all founded elements
188
196
  - accept: DOCUMENT, return: LIST_DOCUMENT
189
197
  """
190
198
  query = " ".join(query.splitlines())
199
+ query, action = parse_pseudo_css_query(query)
191
200
  try:
192
201
  validate_css_query(query)
193
202
  except SelectorSyntaxError:
@@ -199,6 +208,7 @@ class HTMLDocument(BaseDocument):
199
208
  self.stack_last_ret.name,
200
209
  )
201
210
  self._add(ExprCssAll(kwargs={"query": query}))
211
+ self._pseudo_query_action_to_expr(action)
202
212
  return self
203
213
 
204
214
  def xpath_all(self, query: str) -> Self:
@@ -206,6 +216,7 @@ class HTMLDocument(BaseDocument):
206
216
  - accept: DOCUMENT, return: LIST_DOCUMENT
207
217
  """
208
218
  query = " ".join(query.splitlines())
219
+ query, action = parse_pseudo_xpath_query(query)
209
220
  try:
210
221
  validate_xpath_query(query)
211
222
  except SelectorSyntaxError as e:
@@ -217,6 +228,7 @@ class HTMLDocument(BaseDocument):
217
228
  self.stack_last_ret.name,
218
229
  )
219
230
  self._add(ExprXpathAll(kwargs={"query": query}))
231
+ self._pseudo_query_action_to_expr(action)
220
232
  return self
221
233
 
222
234
  def attr(self, key: str) -> Self:
@@ -768,6 +780,13 @@ class AssertDocument(BaseDocument):
768
780
  - accept DOCUMENT, return DOCUMENT
769
781
  """
770
782
  query = " ".join(query.splitlines())
783
+ new_query, action = parse_pseudo_css_query(query)
784
+ if action[0]:
785
+ LOGGER.warning(
786
+ "is_css(%s) not support pseudo parse classes, skip", repr(query)
787
+ )
788
+ query = new_query
789
+
771
790
  try:
772
791
  validate_css_query(query)
773
792
  except SelectorSyntaxError:
@@ -790,6 +809,13 @@ class AssertDocument(BaseDocument):
790
809
  - accept DOCUMENT, return DOCUMENT
791
810
  """
792
811
  query = " ".join(query.splitlines())
812
+ new_query, action = parse_pseudo_xpath_query(query)
813
+ if action[0]:
814
+ LOGGER.warning(
815
+ "is_xpath(%s) not support pseudo parse classes, skip",
816
+ repr(query),
817
+ )
818
+ query = new_query
793
819
  try:
794
820
  validate_xpath_query(query)
795
821
  except SelectorSyntaxError:
@@ -1,6 +1,8 @@
1
+ import logging
1
2
  import re
2
3
  from typing import TYPE_CHECKING, Pattern
3
- import logging
4
+ from typing_extensions import assert_never
5
+
4
6
  from ssc_codegen.ast_ import (
5
7
  BaseAstNode,
6
8
  ExprIsCss,
@@ -10,6 +12,12 @@ from ssc_codegen.ast_ import (
10
12
  ExprXpath,
11
13
  ExprCssAll,
12
14
  )
15
+ from ssc_codegen.pseudo_selectors import (
16
+ parse_pseudo_xpath_query,
17
+ pseudo_action_to_pseudo_xpath,
18
+ parse_pseudo_css_query,
19
+ pseudo_action_to_pseudo_css,
20
+ )
13
21
  from ssc_codegen.selector_utils import css_to_xpath, xpath_to_css
14
22
  from ssc_codegen.static_checker.base import AnalyzeResult
15
23
 
@@ -18,7 +26,6 @@ LOGGER = logging.getLogger("ssc_gen")
18
26
  if TYPE_CHECKING:
19
27
  from .document import BaseDocument
20
28
 
21
-
22
29
  # https://stackoverflow.com/a/14919203
23
30
  CM1_RX = r"(?m)(?<!\\)((\\{2})*)#.*$"
24
31
  CM2_RX = r"(\\)?((\\{2})*)(#)"
@@ -88,35 +95,34 @@ def convert_css_to_xpath(
88
95
  new_stack: list[BaseAstNode] = []
89
96
 
90
97
  for expr in old_stack:
98
+ if expr.kind not in (ExprCss.kind, ExprCssAll.kind, ExprIsCss.kind):
99
+ new_stack.append(expr)
100
+ continue
101
+ query = expr.kwargs["query"]
102
+ query, pseudo_action = parse_pseudo_css_query(query)
103
+ new_query = css_to_xpath(query, prefix=prefix)
104
+ if pseudo_action[0]:
105
+ new_query += pseudo_action_to_pseudo_css(*pseudo_action)
91
106
  match expr.kind:
92
107
  case ExprCss.kind:
93
- new_expr = ExprXpath( # type: ignore[assignment]
94
- kwargs={
95
- "query": css_to_xpath(
96
- expr.kwargs["query"], prefix=prefix
97
- )
98
- }
99
- )
108
+ new_stack.append(ExprXpath(kwargs={"query": new_query}))
100
109
  case ExprCssAll.kind:
101
- new_expr = ExprXpathAll( # type: ignore[assignment]
102
- kwargs={
103
- "query": css_to_xpath(
104
- expr.kwargs["query"], prefix=prefix
105
- )
106
- }
110
+ new_stack.append(
111
+ ExprXpathAll( # type: ignore[assignment]
112
+ kwargs={"query": new_query}
113
+ )
107
114
  )
108
115
  case ExprIsCss.kind:
109
- new_expr = ExprIsXpath( # type: ignore[assignment]
110
- kwargs={
111
- "query": css_to_xpath(
112
- expr.kwargs["query"], prefix=prefix
113
- ),
114
- "msg": expr.kwargs["msg"],
115
- }
116
+ new_stack.append(
117
+ ExprIsXpath( # type: ignore[assignment]
118
+ kwargs={
119
+ "query": new_query,
120
+ "msg": expr.kwargs["msg"],
121
+ }
122
+ )
116
123
  )
117
124
  case _:
118
- new_expr = expr # type: ignore[assignment]
119
- new_stack.append(new_expr)
125
+ assert_never(expr.kind)
120
126
  doc._stack = new_stack
121
127
  return doc
122
128
 
@@ -126,24 +132,33 @@ def convert_xpath_to_css(doc: "BaseDocument") -> "BaseDocument":
126
132
  old_stack = doc.stack.copy()
127
133
  new_stack: list[BaseAstNode] = []
128
134
  for expr in old_stack:
135
+ if expr.kind not in (
136
+ ExprXpath.kind,
137
+ ExprXpathAll.kind,
138
+ ExprIsXpath.kind,
139
+ ):
140
+ new_stack.append(expr)
141
+ continue
142
+ query = expr.kwargs["query"]
143
+ query, pseudo_action = parse_pseudo_xpath_query(query)
144
+ new_query = xpath_to_css(query)
145
+ if pseudo_action[0]:
146
+ new_query += pseudo_action_to_pseudo_xpath(*pseudo_action)
129
147
  match expr.kind:
130
148
  case ExprXpath.kind:
131
- new_expr = ExprCss(
132
- kwargs={"query": xpath_to_css(expr.kwargs["query"])}
133
- )
149
+ new_stack.append(ExprCss(kwargs={"query": new_query}))
134
150
  case ExprXpathAll.kind:
135
- new_expr = ExprCssAll( # type: ignore[assignment]
136
- kwargs={"query": xpath_to_css(expr.kwargs["query"])}
137
- )
151
+ new_stack.append(ExprCssAll(kwargs={"query": new_query}))
138
152
  case ExprIsXpath.kind:
139
- new_expr = ExprIsCss( # type: ignore[assignment]
140
- kwargs={
141
- "query": xpath_to_css(expr.kwargs["query"]),
142
- "msg": expr.kwargs["msg"],
143
- }
153
+ new_stack.append(
154
+ ExprIsCss(
155
+ kwargs={
156
+ "query": new_query,
157
+ "msg": expr.kwargs["msg"],
158
+ }
159
+ )
144
160
  )
145
161
  case _:
146
- new_expr = expr # type: ignore[assignment]
147
- new_stack.append(new_expr)
162
+ assert_never(expr.kind)
148
163
  doc._stack = new_stack
149
164
  return doc
@@ -0,0 +1,64 @@
1
+ import re
2
+ from typing import Literal
3
+
4
+ PseudoAction = Literal["text", "raw", "attr"]
5
+
6
+
7
+ def parse_pseudo_css_query(
8
+ query: str,
9
+ ) -> tuple[str, tuple[PseudoAction | None, str | None]]:
10
+ """parse css selector and extract pseudo class action"""
11
+ query = query.strip()
12
+
13
+ if query.endswith("::text"):
14
+ return query[: -len("::text")], ("text", None)
15
+
16
+ if query.endswith("::raw"):
17
+ return query[: -len("::raw")], ("raw", None)
18
+
19
+ if match := re.search(r"::attr\(([^)]+)\)$", query):
20
+ return query[: match.start()], ("attr", match.group(1))
21
+
22
+ return query, (None, None)
23
+
24
+
25
+ def parse_pseudo_xpath_query(
26
+ query: str,
27
+ ) -> tuple[str, tuple[PseudoAction | None, str | None]]:
28
+ """parse xpath selector and extract pseudo class action"""
29
+ query = query.strip()
30
+
31
+ if query.endswith("/text()"):
32
+ return query[: -len("/text()")], ("text", None)
33
+
34
+ if query.endswith("/raw()"):
35
+ return query[: -len("/raw()")], ("raw", None)
36
+
37
+ if match := re.search(r"/@([\w:-]+)$", query):
38
+ return query[: match.start()], ("attr", match.group(1))
39
+
40
+ return query, (None, None)
41
+
42
+
43
+ def pseudo_action_to_pseudo_css(action: PseudoAction, arg: str | None) -> str:
44
+ match action:
45
+ case "text":
46
+ return "::text"
47
+ case "raw":
48
+ return "::raw"
49
+ case "attr":
50
+ return f"::attr({arg})"
51
+ case _:
52
+ return ""
53
+
54
+
55
+ def pseudo_action_to_pseudo_xpath(action: PseudoAction, arg: str | None) -> str:
56
+ match action:
57
+ case "text":
58
+ return "/text()"
59
+ case "raw":
60
+ return "/raw()"
61
+ case "attr":
62
+ return f"/@{arg}"
63
+ case _:
64
+ return ""
File without changes
File without changes
File without changes