Coverage for packages/dqm-ml-core/src/dqm_ml_core/utils/matching.py: 100%
20 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-07-21 08:27 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-07-21 08:27 +0000
1"""Wildcard pattern matching utilities for config field resolution.
3Provides fnmatch-based expansion of ``include`` / ``exclude`` patterns
4against a list of available strings (column names, split values, etc.).
5"""
7import fnmatch
10def has_pattern(s: str) -> bool:
11 """Return ``True`` if the string contains any fnmatch wildcard character."""
12 return any(ch in s for ch in ("*", "?", "["))
15def resolve_patterns(patterns: list[str], available: list[str]) -> list[str]:
16 """Expand *patterns* against *available* strings using fnmatch.
18 Literal (non-wildcard) patterns are emitted as-is. Wildcard patterns
19 are matched against *available* via ``fnmatch.filter``. Order from
20 *patterns* is preserved (first occurrence wins) and duplicates are
21 removed.
23 Parameters
24 ----------
25 patterns:
26 List of patterns that may contain ``*``, ``?`` or ``[...]``.
27 available:
28 The set of concrete strings to match against.
30 Returns
31 -------
32 A deduplicated, ordered list of matching concrete strings.
33 """
34 result: list[str] = []
35 for p in patterns:
36 if has_pattern(p):
37 result.extend(fnmatch.filter(available, p))
38 else:
39 result.append(p)
40 return list(dict.fromkeys(result))
43def resolve_include_exclude(
44 include: list[str] | None,
45 exclude: list[str] | None,
46 available: list[str],
47) -> list[str]:
48 """Resolve (include, exclude) pattern lists against *available* strings.
50 1. If *include* is ``None``, every item in *available* is a candidate.
51 Otherwise, *include* patterns are expanded via :func:`resolve_patterns`.
52 2. Any candidate matching an *exclude* pattern is removed (exact match
53 for literal strings, ``fnmatch.filter`` for wildcard patterns).
55 Parameters
56 ----------
57 include:
58 Include patterns (``None`` = all).
59 exclude:
60 Exclude patterns (``None`` = nothing excluded).
61 available:
62 The set of concrete strings to match against.
64 Returns
65 -------
66 The filtered list (order preserved).
67 """
68 candidates = resolve_patterns(include, available) if include else list(available)
70 if not exclude:
71 return candidates
73 removed: set[str] = set()
74 for p in exclude:
75 if has_pattern(p):
76 removed.update(fnmatch.filter(candidates, p))
77 else:
78 removed.add(p)
80 return [c for c in candidates if c not in removed]