Skip to content

Commit 4234fd6

Browse files
authored
chore: move parse_data_url to common package (#4982)
1 parent eed3f86 commit 4234fd6

5 files changed

Lines changed: 131 additions & 23 deletions

File tree

src/llama_stack/providers/inline/tool_runtime/rag/memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from pydantic import TypeAdapter
1616

1717
from llama_stack.log import get_logger
18+
from llama_stack.providers.utils.common.data_url import parse_data_url
1819
from llama_stack.providers.utils.inference.prompt_adapter import interleaved_content_as_str
19-
from llama_stack.providers.utils.memory.vector_store import parse_data_url
2020
from llama_stack_api import (
2121
URL,
2222
Files,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the terms described in the LICENSE file in
5+
# the root directory of this source tree.
6+
7+
import re
8+
9+
10+
def parse_data_url(data_url: str):
11+
data_url_pattern = re.compile(
12+
r"^"
13+
r"data:"
14+
r"(?P<mimetype>[\w/\-+.]+)"
15+
r"(?P<charset>;charset=(?P<encoding>[\w-]+))?"
16+
r"(?P<base64>;base64)?"
17+
r",(?P<data>.*)"
18+
r"$",
19+
re.DOTALL,
20+
)
21+
match = data_url_pattern.match(data_url)
22+
if not match:
23+
raise ValueError("Invalid Data URL format")
24+
25+
parts = match.groupdict()
26+
parts["is_base64"] = bool(parts["base64"])
27+
return parts

src/llama_stack/providers/utils/datasetio/url_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import io
1010
from urllib.parse import unquote
1111

12-
from llama_stack.providers.utils.memory.vector_store import parse_data_url
12+
from llama_stack.providers.utils.common.data_url import parse_data_url
1313

1414

1515
async def get_dataframe_from_uri(uri: str):

src/llama_stack/providers/utils/memory/vector_store.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# This source code is licensed under the terms described in the LICENSE file in
55
# the root directory of this source tree.
66
import io
7-
import re
87
import time
98
from abc import ABC, abstractmethod
109
from collections.abc import Sequence
@@ -58,26 +57,6 @@ def parse_pdf(data: bytes) -> str:
5857
return "\n".join([page.extract_text() for page in pdf_reader.pages])
5958

6059

61-
def parse_data_url(data_url: str):
62-
data_url_pattern = re.compile(
63-
r"^"
64-
r"data:"
65-
r"(?P<mimetype>[\w/\-+.]+)"
66-
r"(?P<charset>;charset=(?P<encoding>[\w-]+))?"
67-
r"(?P<base64>;base64)?"
68-
r",(?P<data>.*)"
69-
r"$",
70-
re.DOTALL,
71-
)
72-
match = data_url_pattern.match(data_url)
73-
if not match:
74-
raise ValueError("Invalid Data URL format")
75-
76-
parts = match.groupdict()
77-
parts["is_base64"] = bool(parts["base64"])
78-
return parts
79-
80-
8160
def content_from_data_and_mime_type(data: bytes | str, mime_type: str | None, encoding: str | None = None) -> str:
8261
if isinstance(data, str):
8362
return data
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the terms described in the LICENSE file in
5+
# the root directory of this source tree.
6+
7+
"""
8+
Tests for parse_data_url in llama_stack.providers.utils.common.data_url.
9+
10+
Categories:
11+
- Valid URLs: plain data, base64, charset, combined modifiers, complex mime types, multiline data
12+
- Invalid URLs: wrong scheme, missing comma, empty string, missing mime type
13+
"""
14+
15+
import pytest
16+
17+
from llama_stack.providers.utils.common.data_url import parse_data_url
18+
19+
20+
class TestParseDataUrlValid:
21+
def test_plain_text(self):
22+
parts = parse_data_url("data:text/plain,hello world")
23+
assert parts["mimetype"] == "text/plain"
24+
assert parts["data"] == "hello world"
25+
assert parts["is_base64"] is False
26+
assert parts["encoding"] is None
27+
28+
def test_base64_flag(self):
29+
parts = parse_data_url("data:text/plain;base64,aGVsbG8=")
30+
assert parts["mimetype"] == "text/plain"
31+
assert parts["data"] == "aGVsbG8="
32+
assert parts["is_base64"] is True
33+
assert parts["base64"] == ";base64"
34+
35+
def test_charset_only(self):
36+
parts = parse_data_url("data:text/plain;charset=utf-8,hello")
37+
assert parts["mimetype"] == "text/plain"
38+
assert parts["encoding"] == "utf-8"
39+
assert parts["charset"] == ";charset=utf-8"
40+
assert parts["is_base64"] is False
41+
assert parts["data"] == "hello"
42+
43+
def test_charset_and_base64(self):
44+
parts = parse_data_url("data:text/plain;charset=utf-8;base64,aGVsbG8=")
45+
assert parts["mimetype"] == "text/plain"
46+
assert parts["encoding"] == "utf-8"
47+
assert parts["is_base64"] is True
48+
assert parts["data"] == "aGVsbG8="
49+
50+
def test_image_mime_type(self):
51+
parts = parse_data_url("data:image/png;base64,abc123==")
52+
assert parts["mimetype"] == "image/png"
53+
assert parts["is_base64"] is True
54+
assert parts["data"] == "abc123=="
55+
56+
def test_mime_type_with_plus(self):
57+
parts = parse_data_url("data:application/json+xml,{}")
58+
assert parts["mimetype"] == "application/json+xml"
59+
assert parts["data"] == "{}"
60+
assert parts["is_base64"] is False
61+
62+
def test_empty_data_portion(self):
63+
parts = parse_data_url("data:text/plain,")
64+
assert parts["mimetype"] == "text/plain"
65+
assert parts["data"] == ""
66+
assert parts["is_base64"] is False
67+
68+
def test_multiline_data(self):
69+
# re.DOTALL allows newlines inside the data portion
70+
parts = parse_data_url("data:text/plain,line1\nline2")
71+
assert parts["data"] == "line1\nline2"
72+
73+
def test_hyphenated_charset(self):
74+
parts = parse_data_url("data:text/html;charset=ISO-8859-1,<b>hi</b>")
75+
assert parts["encoding"] == "ISO-8859-1"
76+
77+
def test_mime_type_with_dot(self):
78+
parts = parse_data_url("data:application/vnd.ms-excel,data")
79+
assert parts["mimetype"] == "application/vnd.ms-excel"
80+
81+
82+
class TestParseDataUrlInvalid:
83+
def test_http_url_raises(self):
84+
with pytest.raises(ValueError, match="Invalid Data URL format"):
85+
parse_data_url("http://example.com/image.png")
86+
87+
def test_missing_comma_raises(self):
88+
with pytest.raises(ValueError, match="Invalid Data URL format"):
89+
parse_data_url("data:text/plain")
90+
91+
def test_empty_string_raises(self):
92+
with pytest.raises(ValueError, match="Invalid Data URL format"):
93+
parse_data_url("")
94+
95+
def test_missing_mime_type_raises(self):
96+
# mimetype pattern requires at least one character
97+
with pytest.raises(ValueError, match="Invalid Data URL format"):
98+
parse_data_url("data:,hello")
99+
100+
def test_plain_string_raises(self):
101+
with pytest.raises(ValueError, match="Invalid Data URL format"):
102+
parse_data_url("not a data url at all")

0 commit comments

Comments
 (0)