ssc_codegen 0.11.0__tar.gz → 0.12.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 (67) hide show
  1. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/PKG-INFO +2 -3
  2. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/README.md +1 -1
  3. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/pyproject.toml +3 -2
  4. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/__init__.py +7 -2
  5. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/_compat.py +6 -3
  6. ssc_codegen-0.12.1/ssc_codegen/ast_/__init__.py +129 -0
  7. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_/base.py +3 -3
  8. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_/nodes_core.py +4 -4
  9. ssc_codegen-0.12.1/ssc_codegen/ast_/nodes_filter.py +285 -0
  10. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_build/builder.py +10 -6
  11. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_build/utils.py +1 -1
  12. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/cli/main.py +1 -1
  13. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/base.py +3 -2
  14. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/go_goquery.py +765 -202
  15. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/helpers.py +13 -15
  16. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/js_pure.py +271 -38
  17. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/lua_htmlparser.py +38 -0
  18. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/py_base.py +3 -3
  19. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/py_bs4.py +172 -0
  20. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/py_lxml.py +180 -0
  21. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/py_parsel.py +183 -2
  22. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/py_selectolax.py +175 -0
  23. ssc_codegen-0.12.1/ssc_codegen/converters/templates/go_goquery.py +440 -0
  24. ssc_codegen-0.12.1/ssc_codegen/converters/templates/js_pure.py +38 -0
  25. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/document.py +433 -21
  26. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/json_struct.py +2 -2
  27. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/json_to_scc.py +3 -2
  28. ssc_codegen-0.12.1/ssc_codegen/schema.py +354 -0
  29. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/static_checker/__init__.py +5 -2
  30. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/static_checker/callbacks.py +26 -2
  31. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/tokens.py +22 -1
  32. ssc_codegen-0.11.0/ssc_codegen/ast_/__init__.py +0 -40
  33. ssc_codegen-0.11.0/ssc_codegen/ast_/nodes_filter.py +0 -151
  34. ssc_codegen-0.11.0/ssc_codegen/converters/templates/go_goquery.py +0 -737
  35. ssc_codegen-0.11.0/ssc_codegen/converters/templates/js_pure.py +0 -135
  36. ssc_codegen-0.11.0/ssc_codegen/schema.py +0 -310
  37. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/.gitignore +0 -0
  38. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/LICENSE +0 -0
  39. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_/nodes_array.py +0 -0
  40. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_/nodes_cast.py +0 -0
  41. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_/nodes_selectors.py +0 -0
  42. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_/nodes_string.py +0 -0
  43. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_/nodes_validate.py +0 -0
  44. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_build/__init__.py +0 -0
  45. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_build/main.py +0 -0
  46. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_grep_rules/js_rules.yml +0 -0
  47. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/ast_grep_rules/py_rules.yml +0 -0
  48. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/cli/__init__.py +0 -0
  49. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/cli/ast_grep.py +0 -0
  50. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/cli/cli_callbacks.py +0 -0
  51. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/cli/cli_utils.py +0 -0
  52. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/cli/code_callbacks.py +0 -0
  53. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/cli/consts.py +0 -0
  54. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  55. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/compiler.py +0 -0
  56. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/__init__.py +0 -0
  57. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/templates/__init__.py +0 -0
  58. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/templates/lua_base.py +0 -0
  59. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/templates/lua_css_compat.py +0 -0
  60. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/templates/lua_re_compat.py +0 -0
  61. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/converters/templates/py_base.py +0 -0
  62. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/document_utlis.py +0 -0
  63. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/logs.py +0 -0
  64. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/pseudo_selectors.py +0 -0
  65. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/selector_utils.py +0 -0
  66. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/static_checker/base.py +0 -0
  67. {ssc_codegen-0.11.0 → ssc_codegen-0.12.1}/ssc_codegen/str_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssc_codegen
3
- Version: 0.11.0
3
+ Version: 0.12.1
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
@@ -26,7 +26,6 @@ Requires-Dist: colorama>=0.4.6; sys_platform == 'win32'
26
26
  Requires-Dist: cssselect>=1.2.0
27
27
  Requires-Dist: httpx>=0.28.1
28
28
  Requires-Dist: ichrome>=4.0.4
29
- Requires-Dist: jinja2>=3.1.6
30
29
  Requires-Dist: lxml>=5.3.0
31
30
  Requires-Dist: parsel>=1.10.0
32
31
  Requires-Dist: soupsieve>=2.6
@@ -66,7 +65,7 @@ Current support converters
66
65
  | ... | parsel | Y | Y | N | ... | ... |
