Common Utilities

pymdtools.common is the shared utility layer used by the rest of pymdtools. It exposes one stable facade for path handling, filesystem operations, text helpers, UTC date/time helpers, and small validation utilities.

Prefer importing public helpers from pymdtools.common instead of importing from implementation modules such as pymdtools.common.fs or pymdtools.common.text.

Common Usage

Read and write text files with encoding detection:

from pymdtools.common import get_file_content, set_file_content

content = get_file_content("README.md")
set_file_content("build/output.txt", content)

Create a backup next to an existing file:

from pymdtools.common import create_backup

backup_path = create_backup("report.md")

Copy a directory tree incrementally:

from pymdtools.common import copytree

copytree("templates", "build/templates")

Create safe text identifiers and paths:

from pymdtools.common import get_valid_filename, path_to_url, slugify

filename = get_valid_filename("CON: bad/name.md")
slug = slugify("My Markdown Title")
url_path = path_to_url("Docs/My Page.md")

Enrich exceptions with contextual information:

from pymdtools.common import handle_exception

@handle_exception("Unable to convert markdown file", filename="File")
def convert(filename: str) -> None:
    raise ValueError("invalid input")

Helpers By Family

Core helpers

  • handle_exception: enrich exceptions raised by decorated functions.

  • static: attach static attributes to a function.

  • Constant: expose read-only descriptor values.

Filesystem and path helpers

  • to_path, normpath, with_suffix, path_depth

  • check_folder, ensure_folder, check_file

  • copytree, create_backup, make_temp_dir

  • apply_to_files, ApplyResult, find_file

  • get_this_filename

  • is_binary_file

  • detect_file_encoding, get_file_content, set_file_content

Text helpers

  • convert_for_stdout

  • to_ascii

  • slugify

  • get_valid_filename

  • get_flat_filename

  • path_to_url

  • limit_str

Time helpers

  • today_utc

  • now_utc_timestamp

  • parse_timestamp

Validation helpers

  • check_len

Public API

Shared utility layer for the pymdtools package.

This submodule provides a stable, public façade that aggregates low-level helpers used across the project. The internal implementation is split into several focused modules (core, fs, text, datetime_utils), but consumers should import public helpers from this facade:

from pymdtools.common import slugify, get_file_content

The public API exposed here is considered stable. Internal submodules are implementation details and may evolve without notice.

Design principles

  • Clear separation of concerns:
    • core : typing aliases, decorators, utility classes

    • fs : filesystem and path operations

    • text : string and filename transformations

    • datetime_utils : UTC date/time helpers

  • Minimal dependencies:

    Optional third-party dependencies (e.g. chardet, dateutil, unidecode) are imported lazily in the functions that require them.

  • Explicit API surface:

    The __all__ variable defines the official public contract.

Typing utilities

T, F, P, R, T_sized

Generic type variables used across the project.

PathInput

Alias for str | os.PathLike[str] | pathlib.Path.

Core utilities

handle_exception

Decorator for enriching raised exceptions with contextual metadata.

static

Decorator for attaching static-like attributes to a function.

Constant

Descriptor for read-only instance-level constant values.

Filesystem and path utilities

Path helpers:

to_path, normpath, with_suffix, path_depth

Filesystem checks:

check_folder, ensure_folder, check_file

File operations:

copytree, create_backup, make_temp_dir

Traversal:

apply_to_files, ApplyResult, find_file

Introspection:

get_this_filename

Binary detection:

is_binary_file

Text file IO:

detect_file_encoding get_file_content set_file_content

Text utilities

convert_for_stdout

Safe conversion for stdout output.

to_ascii

Unicode to ASCII transliteration (if unidecode is installed).

slugify

URL-friendly slug generation.

get_valid_filename

Sanitized filename generator.

get_flat_filename

Flattened path string suitable for filenames.

path_to_url

Convert filesystem path to POSIX-style URL path.

limit_str

Truncate a string by whole separator-delimited tokens.

Time and validation utilities

today_utc

