ssc_codegen 0.19.0__tar.gz → 0.19.2__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 (60) hide show
  1. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/PKG-INFO +1 -1
  2. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/pyproject.toml +1 -1
  3. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/converters/py_bs4.py +63 -53
  4. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/converters/py_lxml.py +4 -1
  5. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/converters/py_parsel.py +1 -0
  6. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/converters/py_slax.py +1 -0
  7. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/.gitignore +0 -0
  8. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/LICENSE +0 -0
  9. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/README.md +0 -0
  10. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/__init__.py +0 -0
  11. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/_logging.py +0 -0
  12. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/__init__.py +0 -0
  13. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/array.py +0 -0
  14. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/base.py +0 -0
  15. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/cast.py +0 -0
  16. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/control.py +0 -0
  17. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/extract.py +0 -0
  18. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/helpers.py +0 -0
  19. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/jsondef.py +0 -0
  20. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/module.py +0 -0
  21. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/predicate_containers.py +0 -0
  22. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/predicate_ops.py +0 -0
  23. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/regex.py +0 -0
  24. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/selectors.py +0 -0
  25. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/string.py +0 -0
  26. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/struct.py +0 -0
  27. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/transform.py +0 -0
  28. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/typedef.py +0 -0
  29. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/ast/types.py +0 -0
  30. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/converters/base.py +0 -0
  31. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/converters/go_goquery.py +0 -0
  32. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/converters/helpers.py +0 -0
  33. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/converters/js_pure.py +0 -0
  34. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/document_utils.py +0 -0
  35. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/exceptions.py +0 -0
  36. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/health.py +0 -0
  37. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/kdl/__init__.py +0 -0
  38. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/kdl/parser.py +0 -0
  39. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/__init__.py +0 -0
  40. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/_kdl_lang.py +0 -0
  41. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/base.py +0 -0
  42. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/errors.py +0 -0
  43. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/format_errors.py +0 -0
  44. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/metadata.py +0 -0
  45. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/navigation.py +0 -0
  46. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/path.py +0 -0
  47. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/rule_keywords.py +0 -0
  48. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/rules.py +0 -0
  49. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/rules_struct.py +0 -0
  50. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/type_rules.py +0 -0
  51. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/linter/types.py +0 -0
  52. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/main.py +0 -0
  53. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/parser.py +0 -0
  54. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/parsers/__init__.py +0 -0
  55. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/parsers/curl.py +0 -0
  56. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/parsers/http.py +0 -0
  57. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/parsers/spec.py +0 -0
  58. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/pseudo_selectors.py +0 -0
  59. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/ssc_codegen/regex_utils.py +0 -0
  60. {ssc_codegen-0.19.0 → ssc_codegen-0.19.2}/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.19.0
3
+ Version: 0.19.2
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.19.0"
3
+ version = "0.19.2"
4
4
  description = "Python-dsl code converter to html parser for web scraping "
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -189,6 +189,16 @@ def _module_has_rest(node) -> bool:
189
189
  )
190
190
 
191
191
 
192
+ def rest_imports(node) -> list[str]:
193
+ """Extra imports required when the module has any `struct type=rest`."""
194
+ if not _module_has_rest(node):
195
+ return []
196
+ return [
197
+ "from dataclasses import dataclass, field",
198
+ "from typing import Generic, Literal, Mapping, TypeVar",
199
+ ]
200
+
201
+
192
202
  @PY_BASE_CONVERTER(Imports)
193
203
  def pre_imports(node: Imports, _: ConverterContext):
194
204
  base_imports = [
@@ -198,11 +208,7 @@ def pre_imports(node: Imports, _: ConverterContext):
198
208
  "from typing import TypedDict, Optional, Any, List, Dict, Union",
199
209
  "from html import unescape as _html_unescape",
200
210
  ]
201
- if _module_has_rest(node):
202
- base_imports.append("from dataclasses import dataclass, field")
203
- base_imports.append(
204
- "from typing import Generic, Literal, Mapping, TypeVar"
205
- )
211
+ base_imports.extend(rest_imports(node))
206
212
 
207
213
  # Get transform imports for Python (already collected during parsing)
208
214
  transform_imports = sorted(node.transform_imports.get("py", set()))
@@ -279,51 +285,55 @@ def pre_utilities(node: Utilities, _: ConverterContext):
279
285
  "UNMATCHED_TABLE_ROW = _UnmatchedTableRow()",
280
286
  "\n\n",
281
287
  ]