67
66
  | ... | selectolax (lexbor) | N | Y | N | ... | ... |
68
67
  | ... | lxml | Y | Y | N | ... | ... |
69
- | js (ES6)`2` | pure (firefox/chrome extension/nodejs) | Y | Y | Y | Array, Map`3` | prettier |
68
+ | js (ES6)`2` | pure (firefox/chrome extension/nodejs) | Y | Y | Y | JSDoc | prettier |
70
69
  | go (1.10+) **(UNSTABLE)** | goquery, gjson (`4`) | N | Y | N | struct(+json anchors), array, map | gofmt |
71
70
  | lua (5.2+), luajit(2+) **(UNSTABLE)**`5` | lua-htmlparser, lrexlib(opt), dkjson | N | Y | N | EmmyLua | LuaFormatter |
72
71
 
@@ -29,7 +29,7 @@ Current support converters
29
29
  | ... | parsel | Y | Y | N | ... | ... |
30
30
  | ... | selectolax (lexbor) | N | Y | N | ... | ... |
31
31
  | ... | lxml | Y | Y | N | ... | ... |
32
- | js (ES6)`2` | pure (firefox/chrome extension/nodejs) | Y | Y | Y | Array, Map`3` | prettier |
32
+ | js (ES6)`2` | pure (firefox/chrome extension/nodejs) | Y | Y | Y | JSDoc | prettier |
33
33
  | go (1.10+) **(UNSTABLE)** | goquery, gjson (`4`) | N | Y | N | struct(+json anchors), array, map | gofmt |
34
34
  | lua (5.2+), luajit(2+) **(UNSTABLE)**`5` | lua-htmlparser, lrexlib(opt), dkjson | N | Y | N | EmmyLua | LuaFormatter |
