feat(api): expose registered extension filters on /experiment/models

Each folder in the listing now carries its registered extension
allowlist verbatim; an empty array means the folder accepts any
extension (match-all), mirroring filter_files_extensions semantics.

Gives consumers the filtering rule itself rather than just its output:
/models/{folder} lists files by the per-folder rule but the rule is not
exposed anywhere, and /experiment/models/{folder} filters everything by
the global supported_pt_extensions regardless of registration.
Presentation-level filtering of match-all folders (e.g. hiding
README/config noise that repository-downloading custom nodes leave in
model directories) is deliberately left to the consumer.
This commit is contained in:
Simon Pinfold 2026-07-03 14:47:51 +12:00
parent b08debceca
commit 72bee5427b
3 changed files with 35 additions and 1 deletions

View File

@ -35,7 +35,11 @@ class ModelFileManager:
for folder in model_types: for folder in model_types:
if folder in folder_black_list: if folder in folder_black_list:
continue continue
output_folders.append({"name": folder, "folders": folder_paths.get_folder_paths(folder)}) output_folders.append({
"name": folder,
"folders": folder_paths.get_folder_paths(folder),
"extensions": sorted(folder_paths.folder_names_and_paths[folder][1]),
})
return web.json_response(output_folders) return web.json_response(output_folders)
# NOTE: This is an experiment to replace `/models/{folder}` # NOTE: This is an experiment to replace `/models/{folder}`

View File

@ -783,6 +783,14 @@ components:
ModelFolder: ModelFolder:
description: Represents a folder containing models description: Represents a folder containing models
properties: properties:
extensions:
description: The folder's registered file-extension allowlist. An empty array means the folder accepts any extension (match-all).
example:
- .ckpt
- .safetensors
items:
type: string
type: array
folders: folders:
description: List of paths where models of this type are stored description: List of paths where models of this type are stored
example: example:

View File

@ -24,6 +24,28 @@ def app(model_manager):
app.add_routes(routes) app.add_routes(routes)
return app return app
async def test_get_model_folders_includes_registered_extensions(aiohttp_client, app, tmp_path):
"""Folders expose their registered extension set verbatim; an empty list
means match-all (filter_files_extensions semantics)."""
with patch('folder_paths.folder_names_and_paths', {
'test_checkpoints': ([str(tmp_path)], {'.safetensors', '.ckpt'}),
'test_configs': ([str(tmp_path)], ['.yaml']),
'test_match_all': ([str(tmp_path)], set()),
'configs': ([str(tmp_path)], ['.yaml']),
}):
client = await aiohttp_client(app)
response = await client.get('/experiment/models')
assert response.status == 200
folders = {f['name']: f for f in await response.json()}
assert 'configs' not in folders # blocklisted
assert folders['test_checkpoints']['folders'] == [str(tmp_path)]
assert folders['test_checkpoints']['extensions'] == ['.ckpt', '.safetensors']
assert folders['test_configs']['extensions'] == ['.yaml']
# Match-all registrations are exposed honestly, not substituted.
assert folders['test_match_all']['extensions'] == []
async def test_get_model_preview_safetensors(aiohttp_client, app, tmp_path): async def test_get_model_preview_safetensors(aiohttp_client, app, tmp_path):
img = Image.new('RGB', (100, 100), 'white') img = Image.new('RGB', (100, 100), 'white')
img_byte_arr = BytesIO() img_byte_arr = BytesIO()