Module astrapy.utils.document_paths

Functions

def escape_field_names(*field_names: str | int | Iterable[str | int]) ‑> str
Expand source code
def escape_field_names(*field_names: str | int | Iterable[str | int]) -> str:  # type: ignore[misc]
    """
    Escape one or more field-name path segments into a full string expression.

    Args:
        field_names: the function accepts any number of string or integer (non-negative)
            arguments - or, equivalently, a single argument with an iterable of them.
            In either case, the input(s) is a literal, unescaped specification for
            a path in a document.

    Returns:
        a single string resulting from dot-concatenation of the input segments, with
        each path segment having been escaped conforming to the Data API escaping rules.

    Example:
        >>> escape_field_names()
        ''
        >>> escape_field_names("f")
        'f'
        >>> escape_field_names(123)
        '123'
        >>> escape_field_names("f", 123, "tom&jerry")
        'f.123.tom&&jerry'
        >>> escape_field_names(["f"])
        'f'
        >>> escape_field_names(123)
        '123'
        >>> escape_field_names(["f", 123, "tom&jerry"])
        'f.123.tom&&jerry'
    """
    _field_names: Iterable[str | int]
    # strings are iterables, so:
    if len(field_names) == 1 and not isinstance(field_names[0], (str, int)):
        _field_names = field_names[0]
    else:
        # user passing a list of string-or-ints.
        # But secretly any arg may still be Iterable:
        _field_names = [
            segment
            for field_name in field_names
            for segment in (
                [field_name] if isinstance(field_name, (str, int)) else field_name
            )
        ]

    return FIELD_NAME_SEGMENT_SEPARATOR.join(
        [_escape_field_name(f_n) for f_n in _field_names]
    )

Escape one or more field-name path segments into a full string expression.

Args

field_names
the function accepts any number of string or integer (non-negative) arguments - or, equivalently, a single argument with an iterable of them. In either case, the input(s) is a literal, unescaped specification for a path in a document.

Returns

a single string resulting from dot-concatenation of the input segments, with each path segment having been escaped conforming to the Data API escaping rules.

Example

>>> escape_field_names()
''
>>> escape_field_names("f")
'f'
>>> escape_field_names(123)
'123'
>>> escape_field_names("f", 123, "tom&jerry")
'f.123.tom&&jerry'
>>> escape_field_names(["f"])
'f'
>>> escape_field_names(123)
'123'
>>> escape_field_names(["f", 123, "tom&jerry"])
'f.123.tom&&jerry'
def unescape_field_path(field_path: str) ‑> list[str]
Expand source code
def unescape_field_path(field_path: str) -> list[str]:
    """Apply unescaping rules to a single-string field path.

    The result is a list of the individual field names, each a literal
    with nothing escaped anymore.

    Args:
        field_path: an expression denoting a field path following dot-notation
            with escaping for special characters.

    Returns:
        a list of literal field names. Even "number-looking" fields, such as "0"
        or "12", are returned as strings and it is up to the caller to interpret
        these according to the context.

    Example:
        >>> unescape_field_path("a.b")
        ['a', 'b']
        >>>
        >>> unescape_field_path("a&.b")
        ['a.b']
        >>>
        >>> unescape_field_path("a&&b&.c")
        ['a&b.c']
        >>>
        >>> unescape_field_path("a&.b.c&&d")
        ['a.b', 'c&d']
    """

    segments: list[str] = []
    buffer = ""
    path_length = len(field_path)
    if path_length == 0:
        return segments

    char_i = 0
    while char_i < path_length:
        char = field_path[char_i]
        if char == FIELD_NAME_SEGMENT_SEPARATOR:
            segments += [buffer]
            buffer = ""
        elif char == FIELD_NAME_ESCAPE_CHAR:
            if char_i + 1 >= path_length:
                msg = UNTERMINATED_ESCAPE_ERROR_MESSAGE_TEMPLATE.format(
                    field_path=field_path,
                )
                raise ValueError(msg)
            char_i += 1
            char = field_path[char_i]
            if char in FIELD_NAME_ESCAPED_CHARS:
                buffer += char
            else:
                msg = ILLEGAL_ESCAPE_ERROR_MESSAGE_TEMPLATE.format(
                    field_path=field_path,
                    escape_sequence=f"{FIELD_NAME_ESCAPE_CHAR}{char}",
                )
                raise ValueError(msg)
        else:
            buffer += char
        char_i += 1
    segments += [buffer]

    return segments

Apply unescaping rules to a single-string field path.

The result is a list of the individual field names, each a literal with nothing escaped anymore.

Args

field_path
an expression denoting a field path following dot-notation with escaping for special characters.

Returns

a list of literal field names. Even "number-looking" fields, such as "0" or "12", are returned as strings and it is up to the caller to interpret these according to the context.

Example

>>> unescape_field_path("a.b")
['a', 'b']
>>>
>>> unescape_field_path("a&.b")
['a.b']
>>>
>>> unescape_field_path("a&&b&.c")
['a&b.c']
>>>
>>> unescape_field_path("a&.b.c&&d")
['a.b', 'c&d']