ssc_codegen 0.29.6__tar.gz → 0.31.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/PKG-INFO +3 -3
  2. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/pyproject.toml +3 -5
  3. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/__init__.py +6 -2
  4. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/struct.py +104 -50
  5. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/converters/js_pure.py +148 -179
  6. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_bs4.py +62 -309
  7. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_lxml.py +21 -17
  8. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_parsel.py +12 -6
  9. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_render.py +2 -2
  10. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_slax.py +12 -6
  11. ssc_codegen-0.31.0/ssc_codegen/converters/runtime/__init__.py +7 -0
  12. ssc_codegen-0.31.0/ssc_codegen/converters/runtime/_helpers.py +35 -0
  13. ssc_codegen-0.31.0/ssc_codegen/converters/runtime/js_base.py +24 -0
  14. ssc_codegen-0.31.0/ssc_codegen/converters/runtime/py_base.py +67 -0
  15. ssc_codegen-0.31.0/ssc_codegen/converters/runtime/py_lxml.py +7 -0
  16. ssc_codegen-0.31.0/ssc_codegen/converters/runtime/py_rest.py +133 -0
  17. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/__init__.py +3 -3
  18. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/contexts.py +197 -235
  19. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/expressions.py +4 -13
  20. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/format.py +361 -361
  21. ssc_codegen-0.31.0/ssc_codegen/core/linter.py +1491 -0
  22. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/module_handler.py +9 -49
  23. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/predicates.py +2 -2
  24. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/reader.py +116 -164
  25. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/struct_parser.py +48 -48
  26. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/core/type_checking.py +401 -401
  27. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/kdl/__init__.py +8 -5
  28. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/main.py +1 -1
  29. ssc_codegen-0.29.6/ssc_codegen/core/adapter.py +0 -25
  30. ssc_codegen-0.29.6/ssc_codegen/core/linting.py +0 -1179
  31. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/.gitignore +0 -0
  32. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/LICENSE +0 -0
  33. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/README.md +0 -0
  34. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/__init__.py +0 -0
  35. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/_logging.py +0 -0
  36. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/array.py +0 -0
  37. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/base.py +0 -0
  38. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/cast.py +0 -0
  39. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/control.py +0 -0
  40. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/extract.py +0 -0
  41. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/helpers.py +0 -0
  42. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/jsondef.py +0 -0
  43. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/module.py +0 -0
  44. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/predicate_containers.py +0 -0
  45. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/predicate_ops.py +0 -0
  46. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/regex.py +0 -0
  47. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/selectors.py +0 -0
  48. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/string.py +0 -0
  49. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/transform.py +0 -0
  50. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/typedef.py +0 -0
  51. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/ast/types.py +0 -0
  52. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/converters/base.py +0 -0
  53. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/converters/helpers.py +0 -0
  54. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/document_utils.py +0 -0
  55. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/exceptions.py +0 -0
  56. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/health.py +0 -0
  57. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/kdl/dict_reader.py +0 -0
  58. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/kdl/parser.py +0 -0
  59. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/kdl/reader.py +0 -0
  60. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/parsers/__init__.py +0 -0
  61. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/parsers/curl.py +0 -0
  62. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/parsers/http.py +0 -0
  63. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/pseudo_selectors.py +0 -0
  64. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/regex_utils.py +0 -0
  65. {ssc_codegen-0.29.6/ssc_codegen/converters → ssc_codegen-0.31.0/ssc_codegen}/request_spec.py +0 -0
  66. {ssc_codegen-0.29.6 → ssc_codegen-0.31.0}/ssc_codegen/selector_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssc_codegen
3
- Version: 0.29.6
3
+ Version: 0.31.0
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
@@ -20,13 +20,13 @@ Classifier: Topic :: Text Processing :: Markup :: HTML
20
20
  Classifier: Topic :: Utilities
21
21
  Requires-Python: >=3.10
22
22
  Requires-Dist: bs4>=0.0.2
23
- Requires-Dist: click<8.2.0
24
23
  Requires-Dist: colorama>=0.4.6; sys_platform == 'win32'
25
24
  Requires-Dist: cssselect>=1.2.0
25
+ Requires-Dist: kdlquery>=1.1.2
26
26
  Requires-Dist: lxml>=5.3.0
27
27
  Requires-Dist: pyyaml>=6.0
28
28
  Requires-Dist: soupsieve>=2.6
