Skip to content

Commit 100b947

Browse files
committed
Address review comments
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 3a58ba5 commit 100b947

File tree

2 files changed

+51
-52
lines changed

2 files changed

+51
-52
lines changed

src/packageurl/__init__.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,32 @@ def normalize_namespace(
124124
return "/".join(segments_quoted) or None
125125

126126

127+
def normalize_mlflow_name(
128+
name_str: str,
129+
qualifiers: Union[str, bytes, dict[str, str], None],
130+
) -> Optional[str]:
131+
"""MLflow purl names are case-sensitive for Azure ML, it is case sensitive and must be kept as-is in the package URL
132+
For Databricks, it is case insensitive and must be lowercased in the package URL"""
133+
if isinstance(qualifiers, dict):
134+
repo_url = qualifiers.get("repository_url")
135+
if repo_url and "azureml" in repo_url.lower():
136+
return name_str
137+
if repo_url and "databricks" in repo_url.lower():
138+
return name_str.lower()
139+
if isinstance(qualifiers, str):
140+
if "azureml" in qualifiers.lower():
141+
return name_str
142+
if "databricks" in qualifiers.lower():
143+
return name_str.lower()
144+
return name_str
145+
146+
127147
def normalize_name(
128148
name: AnyStr | None,
129149
qualifiers: Union[Union[str, bytes], dict[str, str], None],
130150
ptype: str | None,
131151
encode: bool | None = True,
132-
) -> str | None:
152+
) -> Optional[str]:
133153
if not name:
134154
return None
135155

@@ -138,19 +158,7 @@ def normalize_name(
138158
name_str = quoter(name_str)
139159
name_str = name_str.strip().strip("/")
140160
if ptype and ptype in ("mlflow"):
141-
# MLflow purl names are case-sensitive for Azure ML, it is case sensitive and must be kept as-is in the package URL
142-
# For Databricks, it is case insensitive and must be lowercased in the package URL
143-
if isinstance(qualifiers, dict):
144-
repo_url = qualifiers.get("repository_url")
145-
if repo_url and "azureml" in repo_url.lower():
146-
return name_str
147-
if repo_url and "databricks" in repo_url.lower():
148-
return name_str.lower()
149-
if isinstance(qualifiers, str):
150-
if "azureml" in qualifiers.lower():
151-
return name_str
152-
if "databricks" in qualifiers.lower():
153-
return name_str.lower()
161+
return normalize_mlflow_name(name_str, qualifiers)
154162
if ptype in ("bitbucket", "github", "pypi", "gitlab", "composer"):
155163
name_str = name_str.lower()
156164
if ptype == "pypi":
@@ -486,14 +494,12 @@ def from_string(cls, purl: str) -> Self:
486494
if not type_ or not sep:
487495
raise ValueError(f"purl is missing the required type component: {purl!r}.")
488496

489-
if not all(c in string.ascii_letters + string.digits + "-._" for c in type_):
497+
valid_chars = string.ascii_letters + string.digits + ".-_"
498+
if not all(c in valid_chars for c in type_):
490499
raise ValueError(
491500
f"purl type must be composed only of ASCII letters and numbers, period, dash and underscore: {type_!r}."
492501
)
493502

494-
if ":" in type_:
495-
raise ValueError(f"purl type cannot contain a colon: {type_!r}.")
496-
497503
if type_[0] in string.digits:
498504
raise ValueError(f"purl type cannot start with a number: {type_!r}.")
499505

tests/test_purl_spec.py

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,42 @@
3333
root_dir = os.path.abspath(os.path.join(current_dir, ".."))
3434
spec_file_path = os.path.join(root_dir, "spec", "tests", "spec", "specification-test.json")
3535

36-
valid_purl_types_file = os.path.join(root_dir, "spec", "purl-types-index.json")
37-
38-
3936
with open(spec_file_path, "r", encoding="utf-8") as f:
4037
test_cases = json.load(f)
4138

42-
with open(valid_purl_types_file, "r", encoding="utf-8") as f:
43-
valid_purl_types = json.load(f)
44-
4539
tests = test_cases["tests"]
4640

4741
parse_tests = [t for t in tests if t["test_type"] == "parse"]
4842
build_tests = [t for t in tests if t["test_type"] == "build"]
4943

5044

45+
def load_spec_files(spec_dir):
46+
"""
47+
Load all JSON files from the given directory into a dictionary.
48+
Key = filename, Value = parsed JSON content
49+
"""
50+
spec_data = {}
51+
for filename in os.listdir(spec_dir):
52+
if filename.endswith("-test.json"):
53+
filepath = os.path.join(spec_dir, filename)
54+
with open(filepath, "r", encoding="utf-8") as f:
55+
try:
56+
data = json.load(f)
57+
spec_data[filename] = data["tests"]
58+
except json.JSONDecodeError as e:
59+
print(f"Error parsing {filename}: {e}")
60+
return spec_data
61+
62+
63+
SPEC_DIR = os.path.join(os.path.dirname(__file__), "..", "spec", "tests", "types")
64+
spec_dict = load_spec_files(SPEC_DIR)
65+
66+
flattened_cases = []
67+
for filename, cases in spec_dict.items():
68+
for case in cases:
69+
flattened_cases.append((filename, case["description"], case))
70+
71+
5172
@pytest.mark.parametrize(
5273
"description, input_str, expected_output, expected_failure",
5374
[
@@ -59,7 +80,6 @@ def test_parse(description, input_str, expected_output, expected_failure):
5980
if expected_failure:
6081
with pytest.raises(Exception):
6182
PackageURL.from_string(input_str)
62-
# assert None ==PackageURL.from_string(input_str)
6383
else:
6484
result = PackageURL.from_string(input_str)
6585
assert result.to_string() == expected_output
@@ -90,33 +110,6 @@ def test_build(description, input_dict, expected_output, expected_failure):
90110
assert purl.to_string() == expected_output
91111

92112

93-
def load_spec_files(spec_dir):
94-
"""
95-
Load all JSON files from the given directory into a dictionary.
96-
Key = filename, Value = parsed JSON content
97-
"""
98-
spec_data = {}
99-
for filename in os.listdir(spec_dir):
100-
if filename.endswith("-test.json"):
101-
filepath = os.path.join(spec_dir, filename)
102-
with open(filepath, "r", encoding="utf-8") as f:
103-
try:
104-
data = json.load(f)
105-
spec_data[filename] = data["tests"]
106-
except json.JSONDecodeError as e:
107-
print(f"Error parsing {filename}: {e}")
108-
return spec_data
109-
110-
111-
SPEC_DIR = os.path.join(os.path.dirname(__file__), "..", "spec", "tests", "types")
112-
spec_dict = load_spec_files(SPEC_DIR)
113-
114-
flattened_cases = []
115-
for filename, cases in spec_dict.items():
116-
for case in cases:
117-
flattened_cases.append((filename, case["description"], case))
118-
119-
120113
@pytest.mark.parametrize("filename,description,test_case", flattened_cases)
121114
def test_package_type_case(filename, description, test_case):
122115
test_type = test_case["test_type"]

0 commit comments

Comments
 (0)