282
- if _module_has_rest(node):
283
- lines.extend(
284
- [
285
- "_T = TypeVar('_T')",
286
- "_E = TypeVar('_E')",
287
- "\n",
288
- "@dataclass(frozen=True, kw_only=True)",
289
- "class Ok(Generic[_T]):",
290
- " status: int",
291
- " headers: Mapping[str, str]",
292
- " value: _T",
293
- " is_ok: Literal[True] = True",
294
- "\n",
295
- "@dataclass(frozen=True, kw_only=True)",
296
- "class Err(Generic[_E]):",
297
- " status: int",
298
- " headers: Mapping[str, str]",
299
- " value: _E",
300
- " is_ok: Literal[False] = False",
301
- "\n",
302
- "@dataclass(frozen=True, kw_only=True)",
303
- "class UnknownErr(Err[Any]):",
304
- " pass",
305
- "\n",
306
- "@dataclass(frozen=True, kw_only=True)",
307
- "class TransportErr(Err[None]):",
308
- " status: Literal[0] = 0",
309
- " cause: str = ''",
310
- " value: None = None",
311
- " headers: Mapping[str, str] = field(default_factory=dict)",
312
- "\n\n",
313
- "def _parse_response(_resp):",
314
- " _status = _resp.status_code",
315
- " _headers = {k.lower(): v for k, v in _resp.headers.items()}",
316
- " try:",
317
- " _body = _resp.json()",
318
- " except Exception:",
319
- " _body = None",
320
- " return _status, _headers, _body",
321
- "\n\n",
322
- ]
323
- )
288
+ lines.extend(rest_utilities(node))
324
289
  return lines
325
290
 
326
291
 
292
+ def rest_utilities(node) -> list[str]:
293
+ """Ok/Err/UnknownErr/TransportErr + _parse_response block; empty if no REST."""
294
+ if not _module_has_rest(node):
295
+ return []
296
+ return [
297
+ "_T = TypeVar('_T')",
298
+ "_E = TypeVar('_E')",
299
+ "\n",
300
+ "@dataclass(frozen=True)",
301
+ "class Ok(Generic[_T]):",
302
+ " status: int = 0",
303
+ " headers: Mapping[str, str] = field(default_factory=dict)",
304
+ " value: _T = None # type: ignore[assignment]",
305
+ " is_ok: Literal[True] = True",
306
+ "\n",
307
+ "@dataclass(frozen=True)",
308
+ "class Err(Generic[_E]):",
309
+ " status: int = 0",
310
+ " headers: Mapping[str, str] = field(default_factory=dict)",
311
+ " value: _E = None # type: ignore[assignment]",
312
+ " is_ok: Literal[False] = False",
313
+ "\n",
314
+ "@dataclass(frozen=True)",
315
+ "class UnknownErr(Err[Any]):",
316
+ " pass",
317
+ "\n",
318
+ "@dataclass(frozen=True)",
319
+ "class TransportErr(Err[None]):",
320
+ " status: Literal[0] = 0",
321
+ " cause: str = ''",
322
+ " value: None = None",
323
+ " headers: Mapping[str, str] = field(default_factory=dict)",
324
+ "\n\n",
325
+ "def _parse_response(_resp):",
326
+ " _status = _resp.status_code",
327
+ " _headers = {k.lower(): v for k, v in _resp.headers.items()}",
328
+ " try:",
329
+ " _body = _resp.json()",
330
+ " except Exception:",
331
+ " _body = None",
332
+ " return _status, _headers, _body",
333
+ "\n\n",
334
+ ]
335
+
336
+
327
337
  @PY_BASE_CONVERTER(JsonDef, post_callback="})")
328
338
  def pre_json_struct(node: JsonDef, _: ConverterContext):
329
339
  name = to_pascal_case(node.name)
@@ -415,7 +425,7 @@ def _rest_err_union_type(struct: Struct) -> str:
415
425
  if cls_name not in seen:
416
426
  seen.add(cls_name)
417
427
  variants.append(cls_name)
418
- return " | ".join([*variants, "UnknownErr", "None"])
428
+ return "Union[" + ", ".join([*variants, "UnknownErr", "None"]) + "]"
419
429
 
420
430
 
421
431
  def _emit_dispatch_err_py(node: Struct, ctx: ConverterContext) -> list[str]:
@@ -498,7 +508,7 @@ def pre_struct(node: Struct, ctx: ConverterContext):
498
508
  continue
