ssc_codegen 0.40.0__tar.gz → 0.41.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 (98) hide show
  1. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/PKG-INFO +1 -1
  2. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/pyproject.toml +124 -124
  3. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/generation/builder.py +9 -2
  4. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/generation/runtime.py +9 -3
  5. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/main.py +39 -1
  6. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/html_libs/base.py +5 -1
  7. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/html_libs/bs4.py +59 -9
  8. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/http_libs/aiohttp.py +81 -59
  9. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/http_libs/base.py +6 -1
  10. ssc_codegen-0.41.0/ssc_codegen/targets/python/http_libs/httpx.py +181 -0
  11. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/http_libs/requests.py +67 -44
  12. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/rest.py +92 -51
  13. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/visitor.py +55 -16
  14. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/resolver.py +282 -227
  15. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/spec.py +67 -61
  16. ssc_codegen-0.40.0/ssc_codegen/targets/python/http_libs/httpx.py +0 -139
  17. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/.gitignore +0 -0
  18. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/LICENSE +0 -0
  19. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/README.md +0 -0
  20. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/__init__.py +0 -0
  21. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/_logging.py +0 -0
  22. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/__init__.py +0 -0
  23. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/array.py +0 -0
  24. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/base.py +0 -0
  25. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/cast.py +0 -0
  26. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/control.py +0 -0
  27. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/extension.py +0 -0
  28. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/extract.py +0 -0
  29. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/function.py +0 -0
  30. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/jsondef.py +0 -0
  31. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/module.py +0 -0
  32. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/predicate_containers.py +0 -0
  33. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/predicate_ops.py +0 -0
  34. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/regex.py +0 -0
  35. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/rest.py +0 -0
  36. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/selectors.py +0 -0
  37. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/string.py +0 -0
  38. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/struct.py +0 -0
  39. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/typedef.py +0 -0
  40. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/ast/types.py +0 -0
  41. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/__init__.py +0 -0
  42. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/contexts.py +0 -0
  43. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/expressions.py +0 -0
  44. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/extensions.py +0 -0
  45. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/format.py +0 -0
  46. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/imports.py +0 -0
  47. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/linter.py +0 -0
  48. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/module_handler.py +0 -0
  49. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/predicates.py +0 -0
  50. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/reader.py +0 -0
  51. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/rest_artifacts.py +0 -0
  52. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/struct_parser.py +0 -0
  53. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/core/type_checking.py +0 -0
  54. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/exceptions.py +0 -0
  55. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/explore.py +0 -0
  56. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/generation/__init__.py +0 -0
  57. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/health.py +0 -0
  58. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/json_to_kdl.py +0 -0
  59. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/naming.py +0 -0
  60. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/parsers/__init__.py +0 -0
  61. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/parsers/curl.py +0 -0
  62. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/parsers/http.py +0 -0
  63. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/regex_utils.py +0 -0
  64. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/request_spec.py +0 -0
  65. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/symbols.py +0 -0
  66. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/__init__.py +0 -0
  67. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/__init__.py +0 -0
  68. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/http_libs/__init__.py +0 -0
  69. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/http_libs/base.py +0 -0
  70. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/http_libs/nethttp.py +0 -0
  71. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/literals.py +0 -0
  72. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/regex.py +0 -0
  73. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/rest.py +0 -0
  74. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/runtime.py +0 -0
  75. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/golang/visitor.py +0 -0
  76. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/javascript/__init__.py +0 -0
  77. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/javascript/http_libs/__init__.py +0 -0
  78. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/javascript/http_libs/axios.py +0 -0
  79. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/javascript/http_libs/base.py +0 -0
  80. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/javascript/http_libs/fetch.py +0 -0
  81. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/javascript/rest.py +0 -0
  82. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/javascript/visitor.py +0 -0
  83. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/profile.py +0 -0
  84. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/__init__.py +0 -0
  85. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/html_libs/__init__.py +0 -0
  86. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/html_libs/lxml.py +0 -0
  87. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/html_libs/parsel.py +0 -0
  88. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/html_libs/slax.py +0 -0
  89. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/python/http_libs/__init__.py +0 -0
  90. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/rust/__init__.py +0 -0
  91. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/rust/regex.py +0 -0
  92. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/rust/rest.py +0 -0
  93. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/rust/runtime.py +0 -0
  94. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/targets/rust/visitor.py +0 -0
  95. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/traversal/__init__.py +0 -0
  96. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/traversal/context.py +0 -0
  97. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/traversal/utils.py +0 -0
  98. {ssc_codegen-0.40.0 → ssc_codegen-0.41.0}/ssc_codegen/traversal/walker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ssc_codegen
