Skip to content

Commit 0231c39

Browse files
🎨 Allow case-insensitive filename matching (#8522)
1 parent 9afa492 commit 0231c39

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

packages/models-library/src/models_library/api_schemas_storage/search_async_jobs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import BaseModel, ByteSize, ConfigDict
66
from pydantic.alias_generators import to_camel
77

8-
SEARCH_TASK_NAME: Final[str] = "files.search"
8+
SEARCH_TASK_NAME: Final[str] = "files_search"
99

1010

1111
class SearchResultItem(BaseModel):

services/storage/src/simcore_service_storage/simcore_s3_dsm.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@ async def _search_project_s3_files(
10011001
min_parts_for_valid_s3_object = 2
10021002

10031003
try:
1004+
name_pattern_lower = name_pattern.lower()
10041005
async for s3_objects in s3_client.list_objects_paginated(
10051006
bucket=self.simcore_bucket_name,
10061007
prefix=f"{proj_id}/",
@@ -1010,7 +1011,7 @@ async def _search_project_s3_files(
10101011
filename = Path(s3_obj.object_key).name
10111012

10121013
if not (
1013-
fnmatch.fnmatch(filename, name_pattern)
1014+
fnmatch.fnmatch(filename.lower(), name_pattern_lower)
10141015
and len(s3_obj.object_key.split("/"))
10151016
>= min_parts_for_valid_s3_object
10161017
):

services/storage/tests/unit/test_simcore_s3_dsm.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,77 @@ async def test_search_files(
315315
assert len(paginated_results) == len(test_files)
316316

317317

318+
@pytest.mark.parametrize(
319+
"location_id",
320+
[SimcoreS3DataManager.get_location_id()],
321+
ids=[SimcoreS3DataManager.get_location_name()],
322+
indirect=True,
323+
)
324+
async def test_search_files_case_insensitive(
325+
simcore_s3_dsm: SimcoreS3DataManager,
326+
upload_file: Callable[..., Awaitable[tuple[Path, SimcoreS3FileID]]],
327+
file_size: ByteSize,
328+
user_id: UserID,
329+
project_id: ProjectID,
330+
faker: Faker,
331+
):
332+
mixed_case_files = [
333+
("TestFile.TXT", "*.txt"),
334+
("MyDocument.PDF", "*.pdf"),
335+
("DataFile.CSV", "data*.csv"),
336+
("ConfigFile.JSON", "config*"),
337+
("BackupData.BAK", "*.bak"),
338+
("CamelCaseFile.txt", "camelcase*"),
339+
("XMLDataFile.xml", "*.XML"),
340+
("config.json", "CONFIG*"),
341+
]
342+
343+
for file_name, _ in mixed_case_files:
344+
checksum: SHA256Str = TypeAdapter(SHA256Str).validate_python(faker.sha256())
345+
await upload_file(file_size, file_name, sha256_checksum=checksum)
346+
347+
# Test case-insensitive extension matching
348+
case_insensitive_txt = await _search_files_by_pattern(
349+
simcore_s3_dsm, user_id, "*.txt", project_id
350+
)
351+
txt_file_names = {file.file_name for file in case_insensitive_txt}
352+
assert "TestFile.TXT" in txt_file_names
353+
assert "CamelCaseFile.txt" in txt_file_names
354+
355+
# Test case-insensitive prefix matching
356+
case_insensitive_data = await _search_files_by_pattern(
357+
simcore_s3_dsm, user_id, "data*", project_id
358+
)
359+
data_file_names = {file.file_name for file in case_insensitive_data}
360+
assert "DataFile.CSV" in data_file_names
361+
362+
# Test mixed case pattern matching
363+
case_insensitive_config = await _search_files_by_pattern(
364+
simcore_s3_dsm, user_id, "CONFIG*", project_id
365+
)
366+
config_file_names = {file.file_name for file in case_insensitive_config}
367+
assert "ConfigFile.JSON" in config_file_names
368+
assert "config.json" in config_file_names
369+
370+
case_insensitive_xml = await _search_files_by_pattern(
371+
simcore_s3_dsm, user_id, "*.XML", project_id
372+
)
373+
xml_file_names = {file.file_name for file in case_insensitive_xml}
374+
assert "XMLDataFile.xml" in xml_file_names
375+
376+
camelcase_results = await _search_files_by_pattern(
377+
simcore_s3_dsm, user_id, "camelcase*", project_id
378+
)
379+
assert len(camelcase_results) == 1
380+
assert camelcase_results[0].file_name == "CamelCaseFile.txt"
381+
382+
pdf_results = await _search_files_by_pattern(
383+
simcore_s3_dsm, user_id, "*.PDF", project_id
384+
)
385+
pdf_file_names = {file.file_name for file in pdf_results}
386+
assert "MyDocument.PDF" in pdf_file_names
387+
388+
318389
@pytest.fixture
319390
async def paths_for_export(
320391
random_project_with_files: Callable[

0 commit comments

Comments
 (0)