499
509
  seen.add(cls_name)
500
510
  value_type = _err_value_type(err, node)
501
- lines.append("@dataclass(frozen=True, kw_only=True)")
511
+ lines.append("@dataclass(frozen=True)")
502
512
  lines.append(f"class {cls_name}(Err[{value_type}]):")
503
513
  lines.append(f" status: Literal[{err.status}] = {err.status}")
504
514
  lines.append("")
@@ -1650,14 +1660,14 @@ def _ph_to_py_annotation(ph: PlaceholderSpec) -> tuple[str, str]:
1650
1660
  """Return (annotation, default_suffix). Suffix is '' or ' = None'."""
1651
1661
  anno = _PY_PRIM_ANNO[ph.type_name]
1652
1662
  if ph.is_array:
1653
- anno = f"list[{anno}]"
1663
+ anno = f"List[{anno}]"
1654
1664
  if ph.is_optional:
1655
- return f"{anno} | None", " = None"
1665
+ return f"Optional[{anno}]", " = None"
1656
1666
  return anno, ""
1657
1667
 
1658
1668
 
1659
1669
  def _render_signature_params(placeholders: list[PlaceholderSpec]) -> str:
1660
- """Build keyword-only parameters clause: ', *, a: int, b: str | None = None'."""
1670
+ """Build keyword-only parameters clause: ', *, a: int, b: Optional[str] = None'."""
1661
1671
  if not placeholders:
1662
1672
  return ""
1663
1673
  required = [p for p in placeholders if not p.is_optional]
@@ -1698,7 +1708,7 @@ def _resolve_response_type(node: RequestConfig) -> str:
1698
1708
  seen.add(cls_name)
1699
1709
  err_variants.append(cls_name)
1700
1710
  parts = [f"Ok[{payload}]", *err_variants, "UnknownErr", "TransportErr"]
1701
- return " | ".join(parts)
1711
+ return "Union[" + ", ".join(parts) + "]"
1702
1712
 
1703
1713
 
1704
1714
  def _build_rest_method(
@@ -81,6 +81,7 @@ def pre_imports(node: Imports, _: ConverterContext):
81
81
  "from typing import TypedDict, Optional, Any, List, Dict, Union",
82
82
  "from html import unescape as _html_unescape",
83
83
  ]
84
+ base_imports.extend(py_bs4.rest_imports(node))
84
85
 
85
86
  # Get transform imports for Python (already collected during parsing)
86
87
  transform_imports = sorted(node.transform_imports.get("py", set()))
@@ -100,7 +101,7 @@ def post_imports(node: Imports, ctx: ConverterContext):
100
101
 
101
102
  @PY_LXML_CONVERTER(Utilities)
102
103
  def pre_utilities(node: Utilities, _: ConverterContext):
103
- return [
104
+ lines = [
104
105
  'FALLBACK_HTML_STR = "<html><body></body></html>"',
105
106
  "_RE_HEX_ENTITY = re.compile(r'&#x([0-9a-fA-F]+);')",
106
107
  "_RE_UNICODE_ENTITY = re.compile(r'\\\\u([0-9a-fA-F]{4})')",
@@ -144,6 +145,8 @@ def pre_utilities(node: Utilities, _: ConverterContext):
144
145
  "UNMATCHED_TABLE_ROW = _UnmatchedTableRow()",
145
146
  "\n\n",
146
147
  ]
148
+ lines.extend(py_bs4.rest_utilities(node))
149
+ return lines
147
150
 
148
151
 
149
152
  # Override struct __init__ to use lxml instead of BeautifulSoup
@@ -67,6 +67,7 @@ def pre_imports(node: Imports, _: ConverterContext):
67
67
  "from typing import TypedDict, Optional, Any, List, Dict, Union",
68
68
  "from html import unescape as _html_unescape",
69
69
  ]
70
+ base_imports.extend(py_bs4.rest_imports(node))
70
71
 
71
72
  transform_imports = sorted(node.transform_imports.get("py", set()))
72
73
 
@@ -66,6 +66,7 @@ def pre_imports(node: Imports, _: ConverterContext):
66
66
  "from typing import TypedDict, Optional, Any, List, Dict, Union",
67
67
  "from html import unescape as _html_unescape",
68
68
  ]
69
+ base_imports.extend(py_bs4.rest_imports(node))
69
70
 
70
71
  transform_imports = sorted(node.transform_imports.get("py", set()))
71
72
 
File without changes
File without changes
File without changes