Current UTC date as YYYY-MM-DD.

now_utc_timestamp

Current UTC timestamp as YYYY-MM-DD HH:MM:SS.

parse_timestamp

Flexible timestamp parsing (requires python-dateutil).

check_len

Length validation helper.

Usage example

from pymdtools.common import slugify, get_file_content

slug = slugify("My Title")
content = get_file_content("README.md")
class pymdtools.common.ApplyResult(processed: int, succeeded: int, failed: int, skipped: int)

Bases: object

Result of a batch apply operation.

failed: int
processed: int
skipped: int
succeeded: int
class pymdtools.common.Constant(value: T | None = None)

Bases: Generic[T]

Descriptor representing an immutable constant value.

This class is intended to be used as a class attribute and provides read-only access to a fixed value through both the class and its instances.

The constant value cannot be modified or deleted via an instance. Any attempt to assign or delete the attribute on an instance will raise a ValueError.

Note

Assignment at the class level (e.g. MyClass.CONST = ...) is not prevented. This is a limitation of Python descriptors and is considered acceptable by design. Preventing class-level reassignment would require a metaclass or custom __setattr__ logic on the owning class.

Example

class Config:

VERSION = Constant(“1.0”) DEBUG = Constant(False)

Config.VERSION # “1.0” Config().VERSION # “1.0”

Config().VERSION = “2.0” # ValueError: You can’t change a constant value

Config.VERSION = “2.0” # Allowed: the descriptor is replaced at the class level

property value: T | None

Return the constant value.

pymdtools.common.apply_to_files(root: str | PathLike[str] | Path, func: Callable[[Path], T], *, recursive: bool = True, include_globs: Sequence[str] = ('*',), exclude_globs: Sequence[str] = (), expected_ext: str | tuple[str, ...] | None = None, follow_symlinks: bool = False, on_error: str = 'raise') tuple[list[T], ApplyResult, list[tuple[Path, Exception]]]

Apply a function to files under a path (file or directory).

Parameters:
  • root (str | os.PathLike[str] | Path) – A file path or a directory path.

  • func (Callable[[Path], T]) – Function applied to each selected file. Receives a Path and returns T.

  • recursive (bool, default=True) – If root is a directory, walk recursively if True, else only direct children.

  • include_globs (Sequence[str], default=("*",)) – Filename patterns to include (fnmatch patterns), applied to relative paths from the root directory. Example: ("**/*.md", "*.md") is NOT supported by fnmatch; use simple patterns like ("*.md",) when non-recursive. For recursive matching, patterns are applied to the POSIX relative path string.

  • exclude_globs (Sequence[str], default=()) – Patterns to exclude (same matching rules as include_globs).

  • expected_ext (str | tuple[str, ...] | None, default=None) – Restrict to file extensions. Accepts “.md”, “md”, or tuple of them. If None, no extension filter is applied.

  • follow_symlinks (bool, default=False) – If True, symlinked directories may be traversed. Use with care (cycles).

  • on_error ({"raise","collect"}, default="raise") –

    • “raise”: stop at first error

    • ”collect”: continue and return errors list

Returns:

(results, summary, errors) – results: return values from func for each successful file summary: counts errors: list of (file, exception) when on_error=”collect”

Return type:

(list[T], ApplyResult, list[(Path, Exception)])

Notes

  • This function does not perform I/O by itself except directory traversal.

  • func is responsible for reading/writing file contents.

pymdtools.common.check_file(path: str | PathLike[str] | Path, expected_ext: str | tuple[str, ...] | None = None) Path

Validate that path exists and is a regular file, optionally enforcing an extension.

Parameters:
  • path (str | os.PathLike[str] | Path) – Input file path.

  • expected_ext (str | tuple[str, ...] | None, default=None) – Expected file extension(s). Examples: - “.md” - “md” - (“.md”, “.markdown”) If None, no extension check is performed.

Returns:

Normalized absolute file path.

Return type:

Path

Raises:
  • FileNotFoundError – If the path does not exist.

  • IsADirectoryError – If the path exists but is not a regular file.

  • ValueError – If expected_ext is provided and the file extension does not match.

