ssc_codegen 0.32.0__tar.gz → 0.32.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 (87) hide show
  1. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/PKG-INFO +1 -1
  2. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/pyproject.toml +1 -1
  3. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/struct.py +13 -5
  4. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/struct_parser.py +49 -7
  5. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/rest.py +18 -11
  6. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/visitor.py +2 -0
  7. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/javascript/rest.py +11 -1
  8. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/rest.py +7 -4
  9. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/.gitignore +0 -0
  10. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/LICENSE +0 -0
  11. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/README.md +0 -0
  12. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/__init__.py +0 -0
  13. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/_logging.py +0 -0
  14. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/__init__.py +0 -0
  15. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/array.py +0 -0
  16. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/base.py +0 -0
  17. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/cast.py +0 -0
  18. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/control.py +0 -0
  19. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/extract.py +0 -0
  20. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/helpers.py +0 -0
  21. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/jsondef.py +0 -0
  22. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/module.py +0 -0
  23. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/predicate_containers.py +0 -0
  24. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/predicate_ops.py +0 -0
  25. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/regex.py +0 -0
  26. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/rest.py +0 -0
  27. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/selectors.py +0 -0
  28. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/string.py +0 -0
  29. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/typedef.py +0 -0
  30. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/ast/types.py +0 -0
  31. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/__init__.py +0 -0
  32. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/contexts.py +0 -0
  33. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/expressions.py +0 -0
  34. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/format.py +0 -0
  35. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/linter.py +0 -0
  36. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/module_handler.py +0 -0
  37. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/predicates.py +0 -0
  38. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/reader.py +0 -0
  39. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/rest_artifacts.py +0 -0
  40. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/core/type_checking.py +0 -0
  41. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/exceptions.py +0 -0
  42. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/explore.py +0 -0
  43. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/generation/__init__.py +0 -0
  44. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/generation/builder.py +0 -0
  45. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/generation/runtime.py +0 -0
  46. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/health.py +0 -0
  47. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/main.py +0 -0
  48. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/naming.py +0 -0
  49. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/parsers/__init__.py +0 -0
  50. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/parsers/curl.py +0 -0
  51. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/parsers/http.py +0 -0
  52. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/regex_utils.py +0 -0
  53. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/request_spec.py +0 -0
  54. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/__init__.py +0 -0
  55. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/__init__.py +0 -0
  56. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/http_libs/__init__.py +0 -0
  57. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/http_libs/base.py +0 -0
  58. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/http_libs/nethttp.py +0 -0
  59. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/literals.py +0 -0
  60. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/regex.py +0 -0
  61. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/golang/runtime.py +0 -0
  62. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/javascript/__init__.py +0 -0
  63. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/javascript/http_libs/__init__.py +0 -0
  64. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/javascript/http_libs/axios.py +0 -0
  65. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/javascript/http_libs/base.py +0 -0
  66. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/javascript/http_libs/fetch.py +0 -0
  67. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/javascript/visitor.py +0 -0
  68. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/profile.py +0 -0
  69. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/__init__.py +0 -0
  70. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/html_libs/__init__.py +0 -0
  71. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/html_libs/base.py +0 -0
  72. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/html_libs/bs4.py +0 -0
  73. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/html_libs/lxml.py +0 -0
  74. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/html_libs/parsel.py +0 -0
  75. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/html_libs/slax.py +0 -0
  76. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/http_libs/__init__.py +0 -0
  77. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/http_libs/aiohttp.py +0 -0
  78. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/http_libs/base.py +0 -0
  79. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/http_libs/httpx.py +0 -0
  80. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/http_libs/requests.py +0 -0
  81. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/python/visitor.py +0 -0
  82. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/resolver.py +0 -0
  83. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/targets/spec.py +0 -0
  84. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/traversal/__init__.py +0 -0
  85. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/traversal/context.py +0 -0
  86. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/traversal/utils.py +0 -0
  87. {ssc_codegen-0.32.0 → ssc_codegen-0.32.1}/ssc_codegen/traversal/walker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssc_codegen
3
- Version: 0.32.0
3
+ Version: 0.32.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ssc_codegen"
3
- version = "0.32.0"
3
+ version = "0.32.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"
@@ -337,6 +337,19 @@ class MethodBase(Node):
337
337
 
338
338
  name: str = "" # method name suffix; "" = default fetch()
339
339
 
340
+ #: Dot-notation JSON path extracted from the 2xx response body before it
341
+ #: becomes ``Ok.value`` (REST) or the parser input (fetch). Example:
342
+ #: ``"data.user"`` resolves ``{"data": {"user": ...}}`` to the inner
343
+ #: object. When set together with ``response_schema`` (REST), the path
344
+ #: wins: the schema type-checks the *extracted* sub-object, not the
345
+ #: whole envelope. ``@error`` matchers always run against the full body.
346
+ response_path: str = ""
347
+
348
+ #: Join separator applied when ``response_path`` resolves to a
349
+ #: ``list[str]`` (fetch-only). Forbidden on ``MethodRest``; the linter
350
+ #: rejects ``response-join`` on ``type=rest`` structs.
351
+ response_join: str = ""
352
+
340
353
  @property