3
- Version: 0.40.0
3
+ Version: 0.41.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
@@ -1,124 +1,124 @@
1
- [project]
2
- name = "ssc_codegen"
3
- version = "0.40.0"
4
- description = "Python-dsl code converter to html parser for web scraping "
5
- readme = "README.md"
6
- requires-python = ">=3.10"
7
- dependencies = [
8
- "bs4>=0.0.2",
9
- "colorama>=0.4.6 ; sys_platform == 'win32'",
10
- "cssselect>=1.2.0",
11
- "lxml>=5.3.0",
12
- "soupsieve>=2.6",
13
- "typer>0.26",
14
- "typing_extensions;python_version<'3.11'",
15
- "kdlquery>=1.3.0",
16
- "httpx>=0.28.1",
17
- ]
18
-
19
- classifiers = [
20
- "Development Status :: 3 - Alpha",
21
- "Environment :: Console",
22
- "Intended Audience :: Developers",
23
- "License :: OSI Approved :: MIT License",
24
- "Topic :: Software Development :: Code Generators",
25
- "Topic :: Text Processing :: Markup :: HTML",
26
- "Topic :: Utilities",
27
- "Programming Language :: Python :: 3.10",
28
- "Programming Language :: Python :: 3.11",
29
- "Programming Language :: Python :: 3.12",
30
- "Programming Language :: Python :: 3.13",
31
- ]
32
-
33
- [project.urls]
34
- Documentation = "https://github.com/vypivshiy/selector_schema_codegen#readme"
35
- Issues = "https://github.com/vypivshiy/selector_schema_codegen/issues"
36
- Source = "https://github.com/vypivshiy/selector_schema_codegen"
37
- Examples = "https://github.com/vypivshiy/selector_schema_codegen/examples"
38
-
39
-
40
- [project.scripts]
41
- ssc-gen = 'ssc_codegen.main:main'
42
-
43
- [build-system]
44
- requires = ["hatchling"]
45
- build-backend = "hatchling.build"
46
-
47
- [tool.hatch.build.targets.sdist]
48
- include = [
49
- "ssc_codegen/**",
50
- ]
51
-
52
- [tool.hatch.build.targets.wheel]
53
- packages = ["ssc_codegen"]
54
-
55
- [dependency-groups]
56
- dev = [
57
- "aiohttp>=3.9,<3.12", # aioresponses 0.7.x breaks on 3.12+ (stream_writer)
58
- "aioresponses>=0.7",
59
- "bs4>=0.0.2",
60
- "coverage>=7.6.12",
61
- "httpx>=0.28.1",
62
- "hypothesis>=6.151.9",
63
- "lxml>=5.3.0",
64
- "mypy>=1.14.1",
65
- "parsel>=1.9.1",
66
- "pytest>=8.3.4",
67
- "pytest-xdist>=3.6.0",
68
- "respx>=0.23.1",
69
- "responses>=0.25",
70
- "ruff>=0.9.3",
71
- "selectolax>=0.3.27",
72
- ]
73
- docs = [
74
- "mkdocs>=1.6.0",
75
- "mkdocs-material>=9.5.0",
76
- "mkdocstrings[python]>=0.27.0",
77
- "mkdocs-click>=0.8.1",
78
- ]
79
-
80
- [tool.ruff]
81
- target-version = "py310"
82
- line-length = 80
83
- exclude = ["__init__.py"]
84
-
85
- [tool.mypy]
86
- python_version = "3.10"
87
- show_error_codes = true
88
- [[tool.mypy.overrides]]
89
- module="tests.*"
90
- disallow_untyped_defs = false
91
-
92
-
93
- [[tool.mypy.overrides]]
94
- module = ["colorama", "lxml", "lxml.*"]
95
- ignore_missing_imports = true
96
-
97
- [tool.coverage.report]
98
- exclude_also = [
99
- 'def __repr__',
100
- 'if self.debug:',
101
- 'if settings.DEBUG',
102
- 'raise AssertionError',
103
- 'raise NotImplementedError',
104
- 'if 0:',
105
- 'if __name__ == .__main__.:',
106
- 'if TYPE_CHECKING:',
107
- 'class .*\bProtocol\):',
108
- '@(abc\.)?abstractmethod',
109
- ]
110
-
111
- [tool.coverage.run]
112
- source = ["ssc_codegen"]
113
- omit = [
114
- "*/.venv/*",
115
- "*/scripts/*",
116
- "*/tests/*"
117
- ]
118
-
119
- [tool.pytest.ini_options]
120
- testpaths = ["tests"]
121
- markers = [
122
- "toolchain: Tests requiring external toolchains (Rust cargo, Go compiler, Node.js)",
123
- ]
124
-
1
+ [project]
2
+ name = "ssc_codegen"
3
+ version = "0.41.0"
4
+ description = "Python-dsl code converter to html parser for web scraping "
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "bs4>=0.0.2",
9
+ "colorama>=0.4.6 ; sys_platform == 'win32'",
10
+ "cssselect>=1.2.0",
11
+ "lxml>=5.3.0",
12
+ "soupsieve>=2.6",
13
+ "typer>0.26",
14
+ "typing_extensions;python_version<'3.11'",
15
+ "kdlquery>=1.3.0",
16
+ "httpx>=0.28.1",
17
+ ]
18
+
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Environment :: Console",
22
+ "Intended Audience :: Developers",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Topic :: Software Development :: Code Generators",
25
+ "Topic :: Text Processing :: Markup :: HTML",
26
+ "Topic :: Utilities",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ ]
32
+
33
+ [project.urls]
34
+ Documentation = "https://github.com/vypivshiy/selector_schema_codegen#readme"
35
+ Issues = "https://github.com/vypivshiy/selector_schema_codegen/issues"
36
+ Source = "https://github.com/vypivshiy/selector_schema_codegen"
37
+ Examples = "https://github.com/vypivshiy/selector_schema_codegen/examples"
38
+
39
+
40
+ [project.scripts]
41
+ ssc-gen = 'ssc_codegen.main:main'
42
+
43
+ [build-system]
44
+ requires = ["hatchling"]
45
+ build-backend = "hatchling.build"
46
+
47
+ [tool.hatch.build.targets.sdist]
48
+ include = [
49
+ "ssc_codegen/**",
50
+ ]
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["ssc_codegen"]
54
+
55
+ [dependency-groups]
56
+ dev = [
57
+ "aiohttp>=3.9,<3.12", # aioresponses 0.7.x breaks on 3.12+ (stream_writer)
58
+ "aioresponses>=0.7",
59
+ "bs4>=0.0.2",
60
+ "coverage>=7.6.12",
61
+ "httpx>=0.28.1",
62
+ "hypothesis>=6.151.9",
63
+ "lxml>=5.3.0",
64
+ "mypy>=1.14.1",
65
+ "parsel>=1.9.1",
66
+ "pytest>=8.3.4",
67
+ "pytest-xdist>=3.6.0",
68
+ "respx>=0.23.1",
69
+ "responses>=0.25",
70
+ "ruff>=0.9.3",
71
+ "selectolax>=0.3.27",
72
+ ]
73
+ docs = [
74
+ "mkdocs>=1.6.0",
75
+ "mkdocs-material>=9.5.0",
76
+ "mkdocstrings[python]>=0.27.0",
77
+ "mkdocs-click>=0.8.1",
78
+ ]
79
+
80
+ [tool.ruff]
81
+ target-version = "py310"
82
+ line-length = 80
83
+ exclude = ["__init__.py"]
84
+
85
+ [tool.mypy]
86
+ python_version = "3.10"
87
+ show_error_codes = true
88
+ [[tool.mypy.overrides]]
89
+ module="tests.*"
90
+ disallow_untyped_defs = false
91
+
92
+
93
+ [[tool.mypy.overrides]]
94
+ module = ["colorama", "lxml", "lxml.*"]
95
+ ignore_missing_imports = true
96
+
97
+ [tool.coverage.report]
98
+ exclude_also = [
99
+ 'def __repr__',
100
+ 'if self.debug:',
101
+ 'if settings.DEBUG',
102
+ 'raise AssertionError',
103
+ 'raise NotImplementedError',
104
+ 'if 0:',
105
+ 'if __name__ == .__main__.:',
106
+ 'if TYPE_CHECKING:',
107
+ 'class .*\bProtocol\):',
108
+ '@(abc\.)?abstractmethod',
109
+ ]
110
+
111
+ [tool.coverage.run]
112
+ source = ["ssc_codegen"]
113
+ omit = [
114
+ "*/.venv/*",
115
+ "*/scripts/*",
116
+ "*/tests/*"
117
+ ]
118
+
119
+ [tool.pytest.ini_options]
120
+ testpaths = ["tests"]
121
+ markers = [
122
+ "toolchain: Tests requiring external toolchains (Rust cargo, Go compiler, Node.js)",
123
+ ]
124
+
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import inspect
6
+
5
7
 
6
8
  class ModuleBuilder:
7
9
  """Language-agnostic accumulator for imports and helper definitions.
@@ -50,12 +52,17 @@ class ModuleBuilder:
50
52
  def require_import(self, line: str) -> None:
51
53
  """Register a top-level import line.
52
54
 
53
- Deduplicated and order-preserving. The line string is stored as-is.
55
+ Deduplicated and order-preserving. Multi-line blocks are normalized
56
+ with `inspect.cleandoc` to remove uniform indentation.
54
57
 
55
58
  Args:
56
59
  line: The verbatim import statement line for the target language.
57
60
  """
58
- self._imports.setdefault(line, None)
61
+ if "\n" in line:
62
+ cleaned = inspect.cleandoc(line)
63
+ else:
64
+ cleaned = line
65
+ self._imports.setdefault(cleaned, None)
59
66
 
60
67
  def require_std(
61
68
  self,
@@ -6,7 +6,6 @@ from collections.abc import Callable
6
6
  from typing import Any
7
7
 
8
8
  import ssc_codegen.ast as a
9
-
10
9
  from ssc_codegen.targets.python.http_libs.base import HttpLibStrategy
11
10
  from ssc_codegen.targets.python.http_libs.httpx import HttpxStrategy
12
11
  from ssc_codegen.traversal.utils import module_has_rest
@@ -439,7 +438,7 @@ def runtime_module_content(
439
438
  lines.extend(_BASE_UTILITY_LINES)
440
439
  lines.append("")
441
440
  if has_rest:
442
- lines.extend(strategy.rest_runtime_lines())
441
+ lines.extend(strategy.rest_runtime_lines(http_io="both"))
443
442
  if extension_defs:
444
443
  for _imports, code in extension_defs.values():
445
444
  lines.append("")
@@ -485,10 +484,17 @@ def register_runtime_file(
485
484
 
486
485
  @converter.file(f"{runtime_name}.py")
487
486
  def _runtime_provider(module_ast: a.Module, meta):
487
+ strat = http_strategy
488
+ if (
489
+ meta
490
+ and meta.get("http_client")
491
+ and hasattr(converter, "http_strategy_for")
492
+ ):
493
+ strat = converter.http_strategy_for(meta.get("http_client"))
488
494
  return _apply_fallback(
489
495
  runtime_module_content(
490
496
  module_ast,
491
- http_strategy=http_strategy,
497
+ http_strategy=strat,
492
498
  extension_defs=_extension_runtime_defs([module_ast]),
493
499
  )
494
500
  )
@@ -152,6 +152,8 @@ def _run_generate(
152
152
  skip_lint: bool = False,
153
153
  verbose: bool = False,
154
154
  fmt: FmtType = FmtType.TEXT,
155
+ bs4_parser: Optional[str] = None,
156
+ http_io: Optional[str] = None,
155
157
  ) -> None:
156
158
  """Execute the shared compilation pipeline for all code generator subcommands.
157
159
 
@@ -216,6 +218,10 @@ def _run_generate(
216
218
  meta: dict = {"package": package or default_package}
217
219
  if http_client:
218
220
  meta["http_client"] = http_client
221
+ if bs4_parser:
222
+ meta["bs4_parser"] = bs4_parser
223
+ if http_io:
224
+ meta["http_io"] = http_io
219
225
 
220
226
  if separate_runtime:
221
227
  from ssc_codegen.generation.runtime import register_runtime_file
@@ -385,6 +391,16 @@ def generate_python(
385
391
  help="HTML parsing library. Default: bs4.",
386
392
  ),
387
393
  ] = None,
394
+ bs4_parser: Annotated[
395
+ Optional[str],
396
+ typer.Option(
397
+ "--bs4-parser",
398
+ help=(
399
+ "Underlying HTML parser engine for bs4: "
400
+ "lxml (default) | html.parser | html5lib."
401
+ ),
402
+ ),
403
+ ] = None,
388
404
  verbose: Annotated[
389
405
  bool,
390
406
  typer.Option(
@@ -413,10 +429,17 @@ def generate_python(
413
429
  "--http-client",
414
430
  help=(
415
431
  "HTTP client for @request codegen: "
416
- "httpx (default) | aiohttp | requests."
432
+ "httpx (default) | httpx2 | aiohttp | requests."
417
433
  ),
418
434
  ),
419
435
  ] = None,
436
+ http_io: Annotated[
437
+ Optional[str],
438
+ typer.Option(
439
+ "--http-io",
440
+ help="HTTP I/O mode for generated methods: both (default) | sync | async.",
441
+ ),
442
+ ] = None,
420
443
  separate_runtime: Annotated[
421
444
  bool,
422
445
  typer.Option(
@@ -452,6 +475,8 @@ def generate_python(
452
475
  lib=lib.value if lib else None,
453
476
  http_client=http_client,
454
477
  separate_runtime=separate_runtime,
478
+ bs4_parser=bs4_parser,
479
+ http_io=http_io,
455
480
  )
456
481
  )
457
482
  except ResolutionError as exc:
@@ -474,6 +499,8 @@ def generate_python(
474
499
  skip_lint=skip_lint,
475
500
  verbose=verbose,
476
501
  fmt=fmt,
502
+ bs4_parser=bs4_parser,
503
+ http_io=http_io,
477
504
  )
478
505
 
479
506
 
@@ -886,6 +913,16 @@ def run(
886
913
  help="HTML parsing library. Default: bs4.",
887
914
  ),
888
915
  ] = None,
916
+ bs4_parser: Annotated[
917
+ Optional[str],
918
+ typer.Option(
919
+ "--bs4-parser",
920
+ help=(
921
+ "Underlying HTML parser engine for bs4: "
922
+ "lxml (default) | html.parser | html5lib."
923
+ ),
924
+ ),
925
+ ] = None,
889
926
  input_file: Annotated[
890
927
  Path | None,
891
928
  typer.Option(
@@ -984,6 +1021,7 @@ def run(
984
1021
  TargetSpec(
985
1022
  lang="python",
986
1023
  lib=lib.value if lib else None,
1024
+ bs4_parser=bs4_parser,
987
1025
  )
988
1026
  )
989
1027
  except ResolutionError as exc:
@@ -29,8 +29,8 @@ from ssc_codegen.ast.selectors import (
29
29
  XpathSelect,
30
30
  XpathSelectAll,
31
31
  )
32
- from ssc_codegen.traversal.context import WalkContext as ConverterContext
33
32
  from ssc_codegen.generation.builder import ModuleBuilder
33
+ from ssc_codegen.traversal.context import WalkContext as ConverterContext
34
34
 
35
35
 
36
36
  class DomSpelling(ABC):
@@ -54,6 +54,8 @@ class DomSpelling(ABC):
54
54
  document_type: Type annotation string for a single parsed HTML element/document.
55
55
  document_array_type: Type annotation string for a collection of elements.
56
56
  init_arg_type: Type annotation string accepted by `__init__` (e.g. `Union[str, ...]`).
57
+ init_extra_params: Optional keyword parameter signature fragment for `__init__`.
58
+ fn_extra_params: Optional keyword parameter signature fragment for function definitions.
57
59
  init_from_str_expr: Python expression parsing a string `document` into a DOM tree.
58
60
  extra_utilities: Module-level helper constants or definitions.
59
61
  supports_xpath: `True` if this DOM library supports XPath expressions natively.
@@ -78,6 +80,8 @@ class DomSpelling(ABC):
78
80
  document_type: str = "Any"
79
81
  document_array_type: str = "List[Any]"
80
82
  init_arg_type: str = "Any"
83
+ init_extra_params: str = ""
84
+ fn_extra_params: str = ""
81
85
  init_from_str_expr: str = "document"
82
86
  extra_utilities: tuple[str, ...] = ()
83
87
  supports_xpath: bool = False
@@ -27,8 +27,9 @@ from ssc_codegen.ast.selectors import (
27
27
  XpathSelect,
28
28
  XpathSelectAll,
29
29
  )
30
- from ssc_codegen.traversal.context import WalkContext as ConverterContext
30
+ from ssc_codegen.generation.builder import ModuleBuilder
31
31
  from ssc_codegen.targets.python.html_libs.base import DomSpelling
32
+ from ssc_codegen.traversal.context import WalkContext as ConverterContext
32
33
 
33
34
 
34
35
  class Bs4DomSpelling(DomSpelling):
@@ -38,24 +39,71 @@ class Bs4DomSpelling(DomSpelling):
38
39
  (such as ``select_one``, ``select``, ``decompose``, ``get_text``, and tag attributes).
39
40
 
40
41
  Attributes:
41
- parser_imports: Imports ``from bs4 import BeautifulSoup, ResultSet, Tag``.
42
+ parser_imports: Imports ``from bs4 import BeautifulSoup, ResultSet, Tag`` and ``Literal``.
42
43
  document_type: Type annotation ``Union[Tag, BeautifulSoup]``.
43
44
  document_array_type: Type annotation ``ResultSet[Tag]``.
44
45
  init_arg_type: Accepted input type ``Union[str, BeautifulSoup, Tag]``.
45
- init_from_str_expr: Expression ``BeautifulSoup(document, features=BS4_FEATURES)``.
46
- extra_utilities: Defines ``BS4_FEATURES = 'lxml'``.
46
+ init_extra_params: Keyword argument signature with default ``features: Literal[...] = BS4_FEATURES``.
47
+ fn_extra_params: Function keyword argument signature with default ``features: Literal[...] = BS4_FEATURES``.
48
+ init_from_str_expr: Expression ``BeautifulSoup(document, features=features)``.
49
+ extra_utilities: Defines ``BS4_FEATURES: Literal["html.parser", "lxml", "html5lib"] = ...``.
47
50
  supports_xpath: `False` (BeautifulSoup4 does not support XPath natively).
48
51
  """
49
52
 
50
53
  # === DATA ===
51
- parser_imports = ("from bs4 import BeautifulSoup, ResultSet, Tag",)
54
+ parser_imports = (
55
+ "from bs4 import BeautifulSoup, ResultSet, Tag",
56
+ "from typing import Literal",
57
+ )
52
58
  document_type = "Union[Tag, BeautifulSoup]"
53
59
  document_array_type = "ResultSet[Tag]"
54
60
  init_arg_type = "Union[str, BeautifulSoup, Tag]"
55
- init_from_str_expr = "BeautifulSoup(document, features=BS4_FEATURES)"
56
- extra_utilities = ("BS4_FEATURES = 'lxml'", "")
61
+ init_extra_params = ', *, features: Literal["html.parser", "lxml", "html5lib"] = BS4_FEATURES'
62
+ fn_extra_params = ', *, features: Literal["html.parser", "lxml", "html5lib"] = BS4_FEATURES'
63
+ init_from_str_expr = "BeautifulSoup(document, features=features)"
64
+ extra_utilities: tuple[str, ...] = (
65
+ 'BS4_FEATURES: Literal["html.parser", "lxml", "html5lib"] = \'lxml\'',
66
+ "",
67
+ )
57
68
  supports_xpath = False
58
69
 
70
+ def __init__(self, builder: ModuleBuilder, parser: str = "lxml") -> None:
71
+ """Initialize the BeautifulSoup4 DOM spelling instance.
72
+
73
+ Args:
74
+ builder: Module builder used for registering imports and runtime helpers.
75
+ parser: Underlying HTML parser feature engine ("lxml", "html.parser",
76
+ or "html5lib"). Defaults to "lxml".
77
+ """
78
+ super().__init__(builder)
79
+ self._parser = parser
80
+ self.extra_utilities = (
81
+ f'BS4_FEATURES: Literal["html.parser", "lxml", "html5lib"] = {parser!r}',
82
+ "",
83
+ )
84
+
85
+ @property
86
+ def parser(self) -> str:
87
+ """Get the underlying HTML parser feature engine.
88
+
89
+ Returns:
90
+ Configured HTML parser engine name ("lxml", "html.parser", or "html5lib").
91
+ """
92
+ return self._parser
93
+
94
+ @parser.setter
95
+ def parser(self, value: str) -> None:
96
+ """Set the underlying HTML parser feature engine and update module constants.
97
+
98
+ Args:
99
+ value: HTML parser engine name ("lxml", "html.parser", or "html5lib").
100
+ """
101
+ self._parser = value
102
+ self.extra_utilities = (
103
+ f'BS4_FEATURES: Literal["html.parser", "lxml", "html5lib"] = {value!r}',
104
+ "",
105
+ )
106
+
59
107
  # === EXPRESSIONS ===
60
108
 
61
109
  def css_select(self, ctx: ConverterContext, node: CssSelect) -> list[str]:
@@ -167,8 +215,10 @@ class Bs4DomSpelling(DomSpelling):
167
215
  if node.accept_type_info.is_array:
168
216
  return [f"{ctx.indent}{ctx.nxt} = len({ctx.prv}) > 0"]
169
217
  return [
170
- f"{ctx.indent}{ctx.nxt} = not ({ctx.prv} is None or {ctx.prv} == '' "
171
- f"or (type({ctx.prv}) is int and {ctx.prv} == 0))"
218
+ (
219
+ f"{ctx.indent}{ctx.nxt} = not ({ctx.prv} is None or {ctx.prv} == '' "
220
+ f"or (type({ctx.prv}) is int and {ctx.prv} == 0))"
221
+ )
172
222
  ]
173
223
 
174
224
  # === PREDICATES ===