pymdtools.common.check_folder(path: str | PathLike[str] | Path) Path

Validate that path exists and is a directory.

Parameters:

path (str | os.PathLike[str] | Path) – Input directory path.

Returns:

Normalized absolute directory path.

Return type:

Path

Raises:
  • FileNotFoundError – If the path does not exist.

  • NotADirectoryError – If the path exists but is not a directory.

pymdtools.common.check_len(obj: T_sized, expected: int = 1, *, name: str = 'object') T_sized

Ensure that an object has the expected length.

Parameters:
  • obj – Any object supporting len().

  • expected – Expected length (must be >= 0).

  • name – Logical name used in error messages.

Returns:

The input object (for fluent-style usage).

Raises:
  • ValueError – If the object’s length does not match expected, or if expected is negative.

  • TypeError – If obj does not support len().

pymdtools.common.convert_for_stdout(text: str, *, stream: ~typing.TextIO = <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>, fallback_encoding: str = 'utf-8', errors: str = 'replace') str

Adapt a Unicode string to the encoding of the given text stream.

Parameters:
  • text (str) – Input Unicode string.

  • stream (TextIO, default=sys.stdout) – Target output stream. Its .encoding attribute is used when available.

  • fallback_encoding (str, default="utf-8") – Encoding used if the stream has no encoding.

  • errors (str, default="replace") – Error handler used for the encode/decode round-trip.

Returns:

Text safe to print to the given stream.

Return type:

str

pymdtools.common.copytree(src: str | PathLike[str] | Path, dst: str | PathLike[str] | Path, *, symlinks: bool = False, ignore: Callable[[str, list[str]], Iterable[str]] | None = None) Path

Copy a directory tree from src to dst (incremental, dirs_exist_ok=True).

  • Creates destination directories as needed

  • Recursively copies files

  • Supports an ignore callable compatible with shutil.copytree

  • Optionally preserves symlinks when symlinks=True

  • Copies a file only if destination missing, sizes differ, or source is newer

Parameters:
  • src (str | os.PathLike[str] | Path) – Source directory path.

  • dst (str | os.PathLike[str] | Path) – Destination directory path (created if missing).

  • symlinks (bool, default=False) – If True, copy symlinks as symlinks. If False, follow symlinks and copy target content.

  • ignore (callable | None, default=None) – Callable with signature ignore(dirpath, names) -> iterable returning the names to ignore in dirpath (same contract as shutil.copytree).

Returns:

Destination directory path.

Return type:

Path

Raises:
  • FileNotFoundError – If src does not exist.

  • NotADirectoryError – If src is not a directory.

  • ValueError – If dst is src, is contained in src, or a followed directory symlink introduces a traversal cycle.

  • FileExistsError – If source and destination entries have incompatible types, or copying a symbolic link would replace an existing entry.

  • OSError – For underlying filesystem errors.

pymdtools.common.create_backup(file_path: str | PathLike[str] | Path, *, ext: str = '.bak', max_tries: int = 100, date_prefix: str | None = None) Path

Create a backup copy of a file next to it.

The backup name is built as:

<original_name>.<YYYY-MM-DD>-<N><ext>

Example

report.md -> report.md.2026-02-27-1.bak

Parameters:
  • file_path (str | os.PathLike[str] | Path) – Source file to back up.

  • ext (str, default=".bak") – Backup extension (with or without leading dot).

  • max_tries (int, default=100) – Maximum number of candidate names to try before failing.

  • date_prefix (str | None, default=None) – Optional override for the date prefix (format not enforced). If None, uses today_utc() from your module.

Returns:

Path to the created backup file.

Return type:

Path

Raises:
  • FileNotFoundError – If the source file does not exist.

  • IsADirectoryError – If the source path is not a regular file.

  • ValueError – If ext is empty or max_tries is not positive.

  • FileExistsError – If no available backup filename is found within max_tries.

