Build path from filename and directory
from pathlib import Path
def build_full_path_pathlib(directory_path, filename):
"""
Builds and returns a full file path from a directory path and a filename using pathlib.
Handles various edge cases for directory_path including:
- Trailing slash or no trailing slash.
- Relative paths.
- Absolute paths.
Args:
directory_path (str): The path to the directory. Can be relative or absolute,
and may or may not have a trailing slash.
filename (str): The name of the file.
Returns:
str: The full, normalized, and absolute path to the file.
Returns None if directory_path or filename is not a string, or is empty/whitespace.
"""
# Edge case 1: Validate input types
if not isinstance(directory_path, str) or not isinstance(filename, str):
print("Error: directory_path and filename must be strings.")
return None
# Edge case 2: Handle empty input strings
if not directory_path.strip() or not filename.strip():
print("Error: directory_path and filename cannot be empty or just whitespace.")
return None
try:
# Create Path objects
# pathlib automatically handles various forms of paths (trailing slashes,
# relative/absolute, different OS separators) gracefully upon creation.
dir_path_obj = Path(directory_path)
file_name_obj = Path(filename) # Filenames can sometimes be paths themselves, though rare.
# Edge case 3 & 4: Combining paths and making them absolute and normalized.
# The '/' operator for Path objects is designed for path joining,
# handling separators automatically.
# .resolve() makes the path absolute and canonical (e.g., resolves '..' and '.' components,
# and symbolic links if they exist). This is the pathlib equivalent of os.path.abspath
# and os.path.normpath combined, but also handles symlinks.
# For strict 'normalize and absolute' without symlink resolution, .absolute().normpath() could be used,
# but .resolve() is generally preferred for "full path".
full_path_obj = (dir_path_obj / file_name_obj).resolve()
return str(full_path_obj)
except Exception as e:
# Catch any other potential errors during path creation/resolution
print(f"An unexpected error occurred: {e}")
return None
# --- Test Cases ---
if __name__ == "__main__":
print("--- Test Cases (using pathlib) ---")
# Get the current working directory for comparison with resolved paths
current_working_dir = Path.cwd()
# Test Case 1: Standard case, no trailing slash, relative path
print(f"Test 1: {build_full_path_pathlib('my_docs', 'report.pdf')}")
# Expected: {current_working_dir}/my_docs/report.pdf
# Test Case 2: Trailing slash, relative path
print(f"Test 2: {build_full_path_pathlib('my_docs/', 'image.jpg')}")
# Expected: {current_working_dir}/my_docs/image.jpg
# Test Case 3: Absolute path, no trailing slash
print(f"Test 3: {build_full_path_pathlib('/home/user/data', 'data.txt')}")
# Expected: /home/user/data/data.txt (assuming this is a valid path on the system)
# Test Case 4: Absolute path, trailing slash
print(f"Test 4: {build_full_path_pathlib('/var/log/', 'syslog.log')}")
# Expected: /var/log/syslog.log (assuming this is a valid path on the system)
# Test Case 5: Directory with '..' (parent directory)
print(f"Test 5: {build_full_path_pathlib('my_app/../config', 'settings.ini')}")
# Expected: {current_working_dir}/config/settings.ini
# Test Case 6: Directory with '.' (current directory)
print(f"Test 6: {build_full_path_pathlib('./temp', 'tempfile.tmp')}")
# Expected: {current_working_dir}/temp/tempfile.tmp
# Test Case 7: Empty directory path
print(f"Test 7: {build_full_path_pathlib('', 'file.txt')}")
# Expected: Error message, returns None
# Test Case 8: Empty filename
print(f"Test 8: {build_full_path_pathlib('/path/to/dir', '')}")
# Expected: Error message, returns None
# Test Case 9: None for directory path
print(f"Test 9: {build_full_path_pathlib(None, 'file.txt')}")
# Expected: Error message, returns None
# Test Case 10: None for filename
print(f"Test 10: {build_full_path_pathlib('/path/to/dir', None)}")
# Expected: Error message, returns None
# Test Case 11: Directory path is just spaces
print(f"Test 11: {build_full_path_pathlib(' ', 'file.txt')}")
# Expected: Error message, returns None
# Test Case 12: Filename is just spaces
print(f"Test 12: {build_full_path_pathlib('/path/to/dir', ' ')}")
# Expected: Error message, returns None
# Test Case 13: Windows-style paths (on systems where pathlib handles them)
# pathlib handles OS-specific path separators correctly internally.
print(f"Test 13: {build_full_path_pathlib('C:\\Users\\Guest\\Documents', 'document.docx')}")
# Expected: C:\Users\Guest\Documents\document.docx (on Windows)
# /C:/Users/Guest/Documents/document.docx (on Linux/macOS, if 'C:' is treated as a component)
# Note: Behavior of C:\ on non-Windows can vary based on exact string interpretation.
# Pathlib will generally treat backslashes as forward slashes on POSIX if not escaped.
# Test Case 14: Filename with path separators (Pathlib handles this intelligently with '/')
print(f"Test 14: {build_full_path_pathlib('/tmp', 'subdir/another_file.log')}")
# Expected: /tmp/subdir/another_file.log
# Test Case 15: Directory path with mixed separators (Pathlib handles this)
print(f"Test 15: {build_full_path_pathlib('/path/to\\dir/', 'file.txt')}")
# Expected: /path/to/dir/file.txt (on Linux/macOS)
# \path\to\dir\file.txt (on Windows)
# Test Case 16: Path that doesn't exist, but should still be resolved
# Note: .resolve() will raise FileNotFoundError if a component of the path
# does not exist and it's a symbolic link pointing to a non-existent target.
# However, for simple non-existent directories or files, it usually still
# constructs the canonical path.
print(f"Test 16: {build_full_path_pathlib('./non_existent_dir/nested', 'new_file.txt')}")
# Expected: {current_working_dir}/non_existent_dir/nested/new_file.txt