ssc_codegen 0.29.4__tar.gz → 0.29.6__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.
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/PKG-INFO +1 -1
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/pyproject.toml +1 -1
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/struct.py +5 -5
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/js_pure.py +4 -3
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/py_bs4.py +5 -11
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/py_lxml.py +3 -3
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/py_render.py +4 -4
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/request_spec.py +2 -2
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/linting.py +34 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/struct_parser.py +2 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/.gitignore +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/LICENSE +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/README.md +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/__init__.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/_logging.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/__init__.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/array.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/base.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/cast.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/control.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/extract.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/helpers.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/jsondef.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/module.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/predicate_containers.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/predicate_ops.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/regex.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/selectors.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/string.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/transform.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/typedef.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/ast/types.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/helpers.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/converters/py_slax.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/__init__.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/adapter.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/contexts.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/expressions.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/format.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/module_handler.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/predicates.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/reader.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/core/type_checking.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/document_utils.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/exceptions.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/health.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/kdl/__init__.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/kdl/dict_reader.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/kdl/parser.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/kdl/reader.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/main.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/parsers/__init__.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/parsers/curl.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/parsers/http.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/pseudo_selectors.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/ssc_codegen/regex_utils.py +0 -0
- {ssc_codegen-0.29.4 → ssc_codegen-0.29.6}/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.
|
|
3
|
+
Version: 0.29.6
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
387
|
-
spec =
|
|
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
|
|
36
|
+
from ssc_codegen.ast.struct import parse_placeholder
|
|
37
37
|
|
|
38
38
|
JS_CONVERTER = BaseConverter(indent=" " * 2)
|
|
39
39
|
|
|
@@ -588,6 +588,7 @@ def pre_struct_check_method(node: a.CheckMethod, ctx: ConverterContext):
|
|
|
588
588
|
method_name = to_camel_case(node.name)
|
|
589
589
|
return [
|
|
590
590
|
f"{ctx.indent}{method_name}() " + "{",
|
|
591
|
+
f"{ctx.deeper().indent}let {ctx.var_name} = this._doc;",
|
|
591
592
|
]
|
|
592
593
|
|
|
593
594
|
|
|
@@ -1662,7 +1663,7 @@ def _js_array_join(ph: a.PlaceholderSpec) -> str:
|
|
|
1662
1663
|
def _js_render_value(v: str) -> str:
|
|
1663
1664
|
"""Render a RequestSpec string value as a JS expression."""
|
|
1664
1665
|
if m := _PH.fullmatch(v):
|
|
1665
|
-
ph =
|
|
1666
|
+
ph = parse_placeholder(m)
|
|
1666
1667
|
if ph.is_array and ph.style in ("csv", "pipe", "space"):
|
|
1667
1668
|
return _js_array_join(ph)
|
|
1668
1669
|
return ph.name
|
|
@@ -1685,7 +1686,7 @@ def _js_render_obj(d: dict[str, str]) -> str:
|
|
|
1685
1686
|
|
|
1686
1687
|
def _js_dict_entry_placeholder(v: str) -> a.PlaceholderSpec | None:
|
|
1687
1688
|
m = _PH.fullmatch(str(v))
|
|
1688
|
-
return
|
|
1689
|
+
return parse_placeholder(m) if m else None
|
|
1689
1690
|
|
|
1690
1691
|
|
|
1691
1692
|
def _js_dict_needs_builder(d: dict[str, str]) -> bool:
|
|
@@ -307,7 +307,7 @@ def pre_utilities(node: a.Utilities, ctx: ConverterContext):
|
|
|
307
307
|
runtime = ctx.meta.get("runtime_module")
|
|
308
308
|
if runtime:
|
|
309
309
|
names = runtime_export_names(node)
|
|
310
|
-
return [f"from {runtime} import " + ", ".join(names), ""]
|
|
310
|
+
return [f"from .{runtime} import " + ", ".join(names), ""]
|
|
311
311
|
|
|
312
312
|
if module_is_rest_only(node):
|
|
313
313
|
lines = []
|
|
@@ -808,16 +808,10 @@ def pre_struct_check_method(node: a.CheckMethod, ctx: ConverterContext):
|
|
|
808
808
|
from ssc_codegen.converters.helpers import to_snake_case
|
|
809
809
|
|
|
810
810
|
method_name = to_snake_case(node.name)
|
|
811
|
-
return [
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
# def post_struct_check_method(node: a.CheckMethod, ctx: ConverterContext):
|
|
816
|
-
# return [
|
|
817
|
-
# " return True",
|
|
818
|
-
# " except Exception:",
|
|
819
|
-
# " return False",
|
|
820
|
-
# ]
|
|
811
|
+
return [
|
|
812
|
+
f" def {method_name}(self) -> bool:",
|
|
813
|
+
f" {ctx.var_name} = self._doc",
|
|
814
|
+
]
|
|
821
815
|
|
|
822
816
|
|
|
823
817
|
@PY_BASE_CONVERTER(a.SplitDoc)
|
|
@@ -87,7 +87,7 @@ def pre_utilities(node: a.Utilities, ctx: ConverterContext):
|
|
|
87
87
|
if runtime:
|
|
88
88
|
names = py_bs4.runtime_export_names(node)
|
|
89
89
|
names.append(_FALLBACK_HTML_EXPORT)
|
|
90
|
-
return [f"from {runtime} import " + ", ".join(names), ""]
|
|
90
|
+
return [f"from .{runtime} import " + ", ".join(names), ""]
|
|
91
91
|
|
|
92
92
|
if py_bs4.module_is_rest_only(node):
|
|
93
93
|
lines = []
|
|
@@ -352,7 +352,7 @@ def pre_expr_attr(node: a.Attr, ctx: ConverterContext):
|
|
|
352
352
|
@PY_LXML_CONVERTER(a.PredCss)
|
|
353
353
|
def pre_expr_pred_css(node: a.PredCss, ctx: ConverterContext):
|
|
354
354
|
query = repr(node.query)
|
|
355
|
-
cond = f"
|
|
355
|
+
cond = f"i.cssselect({query}) is not None"
|
|
356
356
|
if ctx.index == 0:
|
|
357
357
|
return ctx.indent + cond
|
|
358
358
|
return ctx.indent + f"and {cond}"
|
|
@@ -361,7 +361,7 @@ def pre_expr_pred_css(node: a.PredCss, ctx: ConverterContext):
|
|
|
361
361
|
@PY_LXML_CONVERTER(a.PredXpath)
|
|
362
362
|
def pre_expr_pred_xpath(node: a.PredXpath, ctx: ConverterContext):
|
|
363
363
|
query = repr(node.query)
|
|
364
|
-
cond = f"
|
|
364
|
+
cond = f"i.xpath({query}) is not None"
|
|
365
365
|
if ctx.index == 0:
|
|
366
366
|
return ctx.indent + cond
|
|
367
367
|
return ctx.indent + f"and {cond}"
|
|
@@ -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,
|
|
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 =
|
|
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
|
|
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 =
|
|
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,
|
|
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 =
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|