pymdtools.common.detect_file_encoding(path: str | PathLike[str] | Path, *, default: str = 'utf-8', min_confidence: float = 0.5, sample_size: int = 262144, prefer_utf8_sig: bool = True) str

Detect the text encoding of a file using chardet, with BOM handling.

The function reads up to sample_size bytes. If a known Unicode BOM is present, it returns the corresponding encoding immediately. Otherwise it delegates to chardet and returns the detected encoding if the confidence is >= min_confidence. If detection is inconclusive, it returns default.

Parameters:
  • path (str | os.PathLike[str] | Path) – File path to inspect.

  • default (str, default="utf-8") – Encoding returned when detection fails or confidence is too low.

  • min_confidence (float, default=0.50) – Minimum confidence threshold (0.0..1.0).

  • sample_size (int, default=262144) – Number of bytes read from the file (default: 256 KB).

  • prefer_utf8_sig (bool, default=True) – If True, returns “utf-8-sig” when a UTF-8 BOM is present; otherwise “utf-8”.

Returns:

A normalized encoding name (lowercase), or default if detection is inconclusive.

Return type:

str

Raises:
  • FileNotFoundError – If the file does not exist.

  • IsADirectoryError – If the path is not a file.

  • ValueError – If min_confidence is outside [0.0, 1.0] or sample_size <= 0.

  • ImportError – If chardet is not installed.

  • OSError – For underlying I/O errors.

pymdtools.common.ensure_folder(path: str | PathLike[str] | Path) Path

Ensure that a directory exists.

If the directory does not exist, it is created (including parents). If it already exists, nothing is done.

Parameters:

path (str | os.PathLike[str] | Path) – Target directory path.

Returns:

Normalized absolute directory path.

Return type:

Path

Raises:
  • NotADirectoryError – If the path exists but is not a directory.

  • OSError – If directory creation fails.

pymdtools.common.find_file(filename: str, start_points: Sequence[str | PathLike[str] | Path], relative_paths: Sequence[str | PathLike[str] | Path], *, max_up: int = 4) Path

Find a file by searching from multiple start points, optionally walking up parent directories.

The function searches for the first matching file according to a deterministic order.

Search order

For each start in start_points (in the given order):
  • Let base = start

  • For each up from 0 to max_up (inclusive):
    • Let anchor = base moved up up times (anchor = anchor.parent repeated)

    • For each rel in relative_paths (in the given order):
      • Test candidate: anchor / rel / filename

Notes

  • start_points are used as anchors; they are not required to exist.

  • relative_paths MUST be confined relative paths: absolute, rooted, drive-relative, and parent-traversing forms are rejected.

  • Resolved candidates must remain below the anchor selected for that iteration, including when links are involved.

  • The returned path is normalized as an absolute path via resolve(strict=False).

  • The function only returns when candidate.is_file() is True.

param filename:

Target filename (no path). Must be a non-empty string. Example: “config.yml”.

type filename:

str

param start_points:

Absolute or relative paths used as search anchors. Examples: - Path.cwd() - “/some/project/subdir” - “relative/subdir”

type start_points:

Sequence[str | os.PathLike[str] | Path]

param relative_paths:

Relative paths to try under each anchor. Each element must be relative. Examples: - “.” - “docs” - Path(“configs”) / “environments”

type relative_paths:

Sequence[str | os.PathLike[str] | Path]

param max_up:

Maximum number of parent levels to walk up from each start point (inclusive). max_up=0 searches only under the start point itself.

type max_up:

int, default=4

returns:

Absolute normalized path of the first file found.

rtype:

Path

raises ValueError:
  • If filename is empty or contains a path. - If max_up < 0. - If any item in relative_paths is rooted or contains ... - If a resolved candidate escapes its current anchor.

raises FileNotFoundError:

If no matching file is found. The exception message includes the tested paths.

Examples

Search for “config.yml” starting from the current directory and a secondary start point, trying “.”, “configs”, and walking up to 2 parent levels:

