Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crawl4ai/async_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,10 @@ def _filter_untrusted_fields(type_name: str, params: dict) -> dict:
raise UntrustedConfigError(
"fetch_ssl_certificate cannot be combined with deep_crawl_strategy from an untrusted request"
)
cache_mode = out.get("cache_mode") if type_name == "CrawlerRunConfig" else None
if isinstance(cache_mode, str) and cache_mode.lower() not in {m.value for m in CacheMode}:
# CrawlerRunConfig raises ValueError on it, which would surface as a 500.
raise UntrustedConfigError(f"invalid cache_mode: {cache_mode!r}")
return out


Expand Down Expand Up @@ -1942,7 +1946,8 @@ def __init__(
self.fetch_ssl_certificate = fetch_ssl_certificate

# Caching Parameters
self.cache_mode = cache_mode
# Accept "enabled" etc. from CLI -c and YAML/JSON config files
self.cache_mode = CacheMode(cache_mode.lower()) if isinstance(cache_mode, str) else cache_mode
self.session_id = session_id
self.bypass_cache = bypass_cache
self.disable_cache = disable_cache
Expand Down
4 changes: 2 additions & 2 deletions crawl4ai/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ def cdp_cmd(user_data_dir: Optional[str], port: int, browser_type: str, headless
@click.option("--crawler", "-c", type=str, callback=parse_key_values, help="Crawler parameters as key1=value1,key2=value2")
@click.option("--output", "-o", type=click.Choice(["all", "json", "markdown", "md", "markdown-fit", "md-fit"]), default="all", help="json = extracted data only (needs -j or -e). Use 'all' (default) for the full crawl result.")
@click.option("--output-file", "-O", type=click.Path(), help="Output file path (default: stdout)")
@click.option("--bypass-cache", "-bc", is_flag=True, default=True, help="Bypass cache when crawling")
@click.option("--bypass-cache", "-bc", is_flag=True, default=False, help="Bypass cache when crawling (the default unless -c cache_mode=... is set)")
@click.option("--question", "-q", help="Ask a question about the crawled content")
@click.option("--verbose", "-v", is_flag=True)
@click.option("--profile", "-p", help="Use a specific browser profile (by name)")
Expand Down Expand Up @@ -1587,7 +1587,7 @@ def shrink_cmd(profile_name: str, level: str, dry_run: bool):
@click.option("--browser", "-b", type=str, callback=parse_key_values, help="Browser parameters as key1=value1,key2=value2")
@click.option("--crawler", "-c", type=str, callback=parse_key_values, help="Crawler parameters as key1=value1,key2=value2")
@click.option("--output", "-o", type=click.Choice(["all", "json", "markdown", "md", "markdown-fit", "md-fit"]), default="all", help="json = extracted data only (needs -j or -e). Use 'all' (default) for the full crawl result.")
@click.option("--bypass-cache", is_flag=True, default=True, help="Bypass cache when crawling")
@click.option("--bypass-cache", is_flag=True, default=False, help="Bypass cache when crawling (the default unless -c cache_mode=... is set)")
@click.option("--question", "-q", help="Ask a question about the crawled content")
@click.option("--verbose", "-v", is_flag=True)
@click.option("--profile", "-p", help="Use a specific browser profile (by name)")
Expand Down
6 changes: 5 additions & 1 deletion deploy/docker/tests/test_playground_config_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ def test_every_template_field_reaches_the_config_object(self):
obj = CONFIG_CLASSES[name].load(
{"type": name, "params": params}, provenance=Provenance.UNTRUSTED
)
applied = {key: getattr(obj, key) for key in params}
# Enum fields (cache_mode) come back as the enum; compare by value.
applied = {
key: getattr(getattr(obj, key), "value", getattr(obj, key))
for key in params
}
self.assertEqual(applied, params)

def test_stream_reaches_the_shape_should_use_stream_reads(self):
Expand Down
18 changes: 18 additions & 0 deletions tests/cli/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,5 +268,23 @@ def test_json_output_with_question_is_allowed(runner):
run.assert_called() # no early error


def _crawler_cfg(runner, *args):
with patch("crawl4ai.cli.anyio.run") as run:
runner.invoke(cli, ["crawl", "https://example.com", "-o", "md", *args])
return run.call_args.args[3]


def test_cache_bypassed_by_default(runner):
from crawl4ai import CacheMode
assert _crawler_cfg(runner).cache_mode == CacheMode.BYPASS


def test_crawler_param_cache_mode_is_honored(runner):
# #2365: -c cache_mode=enabled was overridden and kept as a plain string
from crawl4ai import CacheMode
assert _crawler_cfg(runner, "-c", "cache_mode=enabled").cache_mode == CacheMode.ENABLED
assert _crawler_cfg(runner, "-c", "cache_mode=enabled", "-bc").cache_mode == CacheMode.BYPASS


if __name__ == '__main__':
pytest.main(['-v', '-s', '--tb=native', __file__])
14 changes: 14 additions & 0 deletions tests/unit/test_config_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,17 @@ def test_url_pattern_star_led_glob_is_linear_and_unchanged():
start = time.time()
assert not f.apply("https://x.com/" + "a" * 50000)
assert time.time() - start < 0.5


def test_cache_mode_string_is_converted_and_invalid_one_refused():
from crawl4ai import CacheMode

cfg = CrawlerRunConfig.load(
{"type": "CrawlerRunConfig", "params": {"cache_mode": "Enabled"}}, provenance=UNTRUSTED
)
assert cfg.cache_mode == CacheMode.ENABLED
# A bad value must be a 400 (UntrustedConfigError), not a ValueError (500).
with pytest.raises(UntrustedConfigError, match="cache_mode"):
CrawlerRunConfig.load(
{"type": "CrawlerRunConfig", "params": {"cache_mode": "nope"}}, provenance=UNTRUSTED
)
Loading