Skip to content

Commit

Permalink
Handle problematic characters in Workunit.store_output_folder
Browse files Browse the repository at this point in the history
  • Loading branch information
leoschwarz committed Feb 28, 2025
1 parent 6455a05 commit 6de069e
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 2 deletions.
8 changes: 8 additions & 0 deletions bfabric/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ Versioning currently follows `X.Y.Z` where

## \[Unreleased\]

### Fixed

- Handle problematic characters in `Workunit.store_output_folder`.

### Added

- Generic functionality in `bfabric.utils.path_safe_name` to validate names for use in paths.

## \[1.13.22\] - 2025-02-19

### Fixed
Expand Down
5 changes: 3 additions & 2 deletions bfabric/src/bfabric/entities/workunit.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from bfabric.entities.core.has_container_mixin import HasContainerMixin
from bfabric.entities.core.has_many import HasMany
from bfabric.entities.core.has_one import HasOne
from bfabric.utils.path_safe_name import path_safe_name

if TYPE_CHECKING:
from bfabric import Bfabric
Expand Down Expand Up @@ -52,8 +53,8 @@ def store_output_folder(self) -> Path:
return Path(
f"{self.application.storage['projectfolderprefix']}{self.container.id}",
"bfabric",
self.application["technology"].replace(" ", "_"),
self.application["name"].replace(" ", "_"),
path_safe_name(self.application["technology"]),
path_safe_name(self.application["name"]),
date.strftime("%Y/%Y-%m/%Y-%m-%d/"),
f"workunit_{self.id}",
)
42 changes: 42 additions & 0 deletions bfabric/src/bfabric/utils/path_safe_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import re
from typing import Annotated
from pydantic import BeforeValidator

# Regex pattern to match any characters that aren't alphanumeric, underscore, or hyphen
_regex = re.compile(r"[^a-zA-Z0-9_-]+")


def path_safe_name(name: str) -> str:
"""
Convert a string to a filesystem-safe format by replacing unsafe characters with underscores.
Args:
name: The string to be converted to a path-safe format
Returns:
A string with all characters not matching [a-zA-Z0-9_-] replaced with underscores
Example:
>>> path_safe_name("Hello World!")
'Hello_World_'
>>> path_safe_name("file:name.txt")
'file_name_txt'
"""
return _regex.sub("_", name)


# Custom type that automatically converts strings to path-safe format
PathSafeStr = Annotated[str, BeforeValidator(path_safe_name)]
"""
A custom Pydantic type that automatically sanitizes strings for safe use in file paths.
When a field is annotated with this type, any unsafe characters will be replaced with underscores.
Example:
>>> from pydantic import BaseModel
>>> class FileModel(BaseModel):
... filename: PathSafeStr
...
>>> file = FileModel(filename="my file:name.txt")
>>> file.filename
'my_file_name_txt'
"""
14 changes: 14 additions & 0 deletions tests/bfabric/entities/test_workunit.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ def test_store_output_folder(mocker, mock_workunit) -> None:
assert Path("xyz12/bfabric/tech/my_app/2024/2024-01/2024-01-02/workunit_30000") == mock_workunit.store_output_folder


def test_store_output_folder_when_unsafe_chars(mocker, mock_workunit) -> None:
mock_application = mocker.MagicMock(storage={"projectfolderprefix": "xyz"})
mock_application.__getitem__.side_effect = {
"technology": "tech & tech",
"name": "my app (:",
}.__getitem__
mocker.patch.object(mock_workunit, "application", mock_application)
mocker.patch.object(Workunit, "container", mocker.PropertyMock(return_value=mocker.MagicMock(id=12)))
assert (
Path("xyz12/bfabric/tech_tech/my_app_/2024/2024-01/2024-01-02/workunit_30000")
== mock_workunit.store_output_folder
)


def test_repr() -> None:
workunit = Workunit({"id": 30000}, client=None)
assert repr(workunit) == "Workunit({'id': 30000}, client=None)"
Expand Down

0 comments on commit 6de069e

Please sign in to comment.