>>> find_file(
...     "config.yml",
...     start_points=[Path.cwd(), "other/start"],
...     relative_paths=[".", "configs"],
...     max_up=2,
... )
The effective candidates include (in order):
  • <cwd> / “.” / “config.yml”

  • <cwd> / “configs” / “config.yml”

  • <cwd.parent> / “.” / “config.yml”

  • <cwd.parent> / “configs” / “config.yml”

  • <cwd.parent.parent> / “.” / “config.yml”

  • <cwd.parent.parent> / “configs” / “config.yml”

  • then the same pattern for “other/start”.

pymdtools.common.get_file_content(path: str | PathLike[str] | Path, *, encoding: str | None = None, default_encoding: str = 'utf-8', min_confidence: float = 0.5, sample_size: int = 262144, errors: str = 'strict', reject_binary: bool = True, strip_bom: bool = True) str

Read a text file and return its content, with BOM protection.

If encoding is not provided, the encoding is detected (BOM first, then chardet). Optionally rejects binary files and strips leading BOM.

Parameters:
  • path (str | os.PathLike[str] | Path) – File path.

  • encoding (str | None, default=None) – If provided, this encoding is used directly (no detection).

  • default_encoding (str, default="utf-8") – Default encoding used when detection is inconclusive.

  • min_confidence (float, default=0.50) – Minimum confidence threshold for chardet when auto-detecting.

  • sample_size (int, default=256*1024) – Number of bytes read for encoding detection.

  • errors (str, default="strict") – Error handler for decoding.

  • reject_binary (bool, default=True) – If True, raises ValueError when file appears binary.

  • strip_bom (bool, default=True) – If True, removes leading Unicode BOM character (U+FEFF) from the decoded content.

Returns:

File content as text.

Return type:

str

pymdtools.common.get_flat_filename(filename: str, *, replacement: str = '_') str

Return a flattened, Windows-safe filename.

This function: - transliterates Unicode characters to ASCII, - removes punctuation and special characters, - replaces spaces and separators with a single replacement character, - ensures the result is a valid Windows filename.

Parameters:
  • filename – Input filename (without path).

  • replacement – Character used to replace separators (default: “_”).

Returns:

A flattened, Windows-safe filename.

Raises:

ValueError – If filename is empty after sanitization.

pymdtools.common.get_this_filename() Path

Return the absolute path of the current program/module.

  • If running as a frozen executable (e.g., PyInstaller), returns sys.executable.

  • Otherwise returns the current module file path (__file__).

  • In interactive contexts where __file__ is unavailable, falls back to sys.argv[0], then to the current working directory.

Returns:

Absolute Path.

pymdtools.common.get_valid_filename(filename: str, *, replacement: str = '_', strip: bool = True) str

Return a filename safe for Windows filesystems.

This function: - replaces invalid Windows filename characters, - trims leading/trailing whitespace, - removes trailing dots and spaces, - avoids Windows reserved names.

Parameters:
  • filename – Input filename (not a full path).

  • replacement – Character used to replace invalid characters.

  • strip – Whether to strip leading/trailing whitespace.

Returns:

A Windows-safe filename.

Raises:
  • TypeError – If filename or replacement is not text.

  • ValueError – If replacement is not one safe filename character, or if filename is empty after sanitization.

pymdtools.common.handle_exception(action_desc: str, **kwargs_print_name: str) Callable[[Callable[[P], R]], Callable[[P], R]]

Enrich exceptions raised by a decorated function.

The wrapper catches any exception, builds a message containing action_desc, the decorated function name, and selected keyword argument values, then raises RuntimeError from the original exception. The original exception remains available through __cause__.

ParamSpec and TypeVar are used so static type checkers keep the decorated function signature.

Parameters:
  • action_desc – Human-readable description of the action being performed.

  • **kwargs_print_name – Mapping from keyword argument names to display labels included in the enriched message.

Returns:

A decorator that wraps the target function while preserving its type signature.

Raises:

RuntimeError – Raised by the wrapper when the decorated function fails.

Example