35
35
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ssc_codegen"
3
- version = "0.11.0"
3
+ version = "0.12.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"
@@ -10,7 +10,6 @@ dependencies = [
10
10
  "cssselect>=1.2.0",
11
11
  "httpx>=0.28.1",
12
12
  "ichrome>=4.0.4",
13
- "jinja2>=3.1.6",
14
13
  "lxml>=5.3.0",
15
14
  "parsel>=1.10.0",
16
15
  "soupsieve>=2.6",
@@ -61,8 +60,10 @@ dev = [
61
60
  "coverage>=7.6.12",
62
61
  "httpx>=0.28.1",
63
62
  "mypy>=1.14.1",
63
+ "parsel>=1.10.0",
64
64
  "pytest>=8.3.4",
65
65
  "ruff>=0.9.3",
66
+ "selectolax>=0.3.27",
66
67
  ]
67
68
  # python generated code tests
68
69
  tests = [
@@ -1,4 +1,4 @@
1
- from ssc_codegen.document import HTMLDocument, ClassVarDocument, StringDocument, ArrayDocument, AssertDocument, NestedDocument, \
1
+ from ssc_codegen.document import DocumentElementsFilter, 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.11.0"
10
+ VERSION = "0.12.1"
11
11
 
12
12
 
13
13
  class __MISSING(object):
@@ -68,6 +68,11 @@ def F() -> DocumentFilter:
68
68
  return DocumentFilter()
69
69
 
70
70
 
71
+ def FE() -> DocumentElementsFilter:
72
+ """Shortcut as a DocumentElementsFilter() object"""
73
+ return DocumentElementsFilter()
74
+
75
+
71
76
  def CV(value: int | str | float | bool | None | list, self_cls_path: str | None = None, *, returns: bool = False) -> ClassVarDocument:
72
77
  """Shortcut as ClassVarDocument(value, self_cls, parse_returns) object
73
78
 
@@ -1,7 +1,10 @@
1
1
  import sys
2
2
  import warnings
3
3
  import functools
4
+ from typing import Callable, ParamSpec, TypeVar
4
5
 
6
+ P = ParamSpec("P")
7
+ R = TypeVar("R")
5
8
 
6
9
  if sys.version_info >= (3, 11):
7
10
  from enum import StrEnum
@@ -12,10 +15,10 @@ else:
12
15
  pass
13
16
 
14
17
 
15
- def deprecated(reason=""):
16
- def decorator(func):
18
+ def deprecated(reason: str = "") -> Callable[[Callable[P, R]], Callable[P, R]]:
19
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
17
20
  @functools.wraps(func)
18
- def wrapper(*args, **kwargs):
21
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
19
22
  warnings.warn(
20
23
  f"Function {func.__name__} is deprecated. {reason}",
21
24
  category=DeprecationWarning,
@@ -0,0 +1,129 @@
1
+ from .base import BaseAstNode
2
+ from .nodes_array import (
3
+ ExprIndex,
4
+ ExprToListLength,
5
+ ExprListStringJoin,
6
+ ExprListUnique
7
+ )
8
+ from .nodes_cast import (
9
+ ExprNested,
10
+ ExprJsonify,
11
+ ExprToInt,
12
+ ExprToFloat,
13
+ ExprToListInt,
14
+ ExprToListFloat,
15
+ ExprToBool,
16
+ ExprJsonifyDynamic
17
+ )
18
+ from .nodes_core import (
19
+ ExprReturn,
20
+ ExprNoReturn,
21
+ ExprDefaultValueEnd,
22
+ ExprDefaultValueStart,
23
+ ExprDefaultValueWrapper,
24
+ ModuleProgram,
25
+ Docstring,
26
+ ModuleImports,
27
+ StructInitMethod,
28
+ StructPreValidateMethod,
29
+ StructFieldMethod,
30
+ StructPartDocMethod,
31
+ StartParseMethod,
32
+ StructParser,
33
+ JsonStruct,
34
+ JsonStructField,
35
+ TypeDef,
36
+ TypeDefField,
37
+ ExprCallStructMethod,
38
+ ExprClassVar,
39
+ ExprCallStructClassVar,
40
+ CodeStart,
41
+ CodeEnd
42
+ )
43
+ from .nodes_filter import (
44
+ FilterOr,
45
+ FilterAnd,
46
+ FilterNot,
47
+ FilterNotEqual,
48
+ FilterEqual,
49
+ FilterStrIn,
50
+ FilterStrStarts,
51
+ FilterStrEnds,
52
+ FilterStrRe,
53
+ ExprFilter,
54
+ FilterStrLenEq,
55
+ FilterStrLenNe,
56
+ FilterStrLenLt,
57
+ FilterStrLenLe,
58
+ FilterStrLenGt,
59
+ FilterStrLenGe,
60
+ ExprDocumentFilter,
61
+ FilterDocCss,
62
+ FilterDocHasAttr,
63
+ FilterDocHasRaw,
64
+ FilterDocHasText,
65
+ FilterDocIsRegexRaw,
66
+ FilterDocIsRegexText,
67
+ FilterDocXpath,
68
+ FilterDocAttrContains,
69
+ FilterDocAttrEnds,
70
+ FilterDocAttrEqual,
71
+ FilterDocAttrRegex,
72
+ FilterDocAttrStarts,
73
+ )
74
+
75
+ from .nodes_selectors import (
76
+ ExprCss,
77
+ ExprCssAll,
78
+ ExprXpathAll,
79
+ ExprXpath,
80
+ ExprGetHtmlText,
81
+ ExprGetHtmlRaw,
82
+ ExprGetHtmlAttr,
83
+ ExprGetHtmlAttrAll,
84
+ ExprGetHtmlRawAll,
85
+ ExprGetHtmlTextAll,
86
+ ExprMapAttrs,
87
+ ExprMapAttrsAll,
88
+ ExprCssElementRemove,
89
+ ExprXpathElementRemove
90
+ )
91
+ from .nodes_string import (
92
+ ExprStringTrim,
93
+ ExprStringRegex,
94
+ ExprStringSplit,
95
+ ExprStringReplace,
96
+ ExprStringLeftTrim,
97
+ ExprStringFormat,
98
+ ExprListStringTrim,
99
+ ExprStringRegexAll,
100
+ ExprStringRegexSub,
101
+ ExprStringRightTrim,
102
+ ExprListStringFormat,
103
+ ExprListStringReplace,
104
+ ExprListStringRightTrim,
105
+ ExprListStringLeftTrim,
106
+ ExprListStringRegexSub,
107
+ ExprStringRmPrefix,
108
+ ExprStringRmSuffix,
109
+ ExprStringRmPrefixAndSuffix,
110
+ ExprListStringRmPrefixAndSuffix,
111
+ ExprListStringRmPrefix,
112
+ ExprListStringRmSuffix,
113
+ ExprStringMapReplace,
114
+ ExprListStringMapReplace,
115
+ ExprStringUnescape,
116
+ ExprListStringUnescape
117
+ )
118
+ from .nodes_validate import (
119
+ ExprIsCss,
120
+ ExprIsEqual,
121
+ ExprStringIsRegex,
122
+ ExprIsXpath,
123
+ ExprIsNotEqual,
124
+ ExprIsContains,
125
+ ExprListStringAnyRegex,
126
+ ExprListStringAllRegex,
127
+ ExprHasAttr,
128
+ ExprListHasAttr
129
+ )
@@ -98,10 +98,10 @@ class BaseAstNode(Generic[T_MAPPING_FIELD, T_ARGUMENTS]):
98
98
 
99
99
  def find_node_all(
100
100
  self, func: Callable[["BaseAstNode"], bool]
101
- ) -> "BaseAstNode":
101
+ ) -> list["BaseAstNode"]:
102
102
  """find nodes in body by func filter"""
103
103
  if len(self.body) == 0:
104
- return []
104
+ return [] # type: ignore
105
105
  return [i for i in self.body if i.kind == func(i)]
106
106
 
107
107
  def find_node_by_token(
@@ -112,7 +112,7 @@ class BaseAstNode(Generic[T_MAPPING_FIELD, T_ARGUMENTS]):
112
112
 
113
113
  def find_nodes_by_token(self, token_type: TokenType) -> list["BaseAstNode"]:
114
114
  """find nodes by TokenType"""
115
- return self.find_nodes_by_token(lambda n: n.kind == token_type)
115
+ return self.find_nodes_by_token(lambda n: n.kind == token_type) # type: ignore[arg-type]
116
116
 
117
117
 
118
118
  @dataclass(kw_only=True)
@@ -107,7 +107,7 @@ class ExprCallStructMethod(
107
107
 
108
108
 
109
109
  KW_EXPR_CALL_CLASSVAR = TypedDict(
110
- "KW_EXPR_CALL_LITERAL",
110
+ "KW_EXPR_CALL_CLASSVAR",
111
111
  {"struct_name": str, "field_name": str, "type": VariableType},
112
112
  )
113
113
  ARGS_EXPR_CALL_CLASSVAR = tuple[str, str, VariableType]
@@ -253,7 +253,7 @@ class ExprNoReturn(_DisableReprBody, BaseAstNode[T_EMPTY_KWARGS, tuple]):
253
253
  # regex pattern auto converts to string
254
254
  T_CLASSVAR = int | str | bool | float | None
255
255
  KW_CLASSVAR = TypedDict(
256
- "KW_LITERAL",
256
+ "KW_CLASSVAR",
257
257
  {
258
258
  "value": T_CLASSVAR,
259
259
  "struct_name": str,
@@ -262,7 +262,7 @@ KW_CLASSVAR = TypedDict(
262
262
  "is_regex": bool,
263
263
  },
264
264
  )
265
- ARGS_CLASSVAR = tuple[T_CLASSVAR, str, str]
265
+ ARGS_CLASSVAR = tuple[T_CLASSVAR, str, str, bool, bool]
266
266
 
267
267
 
268
268
  @dataclass(kw_only=True)
@@ -286,7 +286,7 @@ class ExprClassVar(_DisableReprBody, BaseAstNode[KW_CLASSVAR, ARGS_CLASSVAR]):
286
286
  return self.kwargs["value"]
287
287
 
288
288
  @value.setter
289
- def value(self, val) -> None:
289
+ def value(self, val: T_CLASSVAR) -> None:
290
290
  self.kwargs["value"] = val
291
291
 
292
292
  @property
@@ -0,0 +1,285 @@
1
+ from dataclasses import dataclass
2
+ from typing import TypedDict, ClassVar
3
+
4
+ from ssc_codegen.ast_.base import BaseAstNode, T_EMPTY_KWARGS
5
+ from ssc_codegen.tokens import TokenType, VariableType
6
+
7
+
8
+ # TODO: static analyzer for filter operations
9
+ # TODO: implement operators for DOCUMENT, INT, FLOAT
10
+
11
+
12
+ @dataclass(kw_only=True)
13
+ class FilterOr(BaseAstNode[T_EMPTY_KWARGS, tuple]):
14
+ kind: ClassVar[TokenType] = TokenType.FILTER_OR
15
+ # cast for reuse in Elements filters
16
+ # real accept: VariableType.STRING, VariableType.DOCUMENT
17
+ accept_type: VariableType = VariableType.ANY
18
+ ret_type: VariableType = VariableType.ANY
19
+ # TODO: typing body accept Filter-like nodes
20
+
21
+
22
+ @dataclass(kw_only=True)
23
+ class FilterAnd(BaseAstNode[T_EMPTY_KWARGS, tuple]):
24
+ kind: ClassVar[TokenType] = TokenType.FILTER_AND
25
+ accept_type: VariableType = VariableType.ANY
26
+ ret_type: VariableType = VariableType.ANY
27
+ # TODO: typing body accept Filter-like nodes
28
+
29
+
30
+ @dataclass(kw_only=True)
31
+ class FilterNot(BaseAstNode[T_EMPTY_KWARGS, tuple]):
32
+ kind: ClassVar[TokenType] = TokenType.FILTER_NOT
33
+ accept_type: VariableType = VariableType.ANY
34
+ ret_type: VariableType = VariableType.ANY
35
+ # TODO: typing body accept Filter-like nodes
36
+
37
+
38
+ KW_STR_IN = TypedDict("KW_STR_IN", {"substr": tuple[str, ...]})
39
+ ARGS_STR_IN = tuple[tuple[str, ...]]
40
+
41
+ KW_STR_STARTS_OR_ENDS = TypedDict(
42
+ "KW_STR_STARTS_OR_ENDS", {"substr": tuple[str, ...]}
43
+ )
44
+ ARGS_STR_STARTS_OR_ENDS = tuple[tuple[str, ...]]
45
+
46
+
47
+ @dataclass(kw_only=True)
48
+ class FilterStrIn(BaseAstNode[KW_STR_IN, ARGS_STR_IN]):
49
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_IN
50
+ accept_type: VariableType = VariableType.STRING
51
+ ret_type: VariableType = VariableType.STRING
52
+
53
+
54
+ @dataclass(kw_only=True)
55
+ class FilterStrStarts(
56
+ BaseAstNode[KW_STR_STARTS_OR_ENDS, ARGS_STR_STARTS_OR_ENDS]
57
+ ):
58
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_STARTS
59
+ accept_type: VariableType = VariableType.STRING
60
+ ret_type: VariableType = VariableType.STRING
61
+
62
+
63
+ @dataclass(kw_only=True)
64
+ class FilterStrEnds(
65
+ BaseAstNode[KW_STR_STARTS_OR_ENDS, ARGS_STR_STARTS_OR_ENDS]
66
+ ):
67
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_ENDS
68
+ accept_type: VariableType = VariableType.STRING
69
+ ret_type: VariableType = VariableType.STRING
70
+
71
+
72
+ KW_STR_RE = TypedDict("KW_STR_RE", {"pattern": str, "ignore_case": bool})
73
+ ARGS_STR_RE = tuple[str, bool]
74
+
75
+
76
+ @dataclass(kw_only=True)
77
+ class FilterStrRe(BaseAstNode[KW_STR_RE, ARGS_STR_RE]):
78
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_RE
79
+ accept_type: VariableType = VariableType.STRING
80
+ ret_type: VariableType = VariableType.STRING
81
+
82
+
83
+ KW_STR_LEN = TypedDict("KW_STR_LEN", {"length": int})
84
+ ARGS_STR_LEN = tuple[int]
85
+
86
+
87
+ @dataclass(kw_only=True)
88
+ class FilterStrLenEq(BaseAstNode[KW_STR_LEN, ARGS_STR_LEN]):
89
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_LEN_EQ
90
+ accept_type: VariableType = VariableType.STRING
91
+ ret_type: VariableType = VariableType.STRING
92
+
93
+
94
+ @dataclass(kw_only=True)
95
+ class FilterStrLenNe(BaseAstNode[KW_STR_LEN, ARGS_STR_LEN]):
96
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_LEN_NE
97
+ accept_type: VariableType = VariableType.STRING
98
+ ret_type: VariableType = VariableType.STRING
99
+
100
+
101
+ @dataclass(kw_only=True)
102
+ class FilterStrLenLt(BaseAstNode[KW_STR_LEN, ARGS_STR_LEN]):
103
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_LEN_LT
104
+ accept_type: VariableType = VariableType.STRING
105
+ ret_type: VariableType = VariableType.STRING
106
+
107
+
108
+ @dataclass(kw_only=True)
109
+ class FilterStrLenLe(BaseAstNode[KW_STR_LEN, ARGS_STR_LEN]):
110
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_LEN_LE
111
+ accept_type: VariableType = VariableType.STRING
112
+ ret_type: VariableType = VariableType.STRING
113
+
114
+
115
+ @dataclass(kw_only=True)
116
+ class FilterStrLenGt(BaseAstNode[KW_STR_LEN, ARGS_STR_LEN]):
117
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_LEN_GT
118
+ accept_type: VariableType = VariableType.STRING
119
+ ret_type: VariableType = VariableType.STRING
120
+
121
+
122
+ @dataclass(kw_only=True)
123
+ class FilterStrLenGe(BaseAstNode[KW_STR_LEN, ARGS_STR_LEN]):
124
+ kind: ClassVar[TokenType] = TokenType.FILTER_STR_LEN_GE
125
+ accept_type: VariableType = VariableType.STRING
126
+ ret_type: VariableType = VariableType.STRING
127
+
128
+
129
+ # TODO: provide API for int, float, etc
130
+ KW_STR_EQ_OR_NE = TypedDict("KW_STR_EQ_OR_NE", {"values": tuple[str, ...]})
131
+ ARGS_STR_EQ_OR_NE = tuple[tuple[str, ...]]
132
+
133
+
134
+ @dataclass(kw_only=True)
135
+ class FilterEqual(BaseAstNode[KW_STR_EQ_OR_NE, ARGS_STR_EQ_OR_NE]):
136
+ kind: ClassVar[TokenType] = TokenType.FILTER_EQ
137
+ accept_type: VariableType = VariableType.STRING
138
+ ret_type: VariableType = VariableType.STRING
139
+
140
+
141
+ @dataclass(kw_only=True)
142
+ class FilterNotEqual(BaseAstNode[KW_STR_EQ_OR_NE, ARGS_STR_EQ_OR_NE]):
143
+ kind: ClassVar[TokenType] = TokenType.FILTER_NE
144
+ accept_type: VariableType = VariableType.STRING
145
+ ret_type: VariableType = VariableType.STRING
146
+
147
+
148
+ @dataclass(kw_only=True)
149
+ class ExprFilter(BaseAstNode[T_EMPTY_KWARGS, tuple]):
150
+ kind: ClassVar[TokenType] = TokenType.EXPR_FILTER
151
+ accept_type: VariableType = VariableType.LIST_STRING
152
+ ret_type: VariableType = VariableType.LIST_STRING
153
+ # TODO: typing body accept Filter-like nodes
154
+
155
+
156
+ # Elements filters
157
+ @dataclass(kw_only=True)
158
+ class ExprDocumentFilter(BaseAstNode[T_EMPTY_KWARGS, tuple]):
159
+ kind: ClassVar[TokenType] = TokenType.EXPR_DOC_FILTER
160
+ accept_type: VariableType = VariableType.LIST_DOCUMENT
161
+ ret_type: VariableType = VariableType.LIST_DOCUMENT
162
+
163
+
164
+ KW_FILTER_CSS_OR_XPATH = TypedDict("KW_FILTER_CSS", {"query": str})
165
+ ARGS_FILTER_CSS_OR_XPATH = tuple[str]
166
+
167
+
168
+ @dataclass(kw_only=True)
169
+ class FilterDocCss(
170
+ BaseAstNode[KW_FILTER_CSS_OR_XPATH, ARGS_FILTER_CSS_OR_XPATH]
171
+ ):
172
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_CSS
173
+ accept_type: VariableType = VariableType.DOCUMENT
174
+ ret_type: VariableType = VariableType.DOCUMENT
175
+
176
+
177
+ @dataclass(kw_only=True)
178
+ class FilterDocXpath(
179
+ BaseAstNode[KW_FILTER_CSS_OR_XPATH, ARGS_FILTER_CSS_OR_XPATH]
180
+ ):
181
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_XPATH
182
+ accept_type: VariableType = VariableType.DOCUMENT
183
+ ret_type: VariableType = VariableType.DOCUMENT
184
+
185
+
186
+ KW_FILTER_HAS_TEXT = TypedDict(
187
+ "KW_FILTER_HAS_TEXT", {"values": tuple[str, ...]}
188
+ )
189
+ ARGS_FILTER_HAS_TEXT = tuple[tuple[str, ...]]
190
+
191
+
192
+ @dataclass(kw_only=True)
193
+ class FilterDocHasText(BaseAstNode[KW_FILTER_HAS_TEXT, ARGS_FILTER_HAS_TEXT]):
194
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_HAS_TEXT
195
+ accept_type: VariableType = VariableType.DOCUMENT
196
+ ret_type: VariableType = VariableType.DOCUMENT
197
+
198
+
199
+ @dataclass(kw_only=True)
200
+ class FilterDocHasRaw(BaseAstNode[KW_FILTER_HAS_TEXT, ARGS_FILTER_HAS_TEXT]):
201
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_HAS_RAW
202
+ accept_type: VariableType = VariableType.DOCUMENT
203
+ ret_type: VariableType = VariableType.DOCUMENT
204
+
205
+
206
+ KW_FILTER_HAS_ATTR = TypedDict("KW_FILTER_HAS_ATTR", {"keys": tuple[str, ...]})
207
+ ARGS_FILTER_HAS_ATTR = tuple[tuple[str, ...]]
208
+
209
+
210
+ @dataclass(kw_only=True)
211
+ class FilterDocHasAttr(BaseAstNode[KW_FILTER_HAS_ATTR, ARGS_FILTER_HAS_ATTR]):
212
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_HAS_ATTR
213
+ accept_type: VariableType = VariableType.DOCUMENT
214
+ ret_type: VariableType = VariableType.DOCUMENT
215
+
216
+
217
+ KW_FILTER_ATTR = TypedDict(
218
+ "KW_FILTER_ATTR", {"key": str, "values": tuple[str, ...]}
219
+ )
220
+ ARGS_FILTER_ATTR = tuple[str, tuple[str, ...]]
221
+
222
+
223
+ @dataclass(kw_only=True)
224
+ class FilterDocAttrEqual(BaseAstNode[KW_FILTER_ATTR, ARGS_FILTER_ATTR]):
225
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_ATTR_EQ
226
+ accept_type: VariableType = VariableType.DOCUMENT
227
+ ret_type: VariableType = VariableType.DOCUMENT
228
+
229
+
230
+ @dataclass(kw_only=True)
231
+ class FilterDocAttrStarts(BaseAstNode[KW_FILTER_ATTR, ARGS_FILTER_ATTR]):
232
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_ATTR_STARTS
233
+ accept_type: VariableType = VariableType.DOCUMENT
234
+ ret_type: VariableType = VariableType.DOCUMENT
235
+
236
+
237
+ @dataclass(kw_only=True)
238
+ class FilterDocAttrEnds(BaseAstNode[KW_FILTER_ATTR, ARGS_FILTER_ATTR]):
239
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_ATTR_ENDS
240
+ accept_type: VariableType = VariableType.DOCUMENT
241
+ ret_type: VariableType = VariableType.DOCUMENT
242
+
243
+
244
+ @dataclass(kw_only=True)
245
+ class FilterDocAttrContains(BaseAstNode[KW_FILTER_ATTR, ARGS_FILTER_ATTR]):
246
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_ATTR_CONTAINS
247
+ accept_type: VariableType = VariableType.DOCUMENT
248
+ ret_type: VariableType = VariableType.DOCUMENT
249
+
250
+
251
+ KW_FILTER_ATTR_RE = TypedDict(
252
+ "KW_FILTER_ATTR", {"key": str, "pattern": str, "ignore_case": bool}
253
+ )
254
+ ARGS_FILTER_ATTR_RE = tuple[str, str, bool]
255
+
256
+
257
+ @dataclass(kw_only=True)
258
+ class FilterDocAttrRegex(BaseAstNode[KW_FILTER_ATTR_RE, ARGS_FILTER_ATTR_RE]):
259
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_ATTR_RE_MATCH
260
+ accept_type: VariableType = VariableType.DOCUMENT
261
+ ret_type: VariableType = VariableType.DOCUMENT
262
+
263
+
264
+ KW_FILTER_IS_REGEX = TypedDict(
265
+ "KW_FILTER_IS_REGEX", {"pattern": str, "ignore_case": bool}
266
+ )
267
+ ARGS_FILTER_IS_REGEX = tuple[str, bool]
268
+
269
+
270
+ @dataclass(kw_only=True)
271
+ class FilterDocIsRegexText(
272
+ BaseAstNode[KW_FILTER_IS_REGEX, ARGS_FILTER_IS_REGEX]
273
+ ):
274
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_IS_RE_TEXT
275
+ accept_type: VariableType = VariableType.DOCUMENT
276
+ ret_type: VariableType = VariableType.DOCUMENT
277
+
278
+
279
+ @dataclass(kw_only=True)
280
+ class FilterDocIsRegexRaw(
281
+ BaseAstNode[KW_FILTER_IS_REGEX, ARGS_FILTER_IS_REGEX]
282
+ ):
283
+ kind: ClassVar[TokenType] = TokenType.FILTER_DOC_IS_RE_RAW
284
+ accept_type: VariableType = VariableType.DOCUMENT
285
+ ret_type: VariableType = VariableType.DOCUMENT
@@ -150,7 +150,7 @@ class AstBuilder:
150
150
 
151
151
  def _literal_struct_node(self, schema: Type[BaseSchema]) -> StructParser:
152
152
  # dont required TypeDef struct generate
153
- literals = schema.__get_mro_literals__().copy()
153
+ literals = schema.__get_mro_classvars__().copy()
154
154
  st = StructParser(
155
155
  kwargs={
156
156
  "name": schema.__name__,
@@ -175,7 +175,7 @@ class AstBuilder:
175
175
  parent = node_method
176
176
  for expr in node_method.body:
177
177
  expr.parent = parent
178
- if expr.kind == TokenType.EXPR_FILTER:
178
+ if expr.kind in (TokenType.EXPR_FILTER, TokenType.EXPR_DOC_FILTER):
179
179
  self._ref_filter_expr(expr, expr.body)
180
180
 
181
181
  def _unwrap_default_node(
@@ -251,7 +251,7 @@ class AstBuilder:
251
251
  exprs.append(ExprNoReturn())
252
252
  method.body.extend(exprs)
253
253
  self._ref_method_exprs(method)
254
- node_start_parse.body.append(ExprCallStructMethod(kwargs={"type": VariableType.NULL, "name": "__PRE_VALIDATE__",}))
254
+ node_start_parse.body.append(ExprCallStructMethod(kwargs={"type": VariableType.NULL, "name": "__PRE_VALIDATE__",}, ret_type=VariableType.NULL))
255
255
  return method
256
256
  # fmt: on
257
257
 
@@ -270,7 +270,11 @@ class AstBuilder:
270
270
  exprs.append(ExprReturn(ret_type=VariableType.LIST_DOCUMENT))
271
271
  method.body.extend(exprs)
272
272
  self._ref_method_exprs(method)
273
- node_start_parse.body.append(ExprCallStructMethod(kwargs={"type": VariableType.LIST_DOCUMENT, "name": "__SPLIT_DOC__",}))
273
+ node_start_parse.body.append(
274
+ ExprCallStructMethod(
275
+ kwargs={"type": VariableType.LIST_DOCUMENT, "name": "__SPLIT_DOC__",},
276
+ ret_type=VariableType.LIST_DOCUMENT)
277
+ )
274
278
  return method
275
279
  # fmt: on
276
280
 
@@ -314,7 +318,7 @@ class AstBuilder:
314
318
  self._ref_method_exprs(method)
315
319
  st_fields.append(method)
316
320
  node_start_parse.body.append(
317
- ExprCallStructMethod(kwargs={"type": exprs[-1].ret_type, "name": field_name,}))
321
+ ExprCallStructMethod(kwargs={"type": exprs[-1].ret_type, "name": field_name,}, ret_type=exprs[-1].ret_type))
318
322
  return st_fields
319
323
  # fmt: on
320
324
 
@@ -368,7 +372,7 @@ class AstBuilder:
368
372
  kwargs={"name": schema.__name__, "struct_type": schema.__SCHEMA_TYPE__, "docstring": docstring if self.gen_docstr else "",},
369
373
  parent=self.module,
370
374
  )
371
- literals = schema.__get_mro_literals__().copy()
375
+ literals = schema.__get_mro_classvars__().copy()
372
376
 
373
377
  for literal in literals.values():
374
378
  st.body.append(literal.expr())
@@ -151,5 +151,5 @@ def exec_module_code(path: str | Path, add_sys_path: bool = True) -> ModuleType:
151
151
  def is_literals_only_schema(schema: Type[BaseSchema]) -> bool:
152
152
  return (
153
153
  len(schema.__get_mro_fields__()) == 0
154
- and len(schema.__get_mro_literals__()) > 0
154
+ and len(schema.__get_mro_classvars__()) > 0
155
155
  )
@@ -478,7 +478,7 @@ def parse_from_url(
478
478
  ] = True,
479
479
  ) -> None:
480
480
  from ssc_codegen.compiler import Compiler
481
- from ssc_codegen.converters.py_parsel import CONVERTER
481
+ from ssc_codegen.converters.py_bs4 import CONVERTER
482
482
 
483
483
  cls_target, schema_config = _validate_parser_target(cls_target)
484
484
 
@@ -154,8 +154,8 @@ class BaseCodeConverter:
154
154
  if for_struct_definition:
155
155
  self.post_definitions[
156
156
  (for_definition, for_struct_definition)
157
- ] = post_callback
158
- self.post_definitions[for_definition] = post_callback
157
+ ] = post_callback # type: ignore[assignment]
158
+ self.post_definitions[for_definition] = post_callback # type: ignore[assignment]
159
159
 
160
160
  return decorator
161
161
 
@@ -277,6 +277,7 @@ class BaseCodeConverter:
277
277
 
278
278
  case (
279
279
  TokenType.EXPR_FILTER
280
+ | TokenType.EXPR_DOC_FILTER
280
281
  | TokenType.FILTER_OR
281
282
  | TokenType.FILTER_AND
282
283
  | TokenType.FILTER_NOT