trustfall 0.3.1__cp315-cp315-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trustfall/__init__.py +41 -0
- trustfall/_internals.py +27 -0
- trustfall/_internals.pyi +20 -0
- trustfall/adapter.py +178 -0
- trustfall/execution.py +20 -0
- trustfall/py.typed +0 -0
- trustfall/tests/__init__.py +0 -0
- trustfall/tests/numbers_adapter.py +105 -0
- trustfall/tests/test_execution.py +501 -0
- trustfall/tests/test_schema.py +39 -0
- trustfall/trustfall.cp315-win_amd64.pyd +0 -0
- trustfall-0.3.1.dist-info/METADATA +97 -0
- trustfall-0.3.1.dist-info/RECORD +15 -0
- trustfall-0.3.1.dist-info/WHEEL +4 -0
- trustfall-0.3.1.dist-info/sboms/pytrustfall.cyclonedx.json +1531 -0
trustfall/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Error types:
|
|
2
|
+
# - ParseError, when the provided input doesn't even parse as valid syntax
|
|
3
|
+
# - ValidationError, when the input is syntactically valid but doesn't match the schema
|
|
4
|
+
# - FrontendError, when the input's use of the schema is valid but the operations it attempts
|
|
5
|
+
# are not supported or not valid
|
|
6
|
+
# - InvalidIRQueryError should in principle never be seen in Python. It indicates that
|
|
7
|
+
# the internal representation could not be converted to its "indexed" (i.e. execution-ready) form.
|
|
8
|
+
# - InvalidSchemaError, when the provided schema cannot be used due to violating some necessary
|
|
9
|
+
# precondition as explained in the error message.
|
|
10
|
+
# - QueryArgumentsError, if the query by itself is fine but cannot be executed together with
|
|
11
|
+
# the provided arguments.
|
|
12
|
+
from ._internals import (
|
|
13
|
+
FrontendError,
|
|
14
|
+
InvalidIRQueryError,
|
|
15
|
+
InvalidSchemaError,
|
|
16
|
+
ParseError,
|
|
17
|
+
QueryArgumentsError,
|
|
18
|
+
Schema,
|
|
19
|
+
ValidationError,
|
|
20
|
+
)
|
|
21
|
+
from .adapter import Adapter, Context, FieldValue
|
|
22
|
+
from .execution import execute_query
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
# from .adapter
|
|
26
|
+
"Adapter",
|
|
27
|
+
"Context",
|
|
28
|
+
"FieldValue",
|
|
29
|
+
#
|
|
30
|
+
# from .execution
|
|
31
|
+
"execute_query",
|
|
32
|
+
#
|
|
33
|
+
# from ._internals (defined in Rust)
|
|
34
|
+
"FrontendError",
|
|
35
|
+
"InvalidIRQueryError",
|
|
36
|
+
"InvalidSchemaError",
|
|
37
|
+
"ParseError",
|
|
38
|
+
"QueryArgumentsError",
|
|
39
|
+
"Schema",
|
|
40
|
+
"ValidationError",
|
|
41
|
+
]
|
trustfall/_internals.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from . import trustfall
|
|
2
|
+
|
|
3
|
+
# Workaround around issue preventing direct `from ._trustfall_internal import X` imports:
|
|
4
|
+
# https://github.com/PyO3/pyo3/issues/759
|
|
5
|
+
# https://github.com/PyO3/pyo3/issues/1517
|
|
6
|
+
_trustfall_internal = trustfall._trustfall_internal
|
|
7
|
+
AdapterShim = _trustfall_internal.AdapterShim
|
|
8
|
+
Schema = _trustfall_internal.Schema
|
|
9
|
+
FrontendError = _trustfall_internal.FrontendError
|
|
10
|
+
InvalidIRQueryError = _trustfall_internal.InvalidIRQueryError
|
|
11
|
+
InvalidSchemaError = _trustfall_internal.InvalidSchemaError
|
|
12
|
+
ParseError = _trustfall_internal.ParseError
|
|
13
|
+
QueryArgumentsError = _trustfall_internal.QueryArgumentsError
|
|
14
|
+
ValidationError = _trustfall_internal.ValidationError
|
|
15
|
+
interpret_query = _trustfall_internal.interpret_query
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"AdapterShim",
|
|
19
|
+
"FrontendError",
|
|
20
|
+
"InvalidIRQueryError",
|
|
21
|
+
"InvalidSchemaError",
|
|
22
|
+
"ParseError",
|
|
23
|
+
"QueryArgumentsError",
|
|
24
|
+
"Schema",
|
|
25
|
+
"ValidationError",
|
|
26
|
+
"interpret_query",
|
|
27
|
+
]
|
trustfall/_internals.pyi
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Any, Dict, Iterator, Mapping
|
|
2
|
+
|
|
3
|
+
from .adapter import Adapter
|
|
4
|
+
|
|
5
|
+
class AdapterShim:
|
|
6
|
+
def __init__(self, adapter: Adapter[Any]) -> None: ...
|
|
7
|
+
|
|
8
|
+
class FrontendError(Exception): ...
|
|
9
|
+
class InvalidIRQueryError(Exception): ...
|
|
10
|
+
class InvalidSchemaError(Exception): ...
|
|
11
|
+
class ParseError(Exception): ...
|
|
12
|
+
class QueryArgumentsError(Exception): ...
|
|
13
|
+
class ValidationError(Exception): ...
|
|
14
|
+
|
|
15
|
+
class Schema:
|
|
16
|
+
def __init__(self, schema_txt: str) -> None: ...
|
|
17
|
+
|
|
18
|
+
def interpret_query(
|
|
19
|
+
adapter: AdapterShim, schema: Schema, query: str, arguments: Mapping[str, Any]
|
|
20
|
+
) -> Iterator[Dict[str, Any]]: ...
|
trustfall/adapter.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
|
2
|
+
from typing import (
|
|
3
|
+
Any,
|
|
4
|
+
Generic,
|
|
5
|
+
Iterable,
|
|
6
|
+
Iterator,
|
|
7
|
+
Mapping,
|
|
8
|
+
Optional,
|
|
9
|
+
Tuple,
|
|
10
|
+
TypeVar,
|
|
11
|
+
Union,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
FieldValue = Union[None, str, int, float, bool, list["FieldValue"]]
|
|
15
|
+
"""
|
|
16
|
+
The type of property values, query arguments, and columns in the result.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Vertex = TypeVar("Vertex")
|
|
21
|
+
"""
|
|
22
|
+
The type of vertices in the dataset implemented by an adapter.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Context(Generic[Vertex]):
|
|
27
|
+
"""Adapter helper type. Its active_vertex property indicates vertices whose data is needed."""
|
|
28
|
+
|
|
29
|
+
__slots__ = ("_active_vertex",)
|
|
30
|
+
|
|
31
|
+
_active_vertex: Optional[Vertex]
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def active_vertex(self) -> Optional[Vertex]:
|
|
35
|
+
"""The vertex whose information (properties, edges, etc.) needs to be resolved."""
|
|
36
|
+
return self._active_vertex
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Adapter(Generic[Vertex], metaclass=ABCMeta):
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def resolve_starting_vertices(
|
|
42
|
+
self,
|
|
43
|
+
edge_name: str,
|
|
44
|
+
parameters: Mapping[str, FieldValue],
|
|
45
|
+
/,
|
|
46
|
+
*args: Any,
|
|
47
|
+
**kwargs: Any,
|
|
48
|
+
) -> Iterable[Vertex]:
|
|
49
|
+
"""Produce an iterable of vertices for the specified starting edge.
|
|
50
|
+
|
|
51
|
+
Starting edges are ones where queries are allowed to begin.
|
|
52
|
+
They are defined directly on the root query type of the schema.
|
|
53
|
+
For example, `Foo` is the starting edge of the following query:
|
|
54
|
+
```graphql
|
|
55
|
+
query {
|
|
56
|
+
Foo {
|
|
57
|
+
bar @output
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The caller guarantees that:
|
|
63
|
+
- The specified edge is a starting edge in the schema being queried.
|
|
64
|
+
- Any parameters the edge requires per the schema have values provided.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def resolve_property(
|
|
69
|
+
self,
|
|
70
|
+
contexts: Iterator[Context[Vertex]],
|
|
71
|
+
type_name: str,
|
|
72
|
+
property_name: str,
|
|
73
|
+
/,
|
|
74
|
+
*args: Any,
|
|
75
|
+
**kwargs: Any,
|
|
76
|
+
) -> Iterable[Tuple[Context[Vertex], FieldValue]]:
|
|
77
|
+
"""Resolve the value of a vertex property, over an iterator of query contexts.
|
|
78
|
+
|
|
79
|
+
Each context in the `contexts` argument has an active vertex of type Optional[Vertex],
|
|
80
|
+
which is either None or represents a vertex of type `type_name` defined in the schema.
|
|
81
|
+
|
|
82
|
+
This function resolves the property value on that active vertex.
|
|
83
|
+
|
|
84
|
+
The caller guarantees that:
|
|
85
|
+
- `type_name` is a type or interface defined in the schema.
|
|
86
|
+
- `property_name` is either a property field on `type_name` defined in the schema,
|
|
87
|
+
or the special value `"__typename"` requesting the name of the vertex's type.
|
|
88
|
+
- When the active vertex is not None, then it's a vertex of type `type_name`:
|
|
89
|
+
either its type is exactly `type_name`, or `type_name` is an interface that
|
|
90
|
+
the vertex's type implements.
|
|
91
|
+
|
|
92
|
+
The returned iterable must satisfy these properties:
|
|
93
|
+
- Produce `(context, property_value)` tuples with the property's value for that context.
|
|
94
|
+
- Produce contexts in the same order as the input `contexts` iterator produced them.
|
|
95
|
+
- Produce property values whose type matches the property's type defined in the schema.
|
|
96
|
+
- When a context's active vertex is `None`, its property value is `None`.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def resolve_neighbors(
|
|
101
|
+
self,
|
|
102
|
+
contexts: Iterator[Context[Vertex]],
|
|
103
|
+
type_name: str,
|
|
104
|
+
edge_name: str,
|
|
105
|
+
parameters: Mapping[str, FieldValue],
|
|
106
|
+
/,
|
|
107
|
+
*args: Any,
|
|
108
|
+
**kwargs: Any,
|
|
109
|
+
) -> Iterable[Tuple[Context[Vertex], Iterable[Vertex]]]:
|
|
110
|
+
"""Resolve the neighboring vertices across an edge, over an iterator of query contexts.
|
|
111
|
+
|
|
112
|
+
Each context in the `contexts` argument has an active vertex of type Optional[Vertex],
|
|
113
|
+
which is either None or represents a vertex of type `type_name` defined in the schema.
|
|
114
|
+
|
|
115
|
+
This function resolves the neighboring vertices for that active vertex.
|
|
116
|
+
|
|
117
|
+
If the schema this adapter covers has no edges aside from starting edges, then
|
|
118
|
+
this method will never be called and may be implemented as `raise NotImplementedError()`.
|
|
119
|
+
|
|
120
|
+
The caller guarantees that:
|
|
121
|
+
- `type_name` is a type or interface defined in the schema.
|
|
122
|
+
- `edge_name` is an edge field on `type_name` defined in the schema.
|
|
123
|
+
- Any parameters the edge requires per the schema have values provided.
|
|
124
|
+
- When the active vertex is not None, then it's a vertex of type `type_name`:
|
|
125
|
+
either its type is exactly `type_name`, or `type_name` is an interface that
|
|
126
|
+
the vertex's type implements.
|
|
127
|
+
|
|
128
|
+
The returned iterator must satisfy these properties:
|
|
129
|
+
- Produce `(context, neighbors)` tuples with an iterator of neighbor vertices for that edge.
|
|
130
|
+
- Produce contexts in the same order as the input `contexts` iterator produced them.
|
|
131
|
+
- Each neighboring vertex is of the type specified for that edge in the schema.
|
|
132
|
+
- When a context's active vertex is None, it has an empty neighbors iterator.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
def resolve_coercion(
|
|
137
|
+
self,
|
|
138
|
+
contexts: Iterator[Context[Vertex]],
|
|
139
|
+
type_name: str,
|
|
140
|
+
coerce_to_type: str,
|
|
141
|
+
/,
|
|
142
|
+
*args: Any,
|
|
143
|
+
**kwargs: Any,
|
|
144
|
+
) -> Iterable[Tuple[Context[Vertex], bool]]:
|
|
145
|
+
"""Attempt to coerce vertices to a subtype, over an iterator of query contexts.
|
|
146
|
+
|
|
147
|
+
In this example query, the starting vertices of type `Foo` are coerced to `Bar`:
|
|
148
|
+
```graphql
|
|
149
|
+
query {
|
|
150
|
+
Foo {
|
|
151
|
+
... on Bar {
|
|
152
|
+
abc @output
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Each context in the `contexts` argument has an active vertex of type `Optional[Vertex]`,
|
|
159
|
+
which is either None or represents a vertex of type `type_name` defined in the schema.
|
|
160
|
+
|
|
161
|
+
This function checks whether the active vertex is of the specified subtype.
|
|
162
|
+
|
|
163
|
+
If this adapter's schema contains no subtyping, then no type coercions are possible:
|
|
164
|
+
this method will never be called and may be implemented as `raise NotImplementedError()`.
|
|
165
|
+
|
|
166
|
+
The caller guarantees that:
|
|
167
|
+
- `type_name` is an interface defined in the schema.
|
|
168
|
+
- `coerce_to_type` is a type or interface that implements `type_name` in the schema.
|
|
169
|
+
- When the active vertex is not None, then it's a vertex of type `type_name`:
|
|
170
|
+
either its type is exactly `type_name`, or `type_name` is an interface that
|
|
171
|
+
the vertex's type implements.
|
|
172
|
+
|
|
173
|
+
The returned iterator must satisfy these properties:
|
|
174
|
+
- Produce `(context, can_coerce)` tuples showing if the coercion succeded for that context.
|
|
175
|
+
- Produce contexts in the same order as the input `contexts` iterator produced them.
|
|
176
|
+
- Each neighboring vertex is of the type specified for that edge in the schema.
|
|
177
|
+
- When a context's active vertex is `None`, its coercion outcome is `False`.
|
|
178
|
+
"""
|
trustfall/execution.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Any, Dict, Iterator, Mapping
|
|
2
|
+
|
|
3
|
+
from ._internals import AdapterShim, Schema, interpret_query
|
|
4
|
+
from .adapter import Adapter, Vertex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def execute_query(
|
|
8
|
+
adapter: Adapter[Vertex],
|
|
9
|
+
schema: Schema,
|
|
10
|
+
query: str,
|
|
11
|
+
arguments: Mapping[str, Any],
|
|
12
|
+
/,
|
|
13
|
+
) -> Iterator[Dict[str, Any]]:
|
|
14
|
+
"""Execute the given query using the adapter, returning an iterator of result dicts."""
|
|
15
|
+
if not isinstance(adapter, Adapter):
|
|
16
|
+
raise TypeError(
|
|
17
|
+
f"Expected 'adapter' input to be a subclass of Adapter, but instead got: {adapter}"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return interpret_query(AdapterShim(adapter), schema, query, arguments)
|
trustfall/py.typed
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from typing import Any, Iterable, Iterator, Mapping, Tuple, Union, cast
|
|
2
|
+
|
|
3
|
+
from .. import Adapter, Context, FieldValue
|
|
4
|
+
|
|
5
|
+
_NUMBER_NAMES = [
|
|
6
|
+
"zero",
|
|
7
|
+
"one",
|
|
8
|
+
"two",
|
|
9
|
+
"three",
|
|
10
|
+
"four",
|
|
11
|
+
"five",
|
|
12
|
+
"six",
|
|
13
|
+
"seven",
|
|
14
|
+
"eight",
|
|
15
|
+
"nine",
|
|
16
|
+
"ten",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
Vertex = int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NumbersAdapter(Adapter[Vertex]):
|
|
23
|
+
def resolve_starting_vertices(
|
|
24
|
+
self,
|
|
25
|
+
edge_name: str,
|
|
26
|
+
parameters: Mapping[str, FieldValue],
|
|
27
|
+
/,
|
|
28
|
+
*args: Any,
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
) -> Iterable[Vertex]:
|
|
31
|
+
max_value = cast(int, parameters["max"]) # type known from schema and enforced by Trustfall
|
|
32
|
+
|
|
33
|
+
# We could just `yield from range(0, max_value)` here.
|
|
34
|
+
# But that returns an `Iterator` which is an easier type to deal with than `Iterable`,
|
|
35
|
+
# and we've had a bug with correct `Iterable` handling already.
|
|
36
|
+
# So let's return an `Iterable` instead by wrapping it in `list()`.
|
|
37
|
+
return list(range(0, max_value))
|
|
38
|
+
|
|
39
|
+
def resolve_property(
|
|
40
|
+
self,
|
|
41
|
+
contexts: Iterator[Context[Vertex]],
|
|
42
|
+
type_name: str,
|
|
43
|
+
property_name: str,
|
|
44
|
+
/,
|
|
45
|
+
*args: Any,
|
|
46
|
+
**kwargs: Any,
|
|
47
|
+
) -> Iterable[Tuple[Context[Vertex], Any]]:
|
|
48
|
+
for context in contexts:
|
|
49
|
+
active_vertex = context.active_vertex
|
|
50
|
+
value: Union[int, str, None] = None
|
|
51
|
+
if active_vertex is not None:
|
|
52
|
+
if property_name == "value":
|
|
53
|
+
value = active_vertex
|
|
54
|
+
elif property_name == "name":
|
|
55
|
+
if 0 <= active_vertex < len(_NUMBER_NAMES):
|
|
56
|
+
value = _NUMBER_NAMES[active_vertex]
|
|
57
|
+
else:
|
|
58
|
+
raise NotImplementedError()
|
|
59
|
+
|
|
60
|
+
yield (context, value)
|
|
61
|
+
|
|
62
|
+
def resolve_neighbors(
|
|
63
|
+
self,
|
|
64
|
+
contexts: Iterator[Context[Vertex]],
|
|
65
|
+
type_name: str,
|
|
66
|
+
edge_name: str,
|
|
67
|
+
parameters: Mapping[str, FieldValue],
|
|
68
|
+
/,
|
|
69
|
+
*args: Any,
|
|
70
|
+
**kwargs: Any,
|
|
71
|
+
) -> Iterable[Tuple[Context[Vertex], Iterable[Vertex]]]:
|
|
72
|
+
for context in contexts:
|
|
73
|
+
active_vertex = context.active_vertex
|
|
74
|
+
neighbors: Iterable[Vertex] = []
|
|
75
|
+
if active_vertex is not None:
|
|
76
|
+
if edge_name == "multiple":
|
|
77
|
+
if active_vertex > 0:
|
|
78
|
+
max_value = cast(
|
|
79
|
+
int, parameters["max"]
|
|
80
|
+
) # type known from schema and enforced by Trustfall
|
|
81
|
+
neighbors = range(
|
|
82
|
+
2 * active_vertex,
|
|
83
|
+
max_value * active_vertex + 1,
|
|
84
|
+
active_vertex,
|
|
85
|
+
)
|
|
86
|
+
elif edge_name == "predecessor":
|
|
87
|
+
if active_vertex > 0:
|
|
88
|
+
neighbors = [active_vertex - 1]
|
|
89
|
+
elif edge_name == "successor":
|
|
90
|
+
neighbors = [active_vertex + 1]
|
|
91
|
+
else:
|
|
92
|
+
raise NotImplementedError()
|
|
93
|
+
|
|
94
|
+
yield (context, neighbors)
|
|
95
|
+
|
|
96
|
+
def resolve_coercion(
|
|
97
|
+
self,
|
|
98
|
+
contexts: Iterator[Context[Vertex]],
|
|
99
|
+
type_name: str,
|
|
100
|
+
coerce_to_type: str,
|
|
101
|
+
/,
|
|
102
|
+
*args: Any,
|
|
103
|
+
**kwargs: Any,
|
|
104
|
+
) -> Iterable[Tuple[Context[Vertex], bool]]:
|
|
105
|
+
raise NotImplementedError()
|