>>> @handle_exception("Error while converting file", filename="File")
... def convert(filename: str) -> None:
...     raise ValueError("Invalid format")
...
>>> convert(filename="doc.md")
Traceback (most recent call last):
    ...
RuntimeError: Invalid format
Error while converting file (convert)
File : doc.md
pymdtools.common.is_binary_file(path: str | PathLike[str] | Path, *, sample_size: int = 8192, encoding: str | None = None) bool

Determine whether a file appears to be binary.

A file is considered text if: - It starts with a known Unicode BOM - It does not contain null bytes - It can be decoded as UTF-8 or as common Windows single-byte text

When encoding is provided, that codec is used for the text probe. This avoids classifying valid CP1252 or Latin-1 text as binary before it can be decoded by get_file_content().

Parameters:
  • path (str | os.PathLike[str] | Path) – File path.

  • sample_size (int, default=8192) – Number of bytes to read for detection.

  • encoding (str | None, default=None) – Optional expected text encoding.

Returns:

True if the file appears binary, False otherwise.

Return type:

bool

pymdtools.common.limit_str(value: str, limit: int, sep: str, min_last_word: int = 2) str

Limit a string by keeping whole tokens separated by sep, without exceeding limit.

The string is split on sep. Tokens are appended in order, re-joined with sep, as long as the resulting string length does not exceed limit.

Parameters:
  • value – Input text.

  • limit – Maximum length of the returned string (must be >= 0).

  • sep – Token separator used to split and re-join.

  • min_last_word – Minimum token length to be considered meaningful (>= 0). Tokens shorter than this value are ignored unless already included as part of the kept prefix.

Returns:

A shortened string not exceeding limit.

Raises:

ValueError – If limit < 0, min_last_word < 0, or sep is empty.

pymdtools.common.make_temp_dir(*, prefix: str = 'pymdtools_', suffix: str = '', dir: str | PathLike[str] | Path | None = None) Path

Create a temporary directory and return its Path.

Parameters:
  • prefix (str) – Prefix for the temporary directory name. Defaults to "pymdtools_".

  • suffix (str, default="") – Suffix for the temporary directory name.

  • dir (str | os.PathLike[str] | Path | None, default=None) – Parent directory in which to create the temp directory. If None, the system default temp directory is used.

Returns:

Path to the created temporary directory.

Return type:

Path

Notes

  • The directory is created immediately.

  • Caller is responsible for cleanup (e.g. via shutil.rmtree).

pymdtools.common.normpath(path: str | PathLike[str] | Path) Path

Return a normalized absolute Path.

This function: - Converts the input to a pathlib.Path - Expands ‘~’ - Resolves ‘.’ and ‘..’ - Returns an absolute path

It does NOT require the path to exist.

Parameters:

path (str | os.PathLike[str] | Path) – Input path.

Returns:

A normalized absolute Path object.

Return type:

Path

pymdtools.common.now_utc_timestamp() str

Return current UTC timestamp as ‘YYYY-MM-DD HH:MM:SS’ (UTC).

pymdtools.common.parse_timestamp(value: str) datetime

Parse a timestamp string into a datetime.

This function uses python-dateutil for flexible parsing.

Parameters:

value – Timestamp string.

Returns:

A datetime instance (timezone-aware if the input contains timezone information, otherwise naive).

Raises:
  • ValueError – If the timestamp cannot be parsed.

  • ImportError – If python-dateutil is not installed.

pymdtools.common.path_depth(path: str | PathLike[str] | Path) int

Return the depth of a filesystem path.

The depth is defined as the number of directory components. If the path appears to reference a file (has a suffix), the last component is ignored.

This function does not access the filesystem.

Parameters:

path (str | os.PathLike[str] | Path) – Filesystem path (absolute or relative).

Returns:

Number of directory levels in the path.

Return type:

int

pymdtools.common.path_to_url(path: str | Path, *, remove_accent: bool = True) str

Convert a filesystem path to a URL-safe path.

The function: - normalizes path separators, - lowercases the path, - replaces whitespace with hyphens, - optionally transliterates Unicode characters to ASCII, - percent-encodes characters for safe use in URLs.