341
354
  def http_request(self) -> RequestHttp:
342
355
  return [n for n in self.body if isinstance(n, RequestHttp)][0]
@@ -353,11 +366,6 @@ class MethodFetch(MethodBase):
353
366
  ``fetch()`` returns a parser instance constructed from the HTTP response body.
354
367
  """
355
368
 
356
- response_path: str = "" # dot-notation JSON path, e.g. "payload.html"
357
- response_join: str = (
358
- "" # join separator when response-path resolves to list[str]
359
- )
360
-
361
369
 
362
370
  @dataclass
363
371
  class MethodRest(MethodBase):
@@ -107,8 +107,54 @@ def parse_struct(
107
107
  )
108
108
  http = parse_to_http(raw_payload)
109
109
  method_name = node.get_prop("name") or ""
110
- if isinstance(parent, StructRest):
110
+ response_path_val = node.get_prop("response-path") or ""
111
+ response_join_val = node.get_prop("response-join") or ""
112
+
113
+ is_rest = isinstance(parent, StructRest)
114
+
115
+ # Lint: response-path format (dot-notation, non-empty segments).
116
+ if response_path_val:
117
+ segments = response_path_val.split(".")
118
+ bad = any(not seg for seg in segments) or any(
119
+ not seg.replace("-", "_").isidentifier() for seg in segments
120
+ )
121
+ if bad:
122
+ lint.error(
123
+ node,
124
+ message=(
125
+ "response-path must be dot-notation with "
126
+ "non-empty ASCII identifier segments "
127
+ '(e.g. "data.user"); got '
128
+ f"{response_path_val!r}"
129
+ ),
130
+ code="E001",
131
+ )
132
+
133
+ # Lint: response-join only on fetch-track.
134
+ if response_join_val and is_rest:
135
+ lint.error(
136
+ node,
137
+ message=(
138
+ "response-join is forbidden on type=rest structs "
139
+ "(use response-path alone; Ok.value is the "
140
+ "extracted object)"
141
+ ),
142
+ code="E001",
143
+ )
144
+
145
+ # Lint: response-join without response-path is meaningless.
146
+ if response_join_val and not response_path_val:
147
+ lint.error(
148
+ node,
149
+ message=(
150
+ "response-join requires response-path (nothing to join)"
151
+ ),
152
+ code="E001",
153
+ )
154
+
155
+ if is_rest:
111
156
  rest_method = MethodRest(parent=parent, name=method_name)
157
+ rest_method.response_path = response_path_val
112
158
  response_schema_val = node.get_prop("response") or ""
113
159
  rest_method.response_schema = str(
114
160
  ctx.property_defines.get(
@@ -124,12 +170,8 @@ def parse_struct(
124
170
  parent.body.append(rest_method)
125
171
  else:
126
172
  fetch_method = MethodFetch(parent=parent, name=method_name)
127
- fetch_method.response_path = (
128
- node.get_prop("response-path") or ""
129
- )
130
- fetch_method.response_join = (
131
- node.get_prop("response-join") or ""
132
- )
173
+ fetch_method.response_path = response_path_val
174
+ fetch_method.response_join = response_join_val
133
175
  http.parent = fetch_method
134
176
  fetch_method.body.append(http)
135
177
  parent.body.append(fetch_method)
@@ -333,15 +333,15 @@ def emit_method_rest(
333
333
 
334
334
  # Call sscRestCall — inline if nil, multi-line if struct literal.
335
335
  # For void responses, use `_` since the body is discarded.
336
- body_var = "_" if not node.response_schema else "_body"
336
+ body_var = "_" if not node.response_schema else "body"
337
337
  if len(opts_lines) == 1:
338
338
  lines.append(
339
- f"{i2}{body_var}, _err := sscRestCall(client, {matchers_var}, "
339
+ f"{i2}{body_var}, err := sscRestCall(client, {matchers_var}, "
340
340
  f"{_go_str(spec.method)}, {url_expr}, {opts_lines[0].strip()})"
341
341
  )
342
342
  else:
343
343
  lines.append(
344
- f"{i2}{body_var}, _err := sscRestCall(client, {matchers_var}, "
344
+ f"{i2}{body_var}, err := sscRestCall(client, {matchers_var}, "
345
345
  f"{_go_str(spec.method)}, {url_expr},"
346
346
  )
347
347
  lines.append(f"{opts_lines[0].strip()}")
@@ -350,28 +350,35 @@ def emit_method_rest(
350
350
  lines.append(f"{opts_lines[-1].strip()})")
351
351
 
352
352
  # Error propagation.
353
- lines.append(f"{i2}if _err != nil {{")
354
- lines.append(f"{i2}\treturn {zero}, _err")
353
+ lines.append(f"{i2}if err != nil {{")
354
+ lines.append(f"{i2}\treturn {zero}, err")
355
355
  lines.append(f"{i2}}}")
356
356
 
357
357
  # Parse response body (when typed) or return void.
358
358
  if node.response_schema:
359
359
  schema_go = f"{to_pascal_case(node.response_schema)}Json"
360
+ # response_path extraction: narrow body to the sub-object before
361
+ # Unmarshal. gjson `.Raw` preserves JSON structure (works for both
362
+ # scalar-object and array paths). Path wins over schema: the
363
+ # schema type-checks the *extracted* value, not the envelope.
364
+ if node.response_path:
365
+ path = _go_str(node.response_path)
366
+ lines.append(f"{i2}body = []byte(gjson.GetBytes(body, {path}).Raw)")
360
367
  if getattr(node, "response_is_array", False):
361
- lines.append(f"{i2}var _val []{schema_go}")
368
+ lines.append(f"{i2}var val []{schema_go}")
362
369
  else:
363
- lines.append(f"{i2}var _val {schema_go}")
370
+ lines.append(f"{i2}var val {schema_go}")
364
371
  lines.append(
365
- f"{i2}if _perr := json.Unmarshal(_body, &_val); _perr != nil {{"
372
+ f"{i2}if perr := json.Unmarshal(body, &val); perr != nil {{"
366
373
  )
367
374
  lines.append(
368
- f'{i2}\treturn {zero}, fmt.Errorf("ssc-gen: parse {schema_go}: %w", _perr)'
375
+ f'{i2}\treturn {zero}, fmt.Errorf("ssc-gen: parse {schema_go}: %w", perr)'
369
376
  )
370
377
  lines.append(f"{i2}}}")
371
378
  if getattr(node, "response_is_array", False):
372
- lines.append(f"{i2}return _val, nil")
379
+ lines.append(f"{i2}return val, nil")
373
380
  else:
374
- lines.append(f"{i2}return &_val, nil")
381
+ lines.append(f"{i2}return &val, nil")
375
382
  else:
376
383
  lines.append(f"{i2}return {zero}, nil")
377
384
 
@@ -1812,6 +1812,8 @@ class GoVisitor(BaseWalker):
1812
1812
  self._builder.require_import('"fmt"')
1813
1813
  if node.response_schema:
1814
1814
  self._builder.require_import('"encoding/json"')
1815
+ if node.response_path:
1816
+ self._builder.require_import('"github.com/tidwall/gjson"')
1815
1817
  spec = node.http_request
1816
1818
  if spec.body_kind == "form" and isinstance(spec.body, dict):
1817
1819
  self._builder.require_import('"net/url"')
@@ -7,6 +7,8 @@ Called by JsVisitor via thin delegate methods. The HTTP library strategy
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import json
11
+
10
12
  from ssc_codegen.ast import (
11
13
  JsonDef,
12
14
  MatcherListDef,
@@ -334,7 +336,15 @@ def emit_method_rest(
334
336
  )
335
337
 
336
338
  fn_name = http.fn_name
337
- value_fn = "(_b) => null" if not node.response_schema else "null"
339
+ if node.response_path:
340
+ accessor = "".join(
341
+ f"[{json.dumps(p)}]" for p in node.response_path.split(".")
342
+ )
343
+ value_fn = f"(_b) => _b{accessor}"
344
+ elif not node.response_schema:
345
+ value_fn = "(_b) => null"
346
+ else:
347
+ value_fn = "null"
338
348
 
339
349
  if http_client == "axios":
340
350
  url_expr = render_value(spec.url)
@@ -420,9 +420,12 @@ def emit_method_rest(
420
420
  spec, i2, i3, include_method_url=False
421
421
  )
422
422
 
423
- void_kwarg: list[str] = []
424
- if not node.response_schema:
425
- void_kwarg = [f"{i3}value_fn=lambda _: None,"]
423
+ value_kwarg: list[str] = []
424
+ if node.response_path:
425
+ accessor = "".join(f"[{p!r}]" for p in node.response_path.split("."))
426
+ value_kwarg = [f"{i3}value_fn=lambda _b: _b{accessor},"]
427
+ elif not node.response_schema:
428
+ value_kwarg = [f"{i3}value_fn=lambda _: None,"]
426
429
 
427
430
  def _body(fn_name: str, await_kw: str) -> list[str]:
428
431
  body: list[str] = []
@@ -442,7 +445,7 @@ def emit_method_rest(
442
445
  f"{i3}client, {matchers_var}, {spec.method!r},"
443
446
  f" {render_value(spec.url)},"
444
447
  )
445
- body.extend(void_kwarg)
448
+ body.extend(value_kwarg)
446
449
  body.extend(kwargs_lines)
447
450
  body.append(f"{i2}))")
448
451
  return body
File without changes
File without changes
File without changes