Skip to content

QualName

Dotted-name algebra for command qualnames: QualName and PythonName.

duho.qualname

Dotted-name algebra for command qualnames.

A :class:QualName base defines the parts/parent/join/split algebra, a :class:DotQualNamed mixes it into str with . as the separator, and :class:PythonName, whose :meth:PythonName.new runs parts through :func:duho.text.pysafe.

__all__ = ['QualName', 'DotQualNamed', 'PythonName'] module-attribute

DotQualNamed

Bases: QualName, str

A :class:QualName that is also a str, split on :attr:SEPARATOR.

SEPARATOR = '.' class-attribute instance-attribute

parts cached property

PythonName

Bases: DotQualNamed

A dotted name whose parts are Python-safe (via :func:duho.text.pysafe).

new(*parts, sanitize=True) classmethod

Build a new instance by joining parts.

Each dotted part is coerced through :func:duho.text.pysafe unless sanitize=False. Returns cls(name) (not hard-coded to PythonName), so a subclass calling .new() keeps its own type.

Source code in src/duho/qualname.py
155
156
157
158
159
160
161
162
163
164
165
166
167
@classmethod
def new(cls, *parts: _ty.Union[str, QualName], sanitize: bool = True) -> PythonName:
    """Build a new instance by joining ``parts``.

    Each dotted part is coerced through :func:`duho.text.pysafe` unless
    ``sanitize=False``. Returns ``cls(name)`` (not hard-coded to
    ``PythonName``), so a subclass calling ``.new()`` keeps its own type.
    """
    name = cls.qualjoin(*parts)
    if sanitize:
        name = _text.pysafe(name)

    return cls(name)

QualName

Abstract dotted-name algebra: parts, parent, join/split, path mapping.

name cached property

The last part (e.g. "c" for "a.b.c").

parent cached property

The name with its last part dropped (e.g. "a.b" for "a.b.c").

parts property

The name's parts, most-significant first (e.g. ("a", "b", "c")).

__truediv__(key)

self / key -- alias for self.qualjoin(self, key).

Source code in src/duho/qualname.py
75
76
77
def __truediv__(self, key: str | _ty.Iterable[str] | QualName) -> QualName:
    """``self / key`` -- alias for ``self.qualjoin(self, key)``."""
    return self.qualjoin(self, key)

as_path(root='/')

This qualname's parts joined onto root (a :class:~pathlib.PurePath).

Source code in src/duho/qualname.py
123
124
125
126
127
128
def as_path(self, root: _ty.Union[str, _P] = "/") -> _P:
    """This qualname's parts joined onto ``root`` (a :class:`~pathlib.PurePath`)."""
    if not hasattr(root, "joinpath"):
        root = _ty.cast(_P, _pathlib.PurePosixPath(root))

    return _ty.cast(_P, root).joinpath(*self.parts)

camelcase(start=0, end=None, *, separators=None)

CamelCase over parts[start:end], then re-split on separators.

An empty part (e.g. from a name with a leading, trailing or doubled separator) is skipped rather than indexed into -- matching :func:duho.text.camelcase's own empty-part guard.

Source code in src/duho/qualname.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def camelcase(
    self,
    start: int = 0,
    end: _ty.Optional[int] = None,
    *,
    separators: _ty.Optional[_ty.Union[str, _ty.Sequence[str]]] = None,
) -> str:
    """``CamelCase`` over ``parts[start:end]``, then re-split on ``separators``.

    An empty part (e.g. from a name with a leading, trailing or doubled
    separator) is skipped rather than indexed into -- matching
    :func:`duho.text.camelcase`'s own empty-part guard.
    """
    parts = self.parts
    camelcased = "".join(
        [
            part[0].upper() + part[1:]
            for part in parts[start : len(parts) if end is None else end]
            if part
        ]
    )
    return _text.camelcase(camelcased, separators=separators)

qualjoin(*parts) classmethod

Join parts (strings, iterables of strings, or other qualnames).

Source code in src/duho/qualname.py
51
52
53
54
@classmethod
def qualjoin(cls, *parts: _ty.Union[str, _ty.Iterable[str], QualName]) -> QualName:
    """Join ``parts`` (strings, iterables of strings, or other qualnames)."""
    return cls._qualjoin(cls._qualparts(*parts))

qualsplit(name) classmethod

Split name into its parts (a qualname's .parts if it has one).

Source code in src/duho/qualname.py
56
57
58
59
60
61
@classmethod
def qualsplit(cls, name: _ty.Union[str, QualName]) -> _ty.Sequence[str]:
    """Split ``name`` into its parts (a qualname's ``.parts`` if it has one)."""
    if hasattr(name, "parts"):
        return _ty.cast(QualName, name).parts
    return cls._qualsplit(_ty.cast(str, name))

relative_to(name)

This qualname's parts with name's (a required prefix) stripped.

Raises :class:ValueError with a readable message if name is not a prefix of this qualname's parts (including if it is longer).

Source code in src/duho/qualname.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def relative_to(self, name: QualName) -> QualName:
    """This qualname's parts with ``name``'s (a required prefix) stripped.

    Raises :class:`ValueError` with a readable message if ``name`` is not a
    prefix of this qualname's parts (including if it is longer).
    """
    parts = [*self.parts]
    other = list(name.parts)

    if len(other) > len(parts):
        raise ValueError(f"{self} is not relative to {name}")

    for idx, part in enumerate(other):
        if parts[idx] != part:
            raise ValueError(f"{self} is not relative to {name}")

    # Slice by the length of the (validated) prefix, NOT ``idx + 1``: an empty
    # base leaves the loop unentered, and ``idx + 1`` then dropped the first
    # part instead of returning self unchanged.
    return self.qualjoin(*parts[len(other) :])

with_name(name)

This qualname's parent joined with a new last part, name.

Source code in src/duho/qualname.py
63
64
65
def with_name(self, name: str) -> QualName:
    """This qualname's parent joined with a new last part, ``name``."""
    return self.qualjoin(self.parent, name)