Parameters:
  • path – Input path (string or Path).

  • remove_accent – If True, transliterate Unicode characters to ASCII.

Returns:

A URL-safe path string.

pymdtools.common.set_file_content(path: str | PathLike[str] | Path, content: str, encoding: str = 'utf-8', bom: bool = False, *, atomic: bool = True, newline: str | None = '\n', create_parents: bool = True) Path

Write text content to a file, optionally adding a UTF-8 BOM.

pymdtools.common.slugify(value: Any, *, allow_unicode: bool = False) str

Convert a string to a URL- and filename-safe slug.

The function: - converts the input to string, - optionally normalizes Unicode characters, - removes characters that are not alphanumeric, underscores, spaces or hyphens, - converts spaces and repeated hyphens to single hyphens, - lowercases the result.

Parameters:
  • value – Input value to slugify.

  • allow_unicode – If True, keep Unicode characters. If False, transliterate to ASCII.

Returns:

A slugified string (lowercase, hyphen-separated).

pymdtools.common.static(**attributes: Any) Callable[[F], F]

Attach static attributes to a function.

This decorator sets attributes on the decorated function object. It is typically used to emulate “static variables” inside a function, i.e. to keep state across calls without using globals.

Parameters:

**attributes – Attribute names and their initial values to attach to the function.

Returns:

A decorator that returns the same function with attributes set.

Example

>>> @static(counter=0)
... def f():
...     f.counter += 1
...     return f.counter
...
>>> f()
1
>>> f()
2
pymdtools.common.to_ascii(value: str) str

Transliterate a Unicode string to an ASCII approximation.

This function uses the third-party package Unidecode to convert non-ASCII characters to an ASCII representation.

Parameters:

value – Input Unicode string.

Returns:

An ASCII transliteration of the input string.

Raises:

ImportError – If the Unidecode package is not installed.

pymdtools.common.to_path(p: str | PathLike[str] | Path, *, expand_user: bool = True, resolve: bool = False, strict: bool = False) Path

Convert a path-like input to a pathlib.Path instance.

This helper standardizes path handling across the module. It guarantees that all internal path manipulations operate on pathlib.Path objects.

Parameters:
  • p (str | os.PathLike[str] | Path) –

    Input path. Can be:

    • A string path

    • Any os.PathLike object

    • A pathlib.Path instance

  • expand_user (bool, default=True) – If True, expands ‘~’ to the user home directory using Path.expanduser().

  • resolve (bool, default=False) –

    If True, resolves the path using Path.resolve().

    This will:

    • Normalize the path (remove ‘..’, ‘.’)

    • Follow symbolic links (unless strict=False and target missing)

  • strict (bool, default=False) –

    Only used if resolve=True.

    • If True, raises FileNotFoundError if the path does not exist.

    • If False, resolves as much as possible without requiring existence.

Returns:

A pathlib.Path object.

Return type:

Path

Notes

  • This function does NOT implicitly resolve paths unless resolve=True. This avoids surprising behavior with symlinks or non-existing paths.

  • Most library-level functions should call p = to_path(path) without resolve=True.

  • Use resolve=True only when canonicalization is explicitly required.

Examples

>>> to_path("~/.config")
PosixPath('/home/user/.config')
>>> to_path("file.txt", resolve=True)
PosixPath('/current/dir/file.txt')
>>> to_path("missing.txt", resolve=True, strict=True)
FileNotFoundError
pymdtools.common.today_utc() str

Return today’s date in UTC as ‘YYYY-MM-DD’.

pymdtools.common.with_suffix(path: str | PathLike[str] | Path, suffix: str) Path

Return a new Path with a modified file suffix.

Parameters:
  • path (str | os.PathLike[str] | Path) – Input file path.

  • suffix (str) – New file extension (with or without leading dot). Examples: - “.html” - “html”

Returns:

Path with updated suffix.

Return type:

Path

Raises:

ValueError – If suffix is empty or invalid.