29
- Requires-Dist: typer>=0.15.1
29
+ Requires-Dist: typer>0.26
30
30
  Requires-Dist: typing-extensions; python_version < '3.11'
31
31
  Description-Content-Type: text/markdown
32
32
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ssc_codegen"
3
- version = "0.29.6"
3
+ version = "0.31.0"
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,12 +10,10 @@ dependencies = [
10
10
  "cssselect>=1.2.0",
11
11
  "lxml>=5.3.0",
12
12
  "soupsieve>=2.6",
13
- "typer>=0.15.1",
13
+ "typer>0.26",
14
14
  "typing_extensions;python_version<'3.11'",
15
- # https://github.com/fastapi/typer/discussions/1215
16
- # https://github.com/fastapi/typer/pull/1145
17
- "click<8.2.0",
18
15
  "pyyaml>=6.0",
16
+ "kdlquery>=1.1.2",
19
17
  ]
20
18
 
21
19
  classifiers = [
@@ -43,7 +43,10 @@ from .struct import (
43
43
  TableConfig,
44
44
  TableRow,
45
45
  TableMatchKey,
46
- RequestConfig,
46
+ RequestHttp,
47
+ MethodBase,
48
+ MethodFetch,
49
+ MethodRest,
47
50
  ErrorResponse,
48
51
  PlaceholderSpec,
49
52
  Field,
@@ -150,7 +153,8 @@ __all__ = [
150
153
  "Init", "InitField", "SplitDoc",
151
154
  "Key", "Value",
152
155
  "TableConfig", "TableRow", "TableMatchKey",
153
- "RequestConfig", "ErrorResponse", "PlaceholderSpec", "Field", "StartParse",
156
+ "RequestHttp", "MethodBase", "MethodFetch", "MethodRest",
157
+ "ErrorResponse", "PlaceholderSpec", "Field", "StartParse",
154
158
  # selectors
155
159
  "CssSelect", "CssSelectAll",
156
160
  "XpathSelect", "XpathSelectAll",
@@ -49,6 +49,106 @@ def parse_placeholder(match: "_re.Match[str]") -> PlaceholderSpec:
49
49
  )
50
50
 
51
51
 
52
+ # ── Request/Method nodes ──────────────────────────────────────────────────────
53
+
54
+
55
+ def _iter_http_strings(http: RequestHttp):
56
+ """Yield every string value that may contain placeholders."""
57
+ yield http.url
58
+ yield from http.headers.values()
59
+ yield from http.cookies.values()
60
+ yield from http.params.values()
61
+ if isinstance(http.body, str) and http.body:
62
+ yield http.body
63
+ elif isinstance(http.body, dict):
64
+ yield from _iter_dict_strings(http.body)
65
+
66
+
67
+ def _iter_dict_strings(d: dict):
68
+ for v in d.values():
69
+ if isinstance(v, str):
70
+ yield v
71
+ elif isinstance(v, dict):
72
+ yield from _iter_dict_strings(v)
73
+ elif isinstance(v, list):
74
+ for item in v:
75
+ if isinstance(item, str):
76
+ yield item
77
+
78
+
79
+ @dataclass
80
+ class RequestHttp(Node):
81
+ """Parsed HTTP request — child node of MethodBase.
82
+
83
+ Created once at parse time from ``parse_to_spec(raw_payload)``;
84
+ converters read fields directly instead of re-parsing raw_payload.
85
+ """
86
+
87
+ method: str = "GET"
88
+ url: str = ""
89
+ headers: dict[str, str] = field(default_factory=dict)
90
+ cookies: dict[str, str] = field(default_factory=dict)
91
+ params: dict[str, str] = field(default_factory=dict)
92
+ body_kind: str = "empty" # "empty" | "json" | "form" | "raw"
93
+ body: str | dict | None = None
94
+
95
+ @property
96
+ def placeholders(self) -> list[PlaceholderSpec]:
97
+ """Unique placeholders across all string fields."""
98
+ seen: set[str] = set()
99
+ result: list[PlaceholderSpec] = []
100
+ for text in _iter_http_strings(self):
101
+ for m in PLACEHOLDER_RE.finditer(text):
102
+ spec = parse_placeholder(m)
103
+ if spec.name not in seen:
104
+ seen.add(spec.name)
105
+ result.append(spec)
106
+ return result
107
+
108
+
109
+ @dataclass
110
+ class MethodBase(Node):
111
+ """Base class for @request method nodes."""
112
+
113
+ name: str = "" # method name suffix; "" = default fetch()
114
+
115
+ @property
116
+ def http_request(self) -> RequestHttp:
117
+ return [n for n in self.body if isinstance(n, RequestHttp)][0]
118
+
119
+ @property
120
+ def placeholders(self) -> list[PlaceholderSpec]:
121
+ return self.http_request.placeholders
122
+
123
+ @property
124
+ def placeholder_names(self) -> list[str]:
125
+ return [p.name for p in self.placeholders]
126
+
127
+
128
+ @dataclass
129
+ class MethodFetch(MethodBase):
130
+ """Fetch shortcut for regular schemas (Item/List/etc).
131
+
132
+ ``fetch()`` returns a parser instance constructed from the HTTP response body.
133
+ """
134
+
135
+ response_path: str = "" # dot-notation JSON path, e.g. "payload.html"
136
+ response_join: str = (
137
+ "" # join separator when response-path resolves to list[str]
138
+ )
139
+
140
+
141
+ @dataclass
142
+ class MethodRest(MethodBase):
143
+ """REST method for ``type=rest`` schemas.
144
+
145
+ Method returns ``Ok/TransportErr`` union; no HTML parsing.
146
+ """
147
+
148
+ doc: str = "" # per-method docstring
149
+ response_schema: str = "" # json schema name for typed 2xx response
150
+
151
+
52
152
  # ── Base struct ──────────────────────────────────────────────────────────────
53
153
 
54
154
 
@@ -68,15 +168,15 @@ class StructBase(Node):
68
168
  return self.body[0] # type: ignore
69
169
 
70
170
  @property
71
- def request_config(self) -> RequestConfig | None:
171
+ def request_config(self) -> MethodBase | None:
72
172
  for node in self.body:
73
- if isinstance(node, RequestConfig):
173
+ if isinstance(node, MethodBase):
74
174
  return node
75
175
  return None
76
176
 
77
177
  @property
78
- def request_configs(self) -> list[RequestConfig]:
79
- return [n for n in self.body if isinstance(n, RequestConfig)]
178
+ def request_configs(self) -> list[MethodBase]:
179
+ return [n for n in self.body if isinstance(n, MethodBase)]
80
180
 
81
181
  @property
82
182
  def use_request(self) -> bool:
@@ -350,52 +450,6 @@ class TableMatchKey(Node):
350
450
  ret: VariableType = field(default=VariableType.STRING)
351
451
 
352
452
 
353
- @dataclass
354
- class RequestConfig(Node):
355
- """
356
- Optional transport layer config for a struct.
357
- DSL: @request [name="suffix"] [response-path="..."] [response-join="..."]
358
- [response=JsonSchema] [doc="..."] \"""...\"""
359
-
360
- raw_payload stores the verbatim curl or raw HTTP string (with {{placeholders}}).
361
- Transport normalization (curl/HTTP parse → kwargs) happens at converter stage.
362
-
363
- name="" (unnamed) generates fetch()/async_fetch().
364
- name="by-id" generates fetch_by_id()/async_fetch_by_id() (Python)
365
- or fetchById() (JS).
366
-
367
- response_schema (type=rest only): json schema name for typed 2xx response.
368
- Empty string = void return.
369
- doc (type=rest only): per-method docstring.
370
- """
371
-
372
- raw_payload: str = ""
373
- response_path: str = "" # dot-notation JSON path, e.g. "payload.html"
374
- response_join: str = (
375
- "" # join separator when response-path resolves to list[str]
376
- )
377
- name: str = "" # method name suffix; "" = default fetch()
378
- response_schema: str = "" # type=rest: json schema for 2xx body
379
- doc: str = "" # type=rest: per-method docstring
380
-
381
- @property
382
- def placeholders(self) -> list[PlaceholderSpec]:
383
- """Unique placeholders in declaration order (dedup by name)."""
384
- seen: set[str] = set()
385
- result: list[PlaceholderSpec] = []
386
- for m in PLACEHOLDER_RE.finditer(self.raw_payload):
387
- spec = parse_placeholder(m)
388
- if spec.name not in seen:
389
- seen.add(spec.name)
390
- result.append(spec)
391
- return result
392
-
393
- @property
394
- def placeholder_names(self) -> list[str]:
395
- """Unique placeholder names in declaration order."""
396
- return [p.name for p in self.placeholders]
397
-
398
-
399
453
  @dataclass
400
454
  class ErrorResponse(Node):
401
455
  """