1from typing import Any
2
3from .location import SourceLocation
4
5__all__ = ["Source", "is_source"]
6
7
8class Source:
9    """A representation of source input to GraphQL."""
10
11    # allow custom attributes and weak references (not used internally)
12    __slots__ = "__weakref__", "__dict__", "body", "name", "location_offset"
13
14    def __init__(
15        self,
16        body: str,
17        name: str = "GraphQL request",
18        location_offset: SourceLocation = SourceLocation(1, 1),
19    ) -> None:
20        """Initialize source input.
21
22        The ``name`` and ``location_offset`` parameters are optional, but they are
23        useful for clients who store GraphQL documents in source files. For example,
24        if the GraphQL input starts at line 40 in a file named ``Foo.graphql``, it might
25        be useful for ``name`` to be ``"Foo.graphql"`` and location to be ``(40, 0)``.
26
27        The ``line`` and ``column`` attributes in ``location_offset`` are 1-indexed.
28        """
29        self.body = body
30        self.name = name
31        if not isinstance(location_offset, SourceLocation):
32            location_offset = SourceLocation._make(location_offset)
33        if location_offset.line <= 0:
34            raise ValueError(
35                "line in location_offset is 1-indexed and must be positive."
36            )
37        if location_offset.column <= 0:
38            raise ValueError(
39                "column in location_offset is 1-indexed and must be positive."
40            )
41        self.location_offset = location_offset
42
43    def get_location(self, position: int) -> SourceLocation:
44        lines = self.body[:position].splitlines()
45        if lines:
46            line = len(lines)
47            column = len(lines[-1]) + 1
48        else:
49            line = 1
50            column = 1
51        return SourceLocation(line, column)
52
53    def __repr__(self) -> str:
54        return f"<{self.__class__.__name__} name={self.name!r}>"
55
56    def __eq__(self, other: Any) -> bool:
57        return (isinstance(other, Source) and other.body == self.body) or (
58            isinstance(other, str) and other == self.body
59        )
60
61    def __ne__(self, other: Any) -> bool:
62        return not self == other
63
64
65def is_source(source: Any) -> bool:
66    """Test if the given value is a Source object.
67
68    For internal use only.
69    """
70    return isinstance(source, Source)
71