Skip to content

Commit f1da67d

Browse files
committed
fix(python): UriSource — add urlopen timeout + HTTP scheme test coverage
1 parent e297ac1 commit f1da67d

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

server/python/src/metaobjects/loader/sources/uri_source.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@ def _infer_format(uri: str) -> MetaDataFormat:
2323
class UriSource(MetaDataSource):
2424
"""A URI-backed source. Lazily fetched on ``read()``."""
2525

26-
def __init__(self, uri: str, format: MetaDataFormat | None = None) -> None:
26+
def __init__(
27+
self,
28+
uri: str,
29+
format: MetaDataFormat | None = None,
30+
timeout: float = 30.0,
31+
) -> None:
2732
self._uri = uri
2833
self._format = format if format is not None else _infer_format(uri)
34+
self._timeout = timeout
2935

3036
@property
3137
def id(self) -> str:
@@ -41,7 +47,9 @@ def read(self) -> str:
4147
# urlparse splits the leading slashes off the path on file:// URIs.
4248
return Path(parsed.path).read_text(encoding="utf-8-sig")
4349
if parsed.scheme in ("http", "https"):
44-
with urlopen(self._uri) as resp: # noqa: S310 — caller-supplied URI
50+
# Schemes are explicitly allowlisted (file/http/https) above; arbitrary
51+
# URI handlers (ftp, etc.) reject with ValueError before urlopen is called.
52+
with urlopen(self._uri, timeout=self._timeout) as resp: # noqa: S310
4553
return resp.read().decode("utf-8")
4654
raise ValueError(
4755
f"UriSource: unsupported scheme '{parsed.scheme}' on {self._uri}"

server/python/tests/unit/test_sources.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
from __future__ import annotations
77

88
from pathlib import Path
9+
from unittest.mock import MagicMock, patch
10+
11+
import pytest
912

1013
from metaobjects.loader.sources import (
1114
DirectorySource,
@@ -108,3 +111,28 @@ def test_uri_source_format_inferred_from_path(tmp_path: Path) -> None:
108111
p.write_text("k: v", encoding="utf-8")
109112
src = UriSource(p.as_uri())
110113
assert src.format == MetaDataFormat.YAML
114+
115+
116+
def test_uri_source_http_scheme_decodes_utf8() -> None:
117+
fake_response = MagicMock()
118+
fake_response.read.return_value = b'{"metadata.root":{"package":"x","children":[]}}'
119+
fake_response.__enter__ = lambda self: self
120+
fake_response.__exit__ = lambda self, *a: None
121+
122+
with patch(
123+
"metaobjects.loader.sources.uri_source.urlopen",
124+
return_value=fake_response,
125+
) as mock_open:
126+
src = UriSource("https://example.com/meta.json")
127+
content = src.read()
128+
129+
assert content == '{"metadata.root":{"package":"x","children":[]}}'
130+
# Verify timeout was passed
131+
args, kwargs = mock_open.call_args
132+
assert kwargs.get("timeout") == 30.0
133+
134+
135+
def test_uri_source_rejects_unsupported_scheme() -> None:
136+
src = UriSource("ftp://example.com/foo.json", format=MetaDataFormat.JSON)
137+
with pytest.raises(ValueError, match="unsupported scheme"):
138+
src.read()

0 commit comments

Comments
 (0)