webextrator-cli 2026.7.1.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.
- webextrator_cli-2026.7.1.0/.env.example +3 -0
- webextrator_cli-2026.7.1.0/CHANGELOG.md +11 -0
- webextrator_cli-2026.7.1.0/LICENSE +21 -0
- webextrator_cli-2026.7.1.0/PKG-INFO +97 -0
- webextrator_cli-2026.7.1.0/README.md +51 -0
- webextrator_cli-2026.7.1.0/pyproject.toml +117 -0
- webextrator_cli-2026.7.1.0/tests/__init__.py +1 -0
- webextrator_cli-2026.7.1.0/tests/conftest.py +55 -0
- webextrator_cli-2026.7.1.0/tests/test_commands.py +315 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/__init__.py +3 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/__main__.py +6 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/commands/__init__.py +1 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/commands/info.py +25 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/commands/task.py +101 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/commands/web.py +252 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/core/__init__.py +1 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/core/client.py +103 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/core/config.py +39 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/core/exceptions.py +37 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/core/output.py +114 -0
- webextrator_cli-2026.7.1.0/webextrator_cli/main.py +65 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2025-05-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Initial release of WebExtrator CLI
|
|
7
|
+
- `extract` command for extracting structured content from web pages (`/webextrator/extract`)
|
|
8
|
+
- `render` command for rendering web pages and returning HTML (`/webextrator/render`)
|
|
9
|
+
- `tasks retrieve` command to query a single render/extract task
|
|
10
|
+
- `tasks batch` command to query multiple render/extract tasks
|
|
11
|
+
- `config` command to show current configuration
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AceDataCloud
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: webextrator-cli
|
|
3
|
+
Version: 2026.7.1.0
|
|
4
|
+
Summary: CLI tool for WebExtrator Web Render & Extract via AceDataCloud
|
|
5
|
+
Project-URL: Homepage, https://github.com/AceDataCloud/Clis
|
|
6
|
+
Project-URL: Repository, https://github.com/AceDataCloud/Clis
|
|
7
|
+
Project-URL: Issues, https://github.com/AceDataCloud/Clis/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/AceDataCloud/Clis/blob/main/webextrator/CHANGELOG.md
|
|
9
|
+
Author-email: AceDataCloud <support@acedata.cloud>
|
|
10
|
+
Maintainer-email: AceDataCloud <support@acedata.cloud>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: acedata,cli,command-line,web-rendering,web-scraping,webextrator
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: click>=8.1.0
|
|
27
|
+
Requires-Dist: httpx>=0.27.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0.0
|
|
29
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
30
|
+
Requires-Dist: rich>=13.0.0
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: webextrator-cli[dev,release,test]; extra == 'all'
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
37
|
+
Provides-Extra: release
|
|
38
|
+
Requires-Dist: build>=1.2.0; extra == 'release'
|
|
39
|
+
Requires-Dist: twine>=6.1.0; extra == 'release'
|
|
40
|
+
Provides-Extra: test
|
|
41
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
|
|
42
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
|
|
43
|
+
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
44
|
+
Requires-Dist: respx>=0.21.0; extra == 'test'
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# WebExtrator CLI
|
|
48
|
+
|
|
49
|
+
A command-line tool for web rendering and extraction via [AceDataCloud](https://platform.acedata.cloud).
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install webextrator-cli
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
export ACEDATACLOUD_API_TOKEN=your_token
|
|
61
|
+
|
|
62
|
+
webextrator extract https://www.amazon.com/dp/B0C1234567
|
|
63
|
+
webextrator render https://example.com
|
|
64
|
+
webextrator tasks retrieve --id <task-id>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
| Command | Description |
|
|
70
|
+
|---------|-------------|
|
|
71
|
+
| `webextrator extract <url>` | Extract structured content from a web page |
|
|
72
|
+
| `webextrator render <url>` | Render a web page and return the HTML |
|
|
73
|
+
| `webextrator tasks retrieve` | Retrieve a single task by ID or trace ID |
|
|
74
|
+
| `webextrator tasks batch` | Retrieve multiple tasks at once |
|
|
75
|
+
| `webextrator config` | Show current configuration |
|
|
76
|
+
|
|
77
|
+
## Global Options
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
--token TEXT API token (or set ACEDATACLOUD_API_TOKEN env var)
|
|
81
|
+
--version Show version
|
|
82
|
+
--help Show help message
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
Set environment variables or use a `.env` file:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ACEDATACLOUD_API_TOKEN=your_token
|
|
91
|
+
ACEDATACLOUD_API_BASE_URL=https://api.acedata.cloud
|
|
92
|
+
WEBEXTRATOR_REQUEST_TIMEOUT=60
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# WebExtrator CLI
|
|
2
|
+
|
|
3
|
+
A command-line tool for web rendering and extraction via [AceDataCloud](https://platform.acedata.cloud).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install webextrator-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
export ACEDATACLOUD_API_TOKEN=your_token
|
|
15
|
+
|
|
16
|
+
webextrator extract https://www.amazon.com/dp/B0C1234567
|
|
17
|
+
webextrator render https://example.com
|
|
18
|
+
webextrator tasks retrieve --id <task-id>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
| Command | Description |
|
|
24
|
+
|---------|-------------|
|
|
25
|
+
| `webextrator extract <url>` | Extract structured content from a web page |
|
|
26
|
+
| `webextrator render <url>` | Render a web page and return the HTML |
|
|
27
|
+
| `webextrator tasks retrieve` | Retrieve a single task by ID or trace ID |
|
|
28
|
+
| `webextrator tasks batch` | Retrieve multiple tasks at once |
|
|
29
|
+
| `webextrator config` | Show current configuration |
|
|
30
|
+
|
|
31
|
+
## Global Options
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
--token TEXT API token (or set ACEDATACLOUD_API_TOKEN env var)
|
|
35
|
+
--version Show version
|
|
36
|
+
--help Show help message
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
Set environment variables or use a `.env` file:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
ACEDATACLOUD_API_TOKEN=your_token
|
|
45
|
+
ACEDATACLOUD_API_BASE_URL=https://api.acedata.cloud
|
|
46
|
+
WEBEXTRATOR_REQUEST_TIMEOUT=60
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "webextrator-cli"
|
|
3
|
+
version = "2026.7.1.0"
|
|
4
|
+
description = "CLI tool for WebExtrator Web Render & Extract via AceDataCloud"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "AceDataCloud", email = "support@acedata.cloud" }
|
|
10
|
+
]
|
|
11
|
+
maintainers = [
|
|
12
|
+
{ name = "AceDataCloud", email = "support@acedata.cloud" }
|
|
13
|
+
]
|
|
14
|
+
keywords = [
|
|
15
|
+
"cli",
|
|
16
|
+
"webextrator",
|
|
17
|
+
"web-scraping",
|
|
18
|
+
"web-rendering",
|
|
19
|
+
"acedata",
|
|
20
|
+
"command-line",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
32
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
33
|
+
"Environment :: Console",
|
|
34
|
+
]
|
|
35
|
+
dependencies = [
|
|
36
|
+
"click>=8.1.0",
|
|
37
|
+
"httpx>=0.27.0",
|
|
38
|
+
"python-dotenv>=1.0.0",
|
|
39
|
+
"rich>=13.0.0",
|
|
40
|
+
"pydantic>=2.0.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
dev = [
|
|
45
|
+
"ruff>=0.4.0",
|
|
46
|
+
"mypy>=1.10.0",
|
|
47
|
+
"pre-commit>=3.7.0",
|
|
48
|
+
]
|
|
49
|
+
test = [
|
|
50
|
+
"pytest>=8.0.0",
|
|
51
|
+
"pytest-asyncio>=0.23.0",
|
|
52
|
+
"pytest-cov>=5.0.0",
|
|
53
|
+
"respx>=0.21.0",
|
|
54
|
+
]
|
|
55
|
+
release = [
|
|
56
|
+
"build>=1.2.0",
|
|
57
|
+
"twine>=6.1.0",
|
|
58
|
+
]
|
|
59
|
+
all = [
|
|
60
|
+
"webextrator-cli[dev,test,release]",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[project.scripts]
|
|
64
|
+
webextrator-cli = "webextrator_cli.main:cli"
|
|
65
|
+
webextrator = "webextrator_cli.main:cli"
|
|
66
|
+
|
|
67
|
+
[project.urls]
|
|
68
|
+
Homepage = "https://github.com/AceDataCloud/Clis"
|
|
69
|
+
Repository = "https://github.com/AceDataCloud/Clis"
|
|
70
|
+
Issues = "https://github.com/AceDataCloud/Clis/issues"
|
|
71
|
+
Changelog = "https://github.com/AceDataCloud/Clis/blob/main/webextrator/CHANGELOG.md"
|
|
72
|
+
|
|
73
|
+
[build-system]
|
|
74
|
+
requires = ["hatchling>=1.21.0,<1.22.0"]
|
|
75
|
+
build-backend = "hatchling.build"
|
|
76
|
+
|
|
77
|
+
[tool.hatch.build.targets.wheel]
|
|
78
|
+
packages = ["webextrator_cli"]
|
|
79
|
+
|
|
80
|
+
[tool.hatch.build.targets.sdist]
|
|
81
|
+
include = [
|
|
82
|
+
"webextrator_cli/",
|
|
83
|
+
"tests/",
|
|
84
|
+
"README.md",
|
|
85
|
+
"LICENSE",
|
|
86
|
+
"CHANGELOG.md",
|
|
87
|
+
".env.example",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
# Mypy Configuration
|
|
91
|
+
[tool.mypy]
|
|
92
|
+
python_version = "3.10"
|
|
93
|
+
warn_return_any = true
|
|
94
|
+
warn_unused_configs = true
|
|
95
|
+
disallow_untyped_defs = true
|
|
96
|
+
check_untyped_defs = true
|
|
97
|
+
|
|
98
|
+
# Pytest Configuration
|
|
99
|
+
[tool.pytest.ini_options]
|
|
100
|
+
testpaths = ["tests"]
|
|
101
|
+
asyncio_mode = "auto"
|
|
102
|
+
markers = [
|
|
103
|
+
"integration: marks tests that require real API access",
|
|
104
|
+
"slow: marks slow tests",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
# Ruff Configuration
|
|
108
|
+
[tool.ruff]
|
|
109
|
+
line-length = 100
|
|
110
|
+
target-version = "py310"
|
|
111
|
+
|
|
112
|
+
[tool.ruff.lint]
|
|
113
|
+
select = ["E", "W", "F", "I", "B", "C4", "UP", "ARG", "SIM"]
|
|
114
|
+
ignore = ["E501"]
|
|
115
|
+
|
|
116
|
+
[tool.ruff.format]
|
|
117
|
+
quote-style = "double"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests init."""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Test configuration and fixtures for WebExtrator CLI."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.fixture
|
|
7
|
+
def mock_extract_response() -> dict:
|
|
8
|
+
return {
|
|
9
|
+
"success": True,
|
|
10
|
+
"task_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
11
|
+
"trace_id": "550e8400-e29b-41d4-a716-446655440001",
|
|
12
|
+
"started_at": "2025-05-02T10:30:00.123Z",
|
|
13
|
+
"finished_at": "2025-05-02T10:30:08.789Z",
|
|
14
|
+
"elapsed": 8.666,
|
|
15
|
+
"data": {
|
|
16
|
+
"kind": "extract",
|
|
17
|
+
"url": "https://www.amazon.com/dp/B0C1234567",
|
|
18
|
+
"contentType": "product",
|
|
19
|
+
"title": "Acme Widget",
|
|
20
|
+
"description": "A widget that does things.",
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def mock_render_response() -> dict:
|
|
27
|
+
return {
|
|
28
|
+
"success": True,
|
|
29
|
+
"task_id": "550e8400-e29b-41d4-a716-446655440002",
|
|
30
|
+
"trace_id": "550e8400-e29b-41d4-a716-446655440003",
|
|
31
|
+
"started_at": "2025-05-02T10:30:00.123Z",
|
|
32
|
+
"finished_at": "2025-05-02T10:30:05.456Z",
|
|
33
|
+
"elapsed": 5.333,
|
|
34
|
+
"data": {
|
|
35
|
+
"kind": "render",
|
|
36
|
+
"url": "https://example.com",
|
|
37
|
+
"finalUrl": "https://example.com/",
|
|
38
|
+
"title": "Example Domain",
|
|
39
|
+
"status": 200,
|
|
40
|
+
"html": "<!DOCTYPE html><html>...</html>",
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
def mock_task_response() -> dict:
|
|
47
|
+
return {
|
|
48
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
49
|
+
"task_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
50
|
+
"trace_id": "550e8400-e29b-41d4-a716-446655440001",
|
|
51
|
+
"type": "extract",
|
|
52
|
+
"started_at": "2025-05-02T10:30:00.123Z",
|
|
53
|
+
"finished_at": "2025-05-02T10:30:08.789Z",
|
|
54
|
+
"elapsed": 8.666,
|
|
55
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Tests for WebExtrator CLI commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import respx
|
|
7
|
+
from click.testing import CliRunner
|
|
8
|
+
from httpx import Response
|
|
9
|
+
|
|
10
|
+
from webextrator_cli.main import cli
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def runner() -> CliRunner:
|
|
15
|
+
return CliRunner()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestGlobalCommands:
|
|
19
|
+
"""Tests for global CLI options."""
|
|
20
|
+
|
|
21
|
+
def test_version(self, runner):
|
|
22
|
+
result = runner.invoke(cli, ["--version"])
|
|
23
|
+
assert result.exit_code == 0
|
|
24
|
+
assert "webextrator-cli" in result.output
|
|
25
|
+
|
|
26
|
+
def test_help(self, runner):
|
|
27
|
+
result = runner.invoke(cli, ["--help"])
|
|
28
|
+
assert result.exit_code == 0
|
|
29
|
+
assert "extract" in result.output
|
|
30
|
+
assert "render" in result.output
|
|
31
|
+
|
|
32
|
+
def test_help_extract(self, runner):
|
|
33
|
+
result = runner.invoke(cli, ["extract", "--help"])
|
|
34
|
+
assert result.exit_code == 0
|
|
35
|
+
assert "URL" in result.output
|
|
36
|
+
|
|
37
|
+
def test_help_render(self, runner):
|
|
38
|
+
result = runner.invoke(cli, ["render", "--help"])
|
|
39
|
+
assert result.exit_code == 0
|
|
40
|
+
assert "URL" in result.output
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestExtractCommand:
|
|
44
|
+
"""Tests for the extract command."""
|
|
45
|
+
|
|
46
|
+
@respx.mock
|
|
47
|
+
def test_extract_json(self, runner, mock_extract_response):
|
|
48
|
+
respx.post("https://api.acedata.cloud/webextrator/extract").mock(
|
|
49
|
+
return_value=Response(200, json=mock_extract_response)
|
|
50
|
+
)
|
|
51
|
+
result = runner.invoke(
|
|
52
|
+
cli,
|
|
53
|
+
[
|
|
54
|
+
"--token",
|
|
55
|
+
"test-token",
|
|
56
|
+
"extract",
|
|
57
|
+
"https://www.amazon.com/dp/B0C1234567",
|
|
58
|
+
"--json",
|
|
59
|
+
],
|
|
60
|
+
)
|
|
61
|
+
assert result.exit_code == 0
|
|
62
|
+
data = json.loads(result.output)
|
|
63
|
+
assert data["success"] is True
|
|
64
|
+
assert data["task_id"] == "550e8400-e29b-41d4-a716-446655440000"
|
|
65
|
+
|
|
66
|
+
@respx.mock
|
|
67
|
+
def test_extract_rich_output(self, runner, mock_extract_response):
|
|
68
|
+
respx.post("https://api.acedata.cloud/webextrator/extract").mock(
|
|
69
|
+
return_value=Response(200, json=mock_extract_response)
|
|
70
|
+
)
|
|
71
|
+
result = runner.invoke(
|
|
72
|
+
cli,
|
|
73
|
+
["--token", "test-token", "extract", "https://www.amazon.com/dp/B0C1234567"],
|
|
74
|
+
)
|
|
75
|
+
assert result.exit_code == 0
|
|
76
|
+
assert "550e8400-e29b-41d4-a716-446655440000" in result.output
|
|
77
|
+
|
|
78
|
+
@respx.mock
|
|
79
|
+
def test_extract_with_expected_type(self, runner, mock_extract_response):
|
|
80
|
+
respx.post("https://api.acedata.cloud/webextrator/extract").mock(
|
|
81
|
+
return_value=Response(200, json=mock_extract_response)
|
|
82
|
+
)
|
|
83
|
+
result = runner.invoke(
|
|
84
|
+
cli,
|
|
85
|
+
[
|
|
86
|
+
"--token",
|
|
87
|
+
"test-token",
|
|
88
|
+
"extract",
|
|
89
|
+
"https://example.com",
|
|
90
|
+
"--expected-type",
|
|
91
|
+
"product",
|
|
92
|
+
"--json",
|
|
93
|
+
],
|
|
94
|
+
)
|
|
95
|
+
assert result.exit_code == 0
|
|
96
|
+
|
|
97
|
+
@respx.mock
|
|
98
|
+
def test_extract_with_enable_llm(self, runner, mock_extract_response):
|
|
99
|
+
route = respx.post("https://api.acedata.cloud/webextrator/extract").mock(
|
|
100
|
+
return_value=Response(200, json=mock_extract_response)
|
|
101
|
+
)
|
|
102
|
+
result = runner.invoke(
|
|
103
|
+
cli,
|
|
104
|
+
[
|
|
105
|
+
"--token",
|
|
106
|
+
"test-token",
|
|
107
|
+
"extract",
|
|
108
|
+
"https://example.com",
|
|
109
|
+
"--enable-llm",
|
|
110
|
+
"--json",
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
assert result.exit_code == 0
|
|
114
|
+
sent = json.loads(route.calls[0].request.content)
|
|
115
|
+
assert sent["enable_llm"] is True
|
|
116
|
+
|
|
117
|
+
@respx.mock
|
|
118
|
+
def test_extract_with_block_resources_and_headers(self, runner, mock_extract_response):
|
|
119
|
+
route = respx.post("https://api.acedata.cloud/webextrator/extract").mock(
|
|
120
|
+
return_value=Response(200, json=mock_extract_response)
|
|
121
|
+
)
|
|
122
|
+
result = runner.invoke(
|
|
123
|
+
cli,
|
|
124
|
+
[
|
|
125
|
+
"--token",
|
|
126
|
+
"test-token",
|
|
127
|
+
"extract",
|
|
128
|
+
"https://example.com",
|
|
129
|
+
"--block-resources",
|
|
130
|
+
"--headers",
|
|
131
|
+
'{"Accept-Language":"en-US"}',
|
|
132
|
+
"--json",
|
|
133
|
+
],
|
|
134
|
+
)
|
|
135
|
+
assert result.exit_code == 0
|
|
136
|
+
sent = json.loads(route.calls[0].request.content)
|
|
137
|
+
assert sent["block_resources"] is True
|
|
138
|
+
assert sent["headers"] == {"Accept-Language": "en-US"}
|
|
139
|
+
|
|
140
|
+
def test_extract_no_token(self, runner):
|
|
141
|
+
result = runner.invoke(cli, ["--token", "", "extract", "https://example.com"])
|
|
142
|
+
assert result.exit_code != 0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TestRenderCommand:
|
|
146
|
+
"""Tests for the render command."""
|
|
147
|
+
|
|
148
|
+
@respx.mock
|
|
149
|
+
def test_render_json(self, runner, mock_render_response):
|
|
150
|
+
respx.post("https://api.acedata.cloud/webextrator/render").mock(
|
|
151
|
+
return_value=Response(200, json=mock_render_response)
|
|
152
|
+
)
|
|
153
|
+
result = runner.invoke(
|
|
154
|
+
cli,
|
|
155
|
+
["--token", "test-token", "render", "https://example.com", "--json"],
|
|
156
|
+
)
|
|
157
|
+
assert result.exit_code == 0
|
|
158
|
+
data = json.loads(result.output)
|
|
159
|
+
assert data["success"] is True
|
|
160
|
+
assert data["task_id"] == "550e8400-e29b-41d4-a716-446655440002"
|
|
161
|
+
|
|
162
|
+
@respx.mock
|
|
163
|
+
def test_render_rich_output(self, runner, mock_render_response):
|
|
164
|
+
respx.post("https://api.acedata.cloud/webextrator/render").mock(
|
|
165
|
+
return_value=Response(200, json=mock_render_response)
|
|
166
|
+
)
|
|
167
|
+
result = runner.invoke(cli, ["--token", "test-token", "render", "https://example.com"])
|
|
168
|
+
assert result.exit_code == 0
|
|
169
|
+
assert "550e8400-e29b-41d4-a716-446655440002" in result.output
|
|
170
|
+
|
|
171
|
+
@respx.mock
|
|
172
|
+
def test_render_with_wait_until(self, runner, mock_render_response):
|
|
173
|
+
respx.post("https://api.acedata.cloud/webextrator/render").mock(
|
|
174
|
+
return_value=Response(200, json=mock_render_response)
|
|
175
|
+
)
|
|
176
|
+
result = runner.invoke(
|
|
177
|
+
cli,
|
|
178
|
+
[
|
|
179
|
+
"--token",
|
|
180
|
+
"test-token",
|
|
181
|
+
"render",
|
|
182
|
+
"https://example.com",
|
|
183
|
+
"--wait-until",
|
|
184
|
+
"load",
|
|
185
|
+
"--json",
|
|
186
|
+
],
|
|
187
|
+
)
|
|
188
|
+
assert result.exit_code == 0
|
|
189
|
+
|
|
190
|
+
@respx.mock
|
|
191
|
+
def test_render_with_callback(self, runner, mock_render_response):
|
|
192
|
+
route = respx.post("https://api.acedata.cloud/webextrator/render").mock(
|
|
193
|
+
return_value=Response(200, json=mock_render_response)
|
|
194
|
+
)
|
|
195
|
+
result = runner.invoke(
|
|
196
|
+
cli,
|
|
197
|
+
[
|
|
198
|
+
"--token",
|
|
199
|
+
"test-token",
|
|
200
|
+
"render",
|
|
201
|
+
"https://example.com",
|
|
202
|
+
"--callback-url",
|
|
203
|
+
"https://my.server.com/webhook",
|
|
204
|
+
"--json",
|
|
205
|
+
],
|
|
206
|
+
)
|
|
207
|
+
assert result.exit_code == 0
|
|
208
|
+
sent = json.loads(route.calls[0].request.content)
|
|
209
|
+
assert sent["callback_url"] == "https://my.server.com/webhook"
|
|
210
|
+
|
|
211
|
+
@respx.mock
|
|
212
|
+
def test_render_with_block_resources_and_headers(self, runner, mock_render_response):
|
|
213
|
+
route = respx.post("https://api.acedata.cloud/webextrator/render").mock(
|
|
214
|
+
return_value=Response(200, json=mock_render_response)
|
|
215
|
+
)
|
|
216
|
+
result = runner.invoke(
|
|
217
|
+
cli,
|
|
218
|
+
[
|
|
219
|
+
"--token",
|
|
220
|
+
"test-token",
|
|
221
|
+
"render",
|
|
222
|
+
"https://example.com",
|
|
223
|
+
"--block-resources",
|
|
224
|
+
"--headers",
|
|
225
|
+
'{"X-Test":"1"}',
|
|
226
|
+
"--json",
|
|
227
|
+
],
|
|
228
|
+
)
|
|
229
|
+
assert result.exit_code == 0
|
|
230
|
+
sent = json.loads(route.calls[0].request.content)
|
|
231
|
+
assert sent["block_resources"] is True
|
|
232
|
+
assert sent["headers"] == {"X-Test": "1"}
|
|
233
|
+
|
|
234
|
+
def test_render_no_token(self, runner):
|
|
235
|
+
result = runner.invoke(cli, ["--token", "", "render", "https://example.com"])
|
|
236
|
+
assert result.exit_code != 0
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class TestTasksCommands:
|
|
240
|
+
"""Tests for tasks management commands."""
|
|
241
|
+
|
|
242
|
+
@respx.mock
|
|
243
|
+
def test_tasks_retrieve_json(self, runner, mock_task_response):
|
|
244
|
+
respx.post("https://api.acedata.cloud/webextrator/tasks").mock(
|
|
245
|
+
return_value=Response(200, json=mock_task_response)
|
|
246
|
+
)
|
|
247
|
+
result = runner.invoke(
|
|
248
|
+
cli,
|
|
249
|
+
[
|
|
250
|
+
"--token",
|
|
251
|
+
"test-token",
|
|
252
|
+
"tasks",
|
|
253
|
+
"retrieve",
|
|
254
|
+
"--id",
|
|
255
|
+
"550e8400-e29b-41d4-a716-446655440000",
|
|
256
|
+
"--json",
|
|
257
|
+
],
|
|
258
|
+
)
|
|
259
|
+
assert result.exit_code == 0
|
|
260
|
+
data = json.loads(result.output)
|
|
261
|
+
assert data["task_id"] == "550e8400-e29b-41d4-a716-446655440000"
|
|
262
|
+
|
|
263
|
+
@respx.mock
|
|
264
|
+
def test_tasks_retrieve_by_trace_id(self, runner, mock_task_response):
|
|
265
|
+
respx.post("https://api.acedata.cloud/webextrator/tasks").mock(
|
|
266
|
+
return_value=Response(200, json=mock_task_response)
|
|
267
|
+
)
|
|
268
|
+
result = runner.invoke(
|
|
269
|
+
cli,
|
|
270
|
+
[
|
|
271
|
+
"--token",
|
|
272
|
+
"test-token",
|
|
273
|
+
"tasks",
|
|
274
|
+
"retrieve",
|
|
275
|
+
"--trace-id",
|
|
276
|
+
"550e8400-e29b-41d4-a716-446655440001",
|
|
277
|
+
"--json",
|
|
278
|
+
],
|
|
279
|
+
)
|
|
280
|
+
assert result.exit_code == 0
|
|
281
|
+
|
|
282
|
+
def test_tasks_retrieve_requires_id_or_trace_id(self, runner):
|
|
283
|
+
result = runner.invoke(cli, ["--token", "test-token", "tasks", "retrieve"])
|
|
284
|
+
assert result.exit_code != 0
|
|
285
|
+
|
|
286
|
+
@respx.mock
|
|
287
|
+
def test_tasks_batch_json(self, runner, mock_task_response):
|
|
288
|
+
batch_response = {"items": [mock_task_response], "count": 1}
|
|
289
|
+
respx.post("https://api.acedata.cloud/webextrator/tasks").mock(
|
|
290
|
+
return_value=Response(200, json=batch_response)
|
|
291
|
+
)
|
|
292
|
+
result = runner.invoke(
|
|
293
|
+
cli,
|
|
294
|
+
[
|
|
295
|
+
"--token",
|
|
296
|
+
"test-token",
|
|
297
|
+
"tasks",
|
|
298
|
+
"batch",
|
|
299
|
+
"--ids",
|
|
300
|
+
"id1",
|
|
301
|
+
"--ids",
|
|
302
|
+
"id2",
|
|
303
|
+
"--json",
|
|
304
|
+
],
|
|
305
|
+
)
|
|
306
|
+
assert result.exit_code == 0
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class TestInfoCommands:
|
|
310
|
+
"""Tests for info and utility commands."""
|
|
311
|
+
|
|
312
|
+
def test_config(self, runner):
|
|
313
|
+
result = runner.invoke(cli, ["config"])
|
|
314
|
+
assert result.exit_code == 0
|
|
315
|
+
assert "api.acedata.cloud" in result.output
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""WebExtrator CLI commands package."""
|