pyspiral 0.8.9__cp311-abi3-macosx_11_0_arm64.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.
Files changed (114) hide show
  1. pyspiral-0.8.9.dist-info/METADATA +53 -0
  2. pyspiral-0.8.9.dist-info/RECORD +114 -0
  3. pyspiral-0.8.9.dist-info/WHEEL +4 -0
  4. pyspiral-0.8.9.dist-info/entry_points.txt +3 -0
  5. spiral/__init__.py +55 -0
  6. spiral/_lib.abi3.so +0 -0
  7. spiral/adbc.py +411 -0
  8. spiral/api/__init__.py +78 -0
  9. spiral/api/admin.py +15 -0
  10. spiral/api/client.py +165 -0
  11. spiral/api/filesystems.py +152 -0
  12. spiral/api/key_space_indexes.py +23 -0
  13. spiral/api/organizations.py +78 -0
  14. spiral/api/projects.py +219 -0
  15. spiral/api/telemetry.py +19 -0
  16. spiral/api/text_indexes.py +56 -0
  17. spiral/api/types.py +23 -0
  18. spiral/api/workers.py +40 -0
  19. spiral/api/workloads.py +52 -0
  20. spiral/arrow_.py +202 -0
  21. spiral/cli/__init__.py +89 -0
  22. spiral/cli/__main__.py +4 -0
  23. spiral/cli/admin.py +33 -0
  24. spiral/cli/app.py +108 -0
  25. spiral/cli/console.py +95 -0
  26. spiral/cli/fs.py +109 -0
  27. spiral/cli/iceberg.py +97 -0
  28. spiral/cli/key_spaces.py +103 -0
  29. spiral/cli/login.py +25 -0
  30. spiral/cli/orgs.py +81 -0
  31. spiral/cli/printer.py +53 -0
  32. spiral/cli/projects.py +148 -0
  33. spiral/cli/state.py +7 -0
  34. spiral/cli/tables.py +225 -0
  35. spiral/cli/telemetry.py +17 -0
  36. spiral/cli/text.py +115 -0
  37. spiral/cli/types.py +50 -0
  38. spiral/cli/workloads.py +86 -0
  39. spiral/client.py +279 -0
  40. spiral/core/__init__.pyi +0 -0
  41. spiral/core/_tools/__init__.pyi +5 -0
  42. spiral/core/authn/__init__.pyi +21 -0
  43. spiral/core/client/__init__.pyi +270 -0
  44. spiral/core/config/__init__.pyi +35 -0
  45. spiral/core/expr/__init__.pyi +15 -0
  46. spiral/core/expr/images/__init__.pyi +3 -0
  47. spiral/core/expr/list_/__init__.pyi +4 -0
  48. spiral/core/expr/pushdown/__init__.pyi +3 -0
  49. spiral/core/expr/refs/__init__.pyi +4 -0
  50. spiral/core/expr/s3/__init__.pyi +3 -0
  51. spiral/core/expr/str_/__init__.pyi +3 -0
  52. spiral/core/expr/struct_/__init__.pyi +6 -0
  53. spiral/core/expr/text/__init__.pyi +5 -0
  54. spiral/core/expr/udf/__init__.pyi +14 -0
  55. spiral/core/expr/video/__init__.pyi +3 -0
  56. spiral/core/table/__init__.pyi +142 -0
  57. spiral/core/table/manifests/__init__.pyi +35 -0
  58. spiral/core/table/metastore/__init__.pyi +58 -0
  59. spiral/core/table/spec/__init__.pyi +214 -0
  60. spiral/dataloader.py +310 -0
  61. spiral/dataset.py +264 -0
  62. spiral/datetime_.py +27 -0
  63. spiral/debug/__init__.py +0 -0
  64. spiral/debug/manifests.py +103 -0
  65. spiral/debug/metrics.py +56 -0
  66. spiral/debug/scan.py +266 -0
  67. spiral/demo.py +100 -0
  68. spiral/enrichment.py +290 -0
  69. spiral/expressions/__init__.py +274 -0
  70. spiral/expressions/base.py +186 -0
  71. spiral/expressions/file.py +17 -0
  72. spiral/expressions/http.py +17 -0
  73. spiral/expressions/list_.py +77 -0
  74. spiral/expressions/pushdown.py +12 -0
  75. spiral/expressions/s3.py +16 -0
  76. spiral/expressions/str_.py +39 -0
  77. spiral/expressions/struct.py +59 -0
  78. spiral/expressions/text.py +62 -0
  79. spiral/expressions/tiff.py +225 -0
  80. spiral/expressions/udf.py +66 -0
  81. spiral/grpc_.py +32 -0
  82. spiral/iceberg.py +31 -0
  83. spiral/iterable_dataset.py +106 -0
  84. spiral/key_space_index.py +44 -0
  85. spiral/project.py +247 -0
  86. spiral/protogen/_/__init__.py +0 -0
  87. spiral/protogen/_/arrow/__init__.py +0 -0
  88. spiral/protogen/_/arrow/flight/__init__.py +0 -0
  89. spiral/protogen/_/arrow/flight/protocol/__init__.py +0 -0
  90. spiral/protogen/_/arrow/flight/protocol/sql/__init__.py +2548 -0
  91. spiral/protogen/_/google/__init__.py +0 -0
  92. spiral/protogen/_/google/protobuf/__init__.py +2310 -0
  93. spiral/protogen/_/message_pool.py +3 -0
  94. spiral/protogen/_/py.typed +0 -0
  95. spiral/protogen/_/scandal/__init__.py +190 -0
  96. spiral/protogen/_/spfs/__init__.py +72 -0
  97. spiral/protogen/_/spql/__init__.py +61 -0
  98. spiral/protogen/_/substrait/__init__.py +6196 -0
  99. spiral/protogen/_/substrait/extensions/__init__.py +169 -0
  100. spiral/protogen/__init__.py +0 -0
  101. spiral/protogen/util.py +41 -0
  102. spiral/py.typed +0 -0
  103. spiral/scan.py +383 -0
  104. spiral/server.py +37 -0
  105. spiral/settings.py +36 -0
  106. spiral/snapshot.py +61 -0
  107. spiral/streaming_/__init__.py +3 -0
  108. spiral/streaming_/reader.py +133 -0
  109. spiral/streaming_/stream.py +156 -0
  110. spiral/substrait_.py +274 -0
  111. spiral/table.py +216 -0
  112. spiral/text_index.py +17 -0
  113. spiral/transaction.py +156 -0
  114. spiral/types_.py +6 -0
@@ -0,0 +1,186 @@
1
+ import datetime
2
+ from typing import TypeAlias, Union
3
+
4
+ import pyarrow as pa
5
+
6
+ from spiral import _lib
7
+
8
+ NativeExpr: TypeAlias = _lib.expr.Expr
9
+
10
+
11
+ class Expr:
12
+ """Base class for Spiral expressions. All expressions support comparison and basic arithmetic operations."""
13
+
14
+ def __init__(self, native: NativeExpr) -> None:
15
+ if not isinstance(native, NativeExpr):
16
+ raise TypeError(f"Expected a native expression, got {type(native)}")
17
+ self._native = native
18
+
19
+ @property
20
+ def __expr__(self) -> NativeExpr:
21
+ return self._native
22
+
23
+ def __str__(self):
24
+ return str(self.__expr__)
25
+
26
+ def __repr__(self):
27
+ return repr(self.__expr__)
28
+
29
+ def __getitem__(self, item: str | int | list[str]) -> "Expr":
30
+ """
31
+ Get an item from a struct or list.
32
+
33
+ Args:
34
+ item: The key or index to get.
35
+ If item is a string, it is assumed to be a field in a struct. Dot-separated string is supported
36
+ to access nested fields.
37
+ If item is a list of strings, it is assumed to be a list of fields in a struct.
38
+ If item is an integer, it is assumed to be an index in a list.
39
+ """
40
+ from spiral import expressions as se
41
+
42
+ expr = self
43
+
44
+ if isinstance(item, int):
45
+ # Assume list and get an element.
46
+ expr = se.list_.element_at(expr, item)
47
+ elif isinstance(item, str):
48
+ if item == "*":
49
+ # Select all fields when "*" is used in getitem.
50
+ # getitem("*") is not a valid expression, so we translate it to select(all).
51
+ expr = se.select(expr, names=[item])
52
+ else:
53
+ # Walk into the struct.
54
+ for part in item.split("."):
55
+ expr = se.getitem(expr, part)
56
+ elif isinstance(item, list) and all(isinstance(i, str) for i in item):
57
+ expr = expr.select(*item)
58
+ else:
59
+ raise TypeError(f"Invalid item type: {type(item)}")
60
+
61
+ return expr
62
+
63
+ def __eq__(self, other: "ExprLike") -> "Expr":
64
+ return self._binary("eq", other)
65
+
66
+ def __ne__(self, other: "ExprLike") -> "Expr":
67
+ return self._binary("neq", other)
68
+
69
+ def __lt__(self, other: "ExprLike") -> "Expr":
70
+ return self._binary("lt", other)
71
+
72
+ def __le__(self, other: "ExprLike") -> "Expr":
73
+ return self._binary("lte", other)
74
+
75
+ def __gt__(self, other: "ExprLike") -> "Expr":
76
+ return self._binary("gt", other)
77
+
78
+ def __ge__(self, other: "ExprLike") -> "Expr":
79
+ return self._binary("gte", other)
80
+
81
+ def __and__(self, other: "ExprLike") -> "Expr":
82
+ return self._binary("and", other)
83
+
84
+ def __or__(self, other: "ExprLike") -> "Expr":
85
+ return self._binary("or", other)
86
+
87
+ def __xor__(self, other: "ExprLike") -> "Expr":
88
+ raise NotImplementedError
89
+
90
+ def __add__(self, other: "ExprLike") -> "Expr":
91
+ return self._binary("add", other)
92
+
93
+ def __sub__(self, other: "ExprLike") -> "Expr":
94
+ return self._binary("sub", other)
95
+
96
+ def __mul__(self, other: "ExprLike") -> "Expr":
97
+ return self._binary("mul", other)
98
+
99
+ def __truediv__(self, other: "ExprLike") -> "Expr":
100
+ return self._binary("div", other)
101
+
102
+ def __mod__(self, other: "ExprLike") -> "Expr":
103
+ return self._binary("mod", other)
104
+
105
+ def __neg__(self):
106
+ return Expr(_lib.expr.unary("neg", self.__expr__))
107
+
108
+ def in_(self, other: "ExprLike") -> "Expr":
109
+ from spiral import expressions as se
110
+
111
+ other = se.lift(other)
112
+ return Expr(_lib.expr.list.contains(other.__expr__, self.__expr__))
113
+
114
+ def contains(self, other: "ExprLike") -> "Expr":
115
+ from spiral import expressions as se
116
+
117
+ return se.lift(other).in_(self)
118
+
119
+ def cast(self, dtype: pa.DataType) -> "Expr":
120
+ """Cast the expression result to a different data type."""
121
+ return Expr(_lib.expr.cast(self.__expr__, dtype))
122
+
123
+ def select(self, *paths: str, exclude: list[str] = None) -> "Expr":
124
+ """Select fields from a struct-like expression.
125
+
126
+ Args:
127
+ *paths: Field names to select. If a path contains a dot, it is assumed to be a nested struct field.
128
+ exclude: List of field names to exclude from result.
129
+ """
130
+ from spiral import expressions as se
131
+
132
+ if paths:
133
+ if exclude:
134
+ raise ValueError("Cannot specify both selection and exclusion.")
135
+
136
+ # If any of the paths contain nested fields, then we re-pack nested select statements.
137
+ if any("." in p for p in paths):
138
+ fields = {}
139
+ for p in paths:
140
+ if p == "*":
141
+ # This is handled later.
142
+ continue
143
+
144
+ if "." not in p:
145
+ fields[p] = self[p]
146
+ continue
147
+
148
+ parent, child = p.split(".", 1)
149
+ node = self[parent].select(child)
150
+ if parent in fields:
151
+ fields[parent] = se.merge(fields[parent], node)
152
+ else:
153
+ fields[parent] = node
154
+
155
+ packed = se.pack(fields)
156
+ if "*" in paths:
157
+ packed = se.merge(self.select("*"), packed)
158
+
159
+ return packed
160
+
161
+ return se.select(self, names=list(paths))
162
+
163
+ if exclude:
164
+ if any("." in p for p in exclude):
165
+ raise ValueError("Exclusion of nested fields is not supported yet.")
166
+ return se.select(self, exclude=exclude)
167
+
168
+ return self
169
+
170
+ def _binary(self, op: str, rhs: "ExprLike") -> "Expr":
171
+ """Create a comparison expression."""
172
+ from spiral import expressions as se
173
+
174
+ rhs = se.lift(rhs)
175
+ return Expr(_lib.expr.binary(op, self.__expr__, rhs.__expr__))
176
+
177
+
178
+ ScalarLike: TypeAlias = bool | int | float | str | list["ScalarLike"] | datetime.datetime | None
179
+ ArrowLike: TypeAlias = Union[
180
+ pa.RecordBatch,
181
+ "pa.Array[pa.Scalar[pa.DataType]]",
182
+ "pa.ChunkedArray[pa.Scalar[pa.DataType]]",
183
+ "pa.Scalar[pa.DataType]",
184
+ pa.Table,
185
+ ]
186
+ ExprLike: TypeAlias = Expr | dict[str, "ExprLike"] | list["ExprLike"] | ArrowLike | ScalarLike
@@ -0,0 +1,17 @@
1
+ from spiral import _lib
2
+ from spiral.expressions.base import Expr, ExprLike
3
+
4
+
5
+ def get(expr: ExprLike, abort_on_error: bool = False) -> Expr:
6
+ """Read data from the local filesystem by the file:// URL.
7
+
8
+ Args:
9
+ expr: URLs of the data that needs to be read.
10
+ abort_on_error: Should the expression abort on errors or just collect them.
11
+ """
12
+ from spiral import expressions as se
13
+
14
+ expr = se.lift(expr)
15
+
16
+ # This just works :)
17
+ return Expr(_lib.expr.s3.get(expr.__expr__, abort_on_error))
@@ -0,0 +1,17 @@
1
+ from spiral import _lib
2
+ from spiral.expressions.base import Expr, ExprLike
3
+
4
+
5
+ def get(expr: ExprLike, abort_on_error: bool = False) -> Expr:
6
+ """Read data from the URL.
7
+
8
+ Args:
9
+ expr: URLs of the data that needs to be read.
10
+ abort_on_error: Should the expression abort on errors or just collect them.
11
+ """
12
+ from spiral import expressions as se
13
+
14
+ expr = se.lift(expr)
15
+
16
+ # This just works :)
17
+ return Expr(_lib.expr.s3.get(expr.__expr__, abort_on_error))
@@ -0,0 +1,77 @@
1
+ from spiral import _lib
2
+ from spiral.expressions.base import Expr, ExprLike
3
+
4
+
5
+ def in_(expr: ExprLike, values: ExprLike) -> Expr:
6
+ """Check if a value is in a list.
7
+
8
+ Args:
9
+ expr: The value to check.
10
+ values: The list array expression to check against.
11
+ """
12
+ # `se.list.in_(Array[2, 4], Array[[1, 2], [1, 2]]) -> Array[True, False]`
13
+ from spiral.expressions import lift
14
+
15
+ expr = lift(expr)
16
+ return expr.in_(values)
17
+
18
+
19
+ def element_at(expr: ExprLike, index: ExprLike) -> Expr:
20
+ """Get the element at the given index.
21
+
22
+ Args:
23
+ expr: The list array expression.
24
+ index: The index to get.
25
+ """
26
+ # e.g. `se.list.element_at([1, 2, 3], 1) -> 2`
27
+ ...
28
+ from spiral import _lib
29
+ from spiral.expressions import lift
30
+
31
+ expr = lift(expr)
32
+ index = lift(index)
33
+ return Expr(_lib.expr.list.element_at(expr.__expr__, index.__expr__))
34
+
35
+
36
+ def of(*expr: ExprLike) -> Expr:
37
+ # Creates an array or scalar list from a series of expressions, all values must be of the same type.
38
+ # The expressions must all also have the same length (1 for scalars).
39
+ #
40
+ # e.g. `se.list.of(1+3, 2, 3) -> [4, 2, 3]`
41
+ ...
42
+
43
+
44
+ def zip(*lists: ExprLike) -> Expr:
45
+ # Merge the given lists, with duplicates.
46
+ #
47
+ # e.g. `se.list.merge([1, 2], [3, 4]) -> [(1, 2), (3, 4)]`
48
+ ...
49
+
50
+
51
+ def concat(*lists: ExprLike) -> Expr:
52
+ # Concatenate the given lists. The types of all the lists must be the same.
53
+ #
54
+ # e.g. `se.list.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]`
55
+ ...
56
+
57
+
58
+ def slice_(expr: ExprLike, start: int | None = None, stop: int | None = None) -> Expr:
59
+ # Slice a list.
60
+ #
61
+ # e.g. `se.list.slice_([0, 1, 2], slice(0,2)) -> [0, 1]`
62
+ ...
63
+
64
+
65
+ def length(expr: ExprLike) -> Expr:
66
+ # Get the length of a list.
67
+ #
68
+ # e.g. `se.list.length([1, 2, 3]) -> 3`
69
+ ...
70
+
71
+
72
+ def contains(expr: ExprLike, value: ExprLike) -> bool:
73
+ from spiral import expressions as se
74
+
75
+ expr = se.lift(expr)
76
+ value = se.lift(value)
77
+ return _lib.expr.list.contains(expr.__expr__, value.__expr__)
@@ -0,0 +1,12 @@
1
+ from spiral import _lib
2
+ from spiral.expressions.base import Expr, ExprLike
3
+
4
+
5
+ def expensive(expr: ExprLike) -> Expr:
6
+ """Minimise the chance of evaluation the expression over the old version of data,
7
+ applying it as late as possible."""
8
+ from spiral import expressions as se
9
+
10
+ expr = se.lift(expr)
11
+
12
+ return Expr(_lib.expr.pushdown.expensive(expr.__expr__))
@@ -0,0 +1,16 @@
1
+ from spiral import _lib
2
+ from spiral.expressions.base import Expr, ExprLike
3
+
4
+
5
+ def get(expr: ExprLike, abort_on_error: bool = False) -> Expr:
6
+ """Read data from object storage by the s3:// URL.
7
+
8
+ Args:
9
+ expr: URLs of the data that needs to be read from object storage.
10
+ abort_on_error: Should the expression abort on errors or just collect them.
11
+ """
12
+ from spiral import expressions as se
13
+
14
+ expr = se.lift(expr)
15
+
16
+ return Expr(_lib.expr.s3.get(expr.__expr__, abort_on_error))
@@ -0,0 +1,39 @@
1
+ import pyarrow as pa
2
+ import pyarrow.compute as pc
3
+ import re2 as re
4
+
5
+ from spiral import _lib
6
+ from spiral.expressions.base import Expr, ExprLike
7
+
8
+ # TODO(ngates): we can add a symmetric "ascii" expression namespace in the future if
9
+ # the performance is required.
10
+
11
+
12
+ def substr(expr: ExprLike = None, *, begin: int = 0, end: int | None = None) -> Expr:
13
+ """Slice a string.
14
+
15
+ Args:
16
+ expr: The string expression to slice.
17
+ begin: The starting index of the slice.
18
+ end: The ending index of the slice.
19
+ """
20
+ from spiral import expressions as se
21
+
22
+ expr = se.lift(expr)
23
+ return Expr(_lib.spql.str.substr(expr.__expr__, begin=begin, end=end))
24
+
25
+
26
+ def extract_regex(pattern: str, *, strings: ExprLike) -> Expr:
27
+ # Extract the first occurrence of a regex pattern from a string.
28
+ raise NotImplementedError
29
+
30
+
31
+ def _extract_regex(arg: pa.Array | pa.Scalar, pattern: str) -> pa.Array | pa.Scalar:
32
+ # Compute the return type based on the regex groups
33
+ m = re.compile(pattern)
34
+ dtype = pa.struct([pa.field(k, type=pa.string()) for k in m.groupindex.keys()])
35
+
36
+ if pa.types.is_string(arg.type):
37
+ return pc.extract_regex(arg, pattern=pattern).cast(dtype)
38
+
39
+ raise TypeError("Input argument does not have the expected type")
@@ -0,0 +1,59 @@
1
+ from spiral import _lib
2
+ from spiral.expressions.base import Expr, ExprLike
3
+
4
+
5
+ def getitem(expr: ExprLike, field: str) -> Expr:
6
+ """Get field from a struct.
7
+
8
+ Args:
9
+ expr: The struct expression to get the field from.
10
+ field: The field to get. Dot-separated string is supported to access nested fields.
11
+ """
12
+ from spiral import expressions as se
13
+
14
+ expr = se.lift(expr)
15
+ return Expr(_lib.expr.struct.getitem(expr.__expr__, field))
16
+
17
+
18
+ def pack(fields: dict[str, ExprLike], *, nullable: bool = False) -> Expr:
19
+ """Assemble a new struct from the given named fields.
20
+
21
+ Args:
22
+ fields: A dictionary of field names to expressions. The field names will be used as the struct field names.
23
+ """
24
+ from spiral import expressions as se
25
+
26
+ return Expr(
27
+ _lib.expr.struct.pack(list(fields.keys()), [se.lift(expr).__expr__ for expr in fields.values()], nullable)
28
+ )
29
+
30
+
31
+ def merge(*structs: "ExprLike") -> Expr:
32
+ """Merge fields from the given structs into a single struct.
33
+
34
+ Args:
35
+ *structs: Each expression must evaluate to a struct.
36
+
37
+ Returns:
38
+ A single struct containing all the fields from the input structs.
39
+ If a field is present in multiple structs, the value from the last struct is used.
40
+ """
41
+ from spiral import expressions as se
42
+
43
+ if len(structs) == 1:
44
+ return se.lift(structs[0])
45
+ return Expr(_lib.expr.struct.merge([se.lift(struct).__expr__ for struct in structs]))
46
+
47
+
48
+ def select(expr: ExprLike, names: list[str] = None, exclude: list[str] = None) -> Expr:
49
+ """Select fields from a struct.
50
+
51
+ Args:
52
+ expr: The struct-like expression to select fields from.
53
+ names: Field names to select. If a path contains a dot, it is assumed to be a nested struct field.
54
+ exclude: List of field names to exclude from result. Exactly one of `names` or `exclude` must be provided.
55
+ """
56
+ from spiral import expressions as se
57
+
58
+ expr = se.lift(expr)
59
+ return Expr(_lib.expr.struct.select(expr.__expr__, names, exclude))
@@ -0,0 +1,62 @@
1
+ from spiral.expressions.base import Expr, ExprLike
2
+
3
+
4
+ def field(expr: ExprLike, field_name: str | None = None, tokenizer: str | None = None) -> Expr:
5
+ """Configure a column for text indexing.
6
+
7
+ Args:
8
+ expr: An input column. The expression must either evaluate to a UTF-8,
9
+ or, if a `field_name` is provided, to a struct with a field of that name.
10
+ field_name: If provided, the expression must evaluate to a struct with a field of that name.
11
+ The given field will be indexed.
12
+ tokenizer: If provided, the text will be tokenized using the given tokenizer.
13
+
14
+ Returns:
15
+ An expression that can be used to construct a text index.
16
+ """
17
+ from spiral import _lib
18
+ from spiral.expressions import getitem, lift, merge, pack
19
+
20
+ expr = lift(expr)
21
+ if field_name is None:
22
+ return Expr(_lib.expr.text.field(expr.__expr__, tokenizer))
23
+
24
+ child = _lib.expr.text.field(getitem(expr, field_name).__expr__)
25
+ return merge(
26
+ expr,
27
+ pack({field_name: child}),
28
+ )
29
+
30
+
31
+ def find(expr: ExprLike, term: str) -> Expr:
32
+ """Search for a term in the text.
33
+
34
+ Args:
35
+ expr: An index field.
36
+ term: The term to search for.
37
+
38
+ Returns:
39
+ An expression that can be used in ranking for text search.
40
+ """
41
+ from spiral import _lib
42
+ from spiral.expressions import lift
43
+
44
+ expr = lift(expr)
45
+ return Expr(_lib.expr.text.find(expr.__expr__, term))
46
+
47
+
48
+ def boost(expr: ExprLike, factor: float) -> Expr:
49
+ """Boost the relevance of a ranking expression.
50
+
51
+ Args:
52
+ expr: Rank by expression.
53
+ factor: The factor by which to boost the relevance.
54
+
55
+ Returns:
56
+ An expression that can be used in ranking for text search.
57
+ """
58
+ from spiral import _lib
59
+ from spiral.expressions import lift
60
+
61
+ expr = lift(expr)
62
+ return Expr(_lib.expr.text.boost(expr.__expr__, factor))