ssc_codegen 0.29.4__tar.gz → 0.29.5__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 (59) hide show
  1. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/PKG-INFO +1 -1
  2. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/pyproject.toml +1 -1
  3. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/struct.py +5 -5
  4. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/js_pure.py +3 -3
  5. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/py_render.py +4 -4
  6. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/request_spec.py +2 -2
  7. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/linting.py +34 -0
  8. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/struct_parser.py +2 -0
  9. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/.gitignore +0 -0
  10. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/LICENSE +0 -0
  11. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/README.md +0 -0
  12. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/__init__.py +0 -0
  13. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/_logging.py +0 -0
  14. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/__init__.py +0 -0
  15. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/array.py +0 -0
  16. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/base.py +0 -0
  17. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/cast.py +0 -0
  18. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/control.py +0 -0
  19. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/extract.py +0 -0
  20. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/helpers.py +0 -0
  21. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/jsondef.py +0 -0
  22. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/module.py +0 -0
  23. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/predicate_containers.py +0 -0
  24. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/predicate_ops.py +0 -0
  25. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/regex.py +0 -0
  26. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/selectors.py +0 -0
  27. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/string.py +0 -0
  28. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/transform.py +0 -0
  29. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/typedef.py +0 -0
  30. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/ast/types.py +0 -0
  31. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/base.py +0 -0
  32. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/helpers.py +0 -0
  33. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/py_bs4.py +0 -0
  34. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/py_lxml.py +0 -0
  35. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/py_parsel.py +0 -0
  36. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/converters/py_slax.py +0 -0
  37. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/__init__.py +0 -0
  38. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/adapter.py +0 -0
  39. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/contexts.py +0 -0
  40. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/expressions.py +0 -0
  41. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/format.py +0 -0
  42. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/module_handler.py +0 -0
  43. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/predicates.py +0 -0
  44. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/reader.py +0 -0
  45. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/core/type_checking.py +0 -0
  46. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/document_utils.py +0 -0
  47. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/exceptions.py +0 -0
  48. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/health.py +0 -0
  49. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/kdl/__init__.py +0 -0
  50. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/kdl/dict_reader.py +0 -0
  51. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/kdl/parser.py +0 -0
  52. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/kdl/reader.py +0 -0
  53. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/main.py +0 -0
  54. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/parsers/__init__.py +0 -0
  55. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/parsers/curl.py +0 -0
  56. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/parsers/http.py +0 -0
  57. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/pseudo_selectors.py +0 -0
  58. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/ssc_codegen/regex_utils.py +0 -0
  59. {ssc_codegen-0.29.4 → ssc_codegen-0.29.5}/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.4
3
+ Version: 0.29.5
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.29.4"
3
+ version = "0.29.5"
4
4
  description = "Python-dsl code converter to html parser for web scraping "
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -13,7 +13,7 @@ from .types import VariableType, StructType
13
13
  # PRIM = str | int | float | bool (default: str)
14
14
  # STYLE = repeat | csv | bracket | pipe | space (arrays only; default: repeat)
15
15
  # Legacy `{{name}}` remains valid (groups 2-5 = None → str, scalar, required).
16
- _PLACEHOLDER_RE = _re.compile(
16
+ PLACEHOLDER_RE = _re.compile(
17
17
  r"\{\{"
18
18
  r"([A-Za-z][A-Za-z0-9_-]*)"
19
19
  r"(?::(str|int|float|bool))?"
@@ -25,7 +25,7 @@ _PLACEHOLDER_RE = _re.compile(
25
25
 
26
26
  # Widened pattern — any `{{…}}`-shaped token. Used by the linter to flag
27
27
  # malformed placeholders that the strict _PLACEHOLDER_RE would silently skip.
28
- _PLACEHOLDER_WIDE_RE = _re.compile(r"\{\{([^{}]*)\}\}")
28
+ PLACEHOLDER_WIDE_RE = _re.compile(r"\{\{([^{}]*)\}\}")
29
29
 
30
30
 
31
31
  @dataclass
@@ -39,7 +39,7 @@ class PlaceholderSpec:
39
39
  style: str | None = None # None == default "repeat" when is_array
40
40
 
41
41
 
42
- def _parse_placeholder(match: "_re.Match[str]") -> PlaceholderSpec:
42
+ def parse_placeholder(match: "_re.Match[str]") -> PlaceholderSpec:
43
43
  return PlaceholderSpec(
44
44
  name=match.group(1),
45
45
  type_name=match.group(2) or "str",
@@ -383,8 +383,8 @@ class RequestConfig(Node):
383
383
  """Unique placeholders in declaration order (dedup by name)."""
384
384
  seen: set[str] = set()
385
385
  result: list[PlaceholderSpec] = []
386
- for m in _PLACEHOLDER_RE.finditer(self.raw_payload):
387
- spec = _parse_placeholder(m)
386
+ for m in PLACEHOLDER_RE.finditer(self.raw_payload):
387
+ spec = parse_placeholder(m)
388
388
  if spec.name not in seen:
389
389
  seen.add(spec.name)
390
390
  result.append(spec)
@@ -33,7 +33,7 @@ from ssc_codegen.converters.request_spec import (
33
33
  normalize_placeholder_names,
34
34
  _PH,
35
35
  )
36
- from ssc_codegen.ast.struct import _parse_placeholder
36
+ from ssc_codegen.ast.struct import parse_placeholder
37
37
 
38
38
  JS_CONVERTER = BaseConverter(indent=" " * 2)
39
39
 
@@ -1662,7 +1662,7 @@ def _js_array_join(ph: a.PlaceholderSpec) -> str:
1662
1662
  def _js_render_value(v: str) -> str:
1663
1663
  """Render a RequestSpec string value as a JS expression."""
1664
1664
  if m := _PH.fullmatch(v):
1665
- ph = _parse_placeholder(m)
1665
+ ph = parse_placeholder(m)
1666
1666
  if ph.is_array and ph.style in ("csv", "pipe", "space"):
1667
1667
  return _js_array_join(ph)
1668
1668
  return ph.name
@@ -1685,7 +1685,7 @@ def _js_render_obj(d: dict[str, str]) -> str:
1685
1685
 
1686
1686
  def _js_dict_entry_placeholder(v: str) -> a.PlaceholderSpec | None:
1687
1687
  m = _PH.fullmatch(str(v))
1688
- return _parse_placeholder(m) if m else None
1688
+ return parse_placeholder(m) if m else None
1689
1689
 
1690
1690
 
1691
1691
  def _js_dict_needs_builder(d: dict[str, str]) -> bool:
@@ -7,7 +7,7 @@ fragments suitable for `requests(...)` / `httpx(...)` call arguments.
7
7
  import json
8
8
  import re
9
9
 
10
- from ssc_codegen.ast.struct import PlaceholderSpec, _parse_placeholder
10
+ from ssc_codegen.ast.struct import PlaceholderSpec, parse_placeholder
11
11
  from ssc_codegen.converters.request_spec import _PH, RequestSpec
12
12
 
13
13
  __all__ = [
@@ -46,7 +46,7 @@ def render_value(v: str) -> str:
46
46
  ``Mozilla/5.0`` -> ``"Mozilla/5.0"`` (string literal)
47
47
  """
48
48
  if m := _PH.fullmatch(v):
49
- ph = _parse_placeholder(m)
49
+ ph = parse_placeholder(m)
50
50
  if ph.is_array and ph.style in ("csv", "pipe", "space"):
51
51
  return _render_array_join(ph)
52
52
  return ph.name
@@ -94,7 +94,7 @@ def render_dict(d: dict[str, str], *, indent: str = "") -> str:
94
94
  def _dict_entry_placeholder(v: str) -> PlaceholderSpec | None:
95
95
  """Return PlaceholderSpec if *v* is a fullmatch placeholder, else None."""
96
96
  m = _PH.fullmatch(str(v))
97
- return _parse_placeholder(m) if m else None
97
+ return parse_placeholder(m) if m else None
98
98
 
99
99
 
100
100
  def dict_needs_builder(d: dict[str, str]) -> bool:
@@ -160,7 +160,7 @@ def render_json_body(raw: str) -> str:
160
160
  if raw[i : i + 2] == "{{":
161
161
  m = _PH.match(raw, i)
162
162
  if m is not None:
163
- name = _parse_placeholder(m).name
163
+ name = parse_placeholder(m).name
164
164
  key = f"__SSC_PH_{len(sentinels)}__"
165
165
  sentinels[key] = name
166
166
  out.append(key if in_string else '"' + key + '"')
@@ -12,7 +12,7 @@ from dataclasses import dataclass, field
12
12
  from typing import Callable
13
13
  from urllib.parse import urlparse, urlunparse
14
14
 
15
- from ssc_codegen.ast.struct import PlaceholderSpec, _parse_placeholder
15
+ from ssc_codegen.ast.struct import PlaceholderSpec, parse_placeholder
16
16
  from ssc_codegen.parsers.curl import parse_curl_command
17
17
  from ssc_codegen.parsers.http import parse_http_request
18
18
 
@@ -48,7 +48,7 @@ class RequestSpec:
48
48
  result: list[PlaceholderSpec] = []
49
49
  for text in _iter_strings(self):
50
50
  for m in _PH.finditer(text):
51
- spec = _parse_placeholder(m)
51
+ spec = parse_placeholder(m)
52
52
  if spec.name not in seen:
53
53
  seen.add(spec.name)
54
54
  result.append(spec)
@@ -6,6 +6,7 @@ import difflib as _difflib
6
6
  import re as _re
7
7
 
8
8
  from ssc_codegen.ast import JsonDefField, Module, VariableType
9
+ from ssc_codegen.ast.struct import PLACEHOLDER_RE, PLACEHOLDER_WIDE_RE
9
10
  from ssc_codegen.kdl import KdlNode
10
11
  from ssc_codegen.core.contexts import (
11
12
  DefineKind,
@@ -799,6 +800,39 @@ def lint_reserved_field(
799
800
  )
800
801
 
801
802
 
803
+ def lint_request_placeholders(
804
+ node: KdlNode, raw_payload: str, lint: LintContext
805
+ ) -> None:
806
+ """Validate placeholder names in an @request raw payload.
807
+
808
+ Convention: uppercase {{NAME}} is a define substitution (only resolved
809
+ within ``define`` values), lowercase {{name}} is a runtime fetch() param.
810
+ Define substitution does NOT run on @request payloads, so any remaining
811
+ uppercase tokens are bugs that silently become literal strings.
812
+ """
813
+ for m in PLACEHOLDER_WIDE_RE.finditer(raw_payload):
814
+ token = m.group(0)
815
+ strict = PLACEHOLDER_RE.match(token)
816
+ if strict is None:
817
+ lint.error(
818
+ node,
819
+ message=f"malformed placeholder {token!r} in @request",
820
+ code="E002",
821
+ hint="expected syntax: {{name}} or {{name:type}} (lowercase)",
822
+ )
823
+ continue
824
+ name = strict.group(1)
825
+ if name != name.lower():
826
+ lint.error(
827
+ node,
828
+ message=f"placeholder '{{{{{name}}}}}' in @request must be lowercase; "
829
+ f"uppercase names are define substitutions which don't resolve in @request",
830
+ code="E002",
831
+ hint=f"use lowercase for runtime params (e.g. {{{{{name.lower()}}}}}), "
832
+ f"or compose the URL in a define first",
833
+ )
834
+
835
+
802
836
  def lint_regular_field(
803
837
  node: KdlNode,
804
838
  field_name: str,
@@ -32,6 +32,7 @@ from ssc_codegen.kdl import KdlArg, KdlNode
32
32
 
33
33
  from ssc_codegen.core.contexts import LintContext, ParseContext, WalkCtx
34
34
  from ssc_codegen.core.expressions import parse_expressions
35
+ from ssc_codegen.core.linting import lint_request_placeholders
35
36
  from ssc_codegen.core.type_checking import check_pipeline_types
36
37
 
37
38
 
@@ -103,6 +104,7 @@ def parse_struct(
103
104
  )
104
105
  req = RequestConfig(parent=parent)
105
106
  req.raw_payload = raw_payload
107
+ lint_request_placeholders(node, raw_payload, lint)
106
108
  req.response_path = str(
107
109
  node.properties.get(
108
110
  "response-path",
File without changes
File without changes
File without changes