rulebricks-spark 0.1.0__tar.gz
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.
- rulebricks_spark-0.1.0/.gitignore +21 -0
- rulebricks_spark-0.1.0/CHANGELOG.md +16 -0
- rulebricks_spark-0.1.0/LICENSE +21 -0
- rulebricks_spark-0.1.0/PKG-INFO +273 -0
- rulebricks_spark-0.1.0/README.md +235 -0
- rulebricks_spark-0.1.0/assets/cover.png +0 -0
- rulebricks_spark-0.1.0/examples/README.md +42 -0
- rulebricks_spark-0.1.0/examples/breach-triage/README.md +145 -0
- rulebricks_spark-0.1.0/examples/breach-triage/demo.py +280 -0
- rulebricks_spark-0.1.0/examples/sql-callable/README.md +151 -0
- rulebricks_spark-0.1.0/examples/sql-callable/demo.py +211 -0
- rulebricks_spark-0.1.0/examples/streaming/README.md +96 -0
- rulebricks_spark-0.1.0/examples/streaming/demo.py +170 -0
- rulebricks_spark-0.1.0/pyproject.toml +100 -0
- rulebricks_spark-0.1.0/src/rulebricks_spark/__init__.py +37 -0
- rulebricks_spark-0.1.0/src/rulebricks_spark/_config.py +33 -0
- rulebricks_spark-0.1.0/src/rulebricks_spark/_partition.py +418 -0
- rulebricks_spark-0.1.0/src/rulebricks_spark/_schema.py +249 -0
- rulebricks_spark-0.1.0/src/rulebricks_spark/_version.py +3 -0
- rulebricks_spark-0.1.0/src/rulebricks_spark/databricks.py +211 -0
- rulebricks_spark-0.1.0/src/rulebricks_spark/exceptions.py +26 -0
- rulebricks_spark-0.1.0/src/rulebricks_spark/solve.py +241 -0
- rulebricks_spark-0.1.0/tests/__init__.py +0 -0
- rulebricks_spark-0.1.0/tests/conftest.py +16 -0
- rulebricks_spark-0.1.0/tests/test_coerce.py +232 -0
- rulebricks_spark-0.1.0/tests/test_integration.py +266 -0
- rulebricks_spark-0.1.0/tests/test_register_udf_args.py +51 -0
- rulebricks_spark-0.1.0/tests/test_schema.py +162 -0
- rulebricks_spark-0.1.0/tests/test_solve_args.py +47 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-04-16)
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- `solve()` — apply a Rulebricks rule to every row of a Spark DataFrame.
|
|
8
|
+
- Automatic batching via `bulk_solve` (configurable, max 1000 rows/call).
|
|
9
|
+
- Intra-partition concurrency with configurable thread pool.
|
|
10
|
+
- Schema inference from rule definition, or explicit `output_schema`.
|
|
11
|
+
- Column collision handling: `on_conflict="overwrite"` (default) or `"error"`.
|
|
12
|
+
- Per-row error isolation in `_rb_error` column with `on_error="continue"`.
|
|
13
|
+
- `input_mapping` for renaming DataFrame columns to match rule fields.
|
|
14
|
+
- Exponential-backoff retries on 408/429/5xx/timeout/connection errors.
|
|
15
|
+
- `auto_repartition` for pathologically partitioned DataFrames.
|
|
16
|
+
- Works in Databricks, EMR, Synapse, Dataproc, and local PySpark.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Prefix Software Inc. (Rulebricks)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rulebricks-spark
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Apache Spark integration for Rulebricks — apply decision-table based rules to Spark DataFrames at scale.
|
|
5
|
+
Project-URL: Homepage, https://rulebricks.com
|
|
6
|
+
Project-URL: Documentation, https://rulebricks.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/rulebricks/spark
|
|
8
|
+
Project-URL: Issues, https://github.com/rulebricks/spark/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/rulebricks/spark/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Rulebricks <support@rulebricks.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: business-rules,databricks,decision-tables,pyspark,rulebricks,rules-engine,spark
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: pandas>=1.5
|
|
27
|
+
Requires-Dist: pyarrow>=10.0
|
|
28
|
+
Requires-Dist: pyspark>=3.3
|
|
29
|
+
Requires-Dist: rulebricks>=1.0.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
36
|
+
Requires-Dist: twine>=4.0; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Rulebricks Spark
|
|
40
|
+
|
|
41
|
+
[](https://pypi.org/project/rulebricks-spark/)
|
|
42
|
+
[](https://pypi.org/project/rulebricks-spark/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
Apache Spark integration for [Rulebricks](https://rulebricks.com). Apply decision-table based rules to Spark DataFrames at scale.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from rulebricks_spark import solve
|
|
49
|
+
|
|
50
|
+
scored = solve(
|
|
51
|
+
spark.table("breach_reports"),
|
|
52
|
+
rule="breach-triage",
|
|
53
|
+
api_key=dbutils.secrets.get("rulebricks", "api_key"),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
scored.display()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
Rule outputs overwrite existing columns, or automatically create new ones. Works in any PySpark environment — Databricks, EMR, Synapse, or a local cluster.
|
|
62
|
+
|
|
63
|
+
## Why
|
|
64
|
+
|
|
65
|
+
Changing business & policy logic shouldn't live in notebooks or SQL CASE statements. Rulebricks enables business users to directly author & manage decision tables in a visual UI, with versioning, audit logs, and test suites. `rulebricks-spark` then lets your Spark pipelines consume those rules at scale without wrapping the HTTP API yourself.
|
|
66
|
+
|
|
67
|
+
**Edit a rule in the Rulebricks UI. Rerun your notebook. Different results, no code change.**
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install rulebricks-spark
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Databricks
|
|
76
|
+
|
|
77
|
+
In a notebook cell:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
%pip install rulebricks-spark
|
|
81
|
+
dbutils.library.restartPython()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Or install as a cluster library (PyPI source, package `rulebricks-spark`) so every notebook on the cluster has it available.
|
|
85
|
+
|
|
86
|
+
## Quickstart
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from rulebricks_spark import solve
|
|
90
|
+
|
|
91
|
+
df = spark.table("claims_raw")
|
|
92
|
+
|
|
93
|
+
scored = solve(
|
|
94
|
+
df,
|
|
95
|
+
rule="claims-triage",
|
|
96
|
+
api_key=dbutils.secrets.get("rulebricks", "api_key"),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
scored.write.mode("overwrite").saveAsTable("claims_scored")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Rule output fields are appended as new columns. A trailing `_rb_error` column is `null` on success and carries the error message on failure, so you can isolate problem rows without failing the whole job.
|
|
103
|
+
|
|
104
|
+
## How it works
|
|
105
|
+
|
|
106
|
+
1. **Driver side:** fetch the rule's response schema once via the Rulebricks SDK, build the Spark output `StructType`.
|
|
107
|
+
2. **Executor side:** each partition's pandas chunks are split into batches (default 500 rows) and sent to `bulk_solve`. Multiple batches per partition run concurrently in a thread pool (default 4).
|
|
108
|
+
3. **Result assembly:** rule outputs are coerced to the declared Spark types, collided input columns are overwritten, errors land in `_rb_error`.
|
|
109
|
+
|
|
110
|
+
Effective parallelism = `num_executors × max_concurrent_requests_per_partition`.
|
|
111
|
+
|
|
112
|
+
## API
|
|
113
|
+
|
|
114
|
+
### `solve(df, rule, *, api_key, ...) -> DataFrame`
|
|
115
|
+
|
|
116
|
+
| Argument | Default | Description |
|
|
117
|
+
| --------------------------------------- | ------------------------ | ------------------------------------------------------------------ |
|
|
118
|
+
| `df` | — | Input Spark DataFrame. |
|
|
119
|
+
| `rule` | — | Rulebricks rule slug. |
|
|
120
|
+
| `api_key` | — | Your Rulebricks API key. |
|
|
121
|
+
| `base_url` | `https://rulebricks.com` | Override for self-hosted deployments. |
|
|
122
|
+
| `batch_size` | `500` | Rows per `bulk_solve` call (max 1000). |
|
|
123
|
+
| `max_concurrent_requests_per_partition` | `4` | Concurrent batches per Spark partition. |
|
|
124
|
+
| `input_mapping` | `None` | `{df_column: rule_field}` for renaming inputs. |
|
|
125
|
+
| `output_schema` | `None` | Explicit `StructType` to skip schema inference. |
|
|
126
|
+
| `on_conflict` | `"overwrite"` | `"error"` or `"overwrite"` when output names collide with inputs. |
|
|
127
|
+
| `on_missing_input` | `"default"` | `"error"` or `"default"` (use rule's declared defaults). |
|
|
128
|
+
| `on_error` | `"continue"` | `"raise"` to fail the job; `"continue"` to isolate in `_rb_error`. |
|
|
129
|
+
| `auto_repartition` | `False` | Repartition a single-partition DataFrame for parallelism. |
|
|
130
|
+
| `timeout_seconds` | `60.0` | Per-request timeout. |
|
|
131
|
+
| `max_retries` | `3` | Retry attempts for 408/429/5xx/timeout/reset. |
|
|
132
|
+
|
|
133
|
+
Returns a new DataFrame. Spark DataFrames are immutable — `solve()` does not mutate the input.
|
|
134
|
+
|
|
135
|
+
## Common patterns
|
|
136
|
+
|
|
137
|
+
### Column names don't match the rule's inputs
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
solve(
|
|
141
|
+
df,
|
|
142
|
+
rule="eligibility",
|
|
143
|
+
api_key=key,
|
|
144
|
+
input_mapping={
|
|
145
|
+
"fin_impact": "financial_impact",
|
|
146
|
+
"dso": "days_since_occurrence",
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Unmapped columns pass through as-is.
|
|
152
|
+
|
|
153
|
+
### Call from SQL via Unity Catalog
|
|
154
|
+
|
|
155
|
+
Register the scored DataFrame as a view, or wrap `solve()` in a Python function that your analysts can use:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
def triage_breaches(input_table: str, output_table: str):
|
|
159
|
+
df = spark.table(input_table)
|
|
160
|
+
scored = solve(df, rule="breach-triage", api_key=get_key())
|
|
161
|
+
scored.write.mode("overwrite").saveAsTable(output_table)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Self-hosted Rulebricks
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
solve(
|
|
168
|
+
df,
|
|
169
|
+
rule="underwriting",
|
|
170
|
+
api_key=key,
|
|
171
|
+
base_url="https://rules.acme.internal",
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### High-throughput tuning
|
|
176
|
+
|
|
177
|
+
For large batch jobs against a self-hosted Rulebricks instance:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
solve(
|
|
181
|
+
df.repartition(32), # match executor count
|
|
182
|
+
rule="score",
|
|
183
|
+
api_key=key,
|
|
184
|
+
batch_size=1000, # max
|
|
185
|
+
max_concurrent_requests_per_partition=8,
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Effective throughput: 32 partitions × 8 concurrent × 1000 rows = 256k rows in flight.
|
|
190
|
+
|
|
191
|
+
### Inspecting errors
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
scored = solve(df, rule="score", api_key=key, on_error="continue")
|
|
195
|
+
|
|
196
|
+
# Successful rows
|
|
197
|
+
scored.filter("_rb_error IS NULL")
|
|
198
|
+
|
|
199
|
+
# Failed rows, for investigation
|
|
200
|
+
scored.filter("_rb_error IS NOT NULL").select("report_id", "_rb_error").show(truncate=False)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Explicit output schema (typed arrays / structs)
|
|
204
|
+
|
|
205
|
+
By default, rule outputs of type `list` and `object` are serialized as JSON strings. To get typed `ArrayType` or `StructType` columns, pass an explicit `output_schema`:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from pyspark.sql.types import StructType, StructField, StringType, ArrayType, DoubleType
|
|
209
|
+
|
|
210
|
+
schema = StructType([
|
|
211
|
+
StructField("priority", StringType()),
|
|
212
|
+
StructField("tags", ArrayType(StringType())),
|
|
213
|
+
StructField("score", DoubleType()),
|
|
214
|
+
])
|
|
215
|
+
|
|
216
|
+
solve(df, rule="triage", api_key=key, output_schema=schema)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Performance notes
|
|
220
|
+
|
|
221
|
+
- **Batch size**: 500 is a reasonable default. Raise to 1000 for simple rules, and drop to 100–200 for rules involving large payloads.
|
|
222
|
+
- **Concurrency**: each executor hits the Rulebricks API in parallel. Against the managed cloud, rate and account limits may kick in before you saturate workers — watch for 429s in `_rb_error`. Against a self-hosted cluster, the autoscaler handles it. [Read more about self-hosted Rulebricks for production volumes](#self-hosted-rulebricks-for-production-volumes).
|
|
223
|
+
- **Partition count**: `solve()` inherits the input DataFrame's partitioning. If your DataFrame has one huge partition, either call `.repartition(n)` yourself or pass `auto_repartition=True`.
|
|
224
|
+
- **Schema inference cost**: a single driver-side API call at job start. Cache the schema or pass `output_schema` explicitly to skip it for repeated runs.
|
|
225
|
+
|
|
226
|
+
## Error handling
|
|
227
|
+
|
|
228
|
+
The `_rb_error` column is always present in the output.
|
|
229
|
+
|
|
230
|
+
- `on_error="continue"` (default): batch failures mark every row in the failed batch with the error string, leaving rule output columns `null`. The job completes.
|
|
231
|
+
- `on_error="raise"`: the Spark task fails on the first batch error, propagating to job failure.
|
|
232
|
+
|
|
233
|
+
Retries run automatically for 408 / 425 / 429 / 500 / 502 / 503 / 504, timeouts, and connection resets, using exponential backoff (2^attempt seconds, capped at 30).
|
|
234
|
+
|
|
235
|
+
## Requirements
|
|
236
|
+
|
|
237
|
+
- Python 3.9+
|
|
238
|
+
- PySpark 3.3+
|
|
239
|
+
- `rulebricks` SDK 1.0+
|
|
240
|
+
- `pandas` 1.5+
|
|
241
|
+
- `pyarrow` 10+
|
|
242
|
+
|
|
243
|
+
## Development
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
git clone https://github.com/rulebricks/spark
|
|
247
|
+
cd rulebricks-spark
|
|
248
|
+
pip install -e ".[dev]"
|
|
249
|
+
pytest
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Run only the fast unit tests:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
pytest -m "not requires_spark"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Run the end-to-end tests (requires PySpark and a JVM):
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
pytest -m requires_spark
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT — see [LICENSE](LICENSE).
|
|
267
|
+
|
|
268
|
+
## Links
|
|
269
|
+
|
|
270
|
+
- [Rulebricks Homepage](https://rulebricks.com)
|
|
271
|
+
- [Rulebricks User Guide](https://rulebricks.com/docs)
|
|
272
|
+
- [Python SDK reference](https://rulebricks.com/docs/api-reference#sdk/python)
|
|
273
|
+
- [Issues & feature requests](https://github.com/rulebricks/spark/issues)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Rulebricks Spark
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/rulebricks-spark/)
|
|
4
|
+
[](https://pypi.org/project/rulebricks-spark/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Apache Spark integration for [Rulebricks](https://rulebricks.com). Apply decision-table based rules to Spark DataFrames at scale.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from rulebricks_spark import solve
|
|
11
|
+
|
|
12
|
+
scored = solve(
|
|
13
|
+
spark.table("breach_reports"),
|
|
14
|
+
rule="breach-triage",
|
|
15
|
+
api_key=dbutils.secrets.get("rulebricks", "api_key"),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
scored.display()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
Rule outputs overwrite existing columns, or automatically create new ones. Works in any PySpark environment — Databricks, EMR, Synapse, or a local cluster.
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
Changing business & policy logic shouldn't live in notebooks or SQL CASE statements. Rulebricks enables business users to directly author & manage decision tables in a visual UI, with versioning, audit logs, and test suites. `rulebricks-spark` then lets your Spark pipelines consume those rules at scale without wrapping the HTTP API yourself.
|
|
28
|
+
|
|
29
|
+
**Edit a rule in the Rulebricks UI. Rerun your notebook. Different results, no code change.**
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install rulebricks-spark
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Databricks
|
|
38
|
+
|
|
39
|
+
In a notebook cell:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
%pip install rulebricks-spark
|
|
43
|
+
dbutils.library.restartPython()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or install as a cluster library (PyPI source, package `rulebricks-spark`) so every notebook on the cluster has it available.
|
|
47
|
+
|
|
48
|
+
## Quickstart
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from rulebricks_spark import solve
|
|
52
|
+
|
|
53
|
+
df = spark.table("claims_raw")
|
|
54
|
+
|
|
55
|
+
scored = solve(
|
|
56
|
+
df,
|
|
57
|
+
rule="claims-triage",
|
|
58
|
+
api_key=dbutils.secrets.get("rulebricks", "api_key"),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
scored.write.mode("overwrite").saveAsTable("claims_scored")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Rule output fields are appended as new columns. A trailing `_rb_error` column is `null` on success and carries the error message on failure, so you can isolate problem rows without failing the whole job.
|
|
65
|
+
|
|
66
|
+
## How it works
|
|
67
|
+
|
|
68
|
+
1. **Driver side:** fetch the rule's response schema once via the Rulebricks SDK, build the Spark output `StructType`.
|
|
69
|
+
2. **Executor side:** each partition's pandas chunks are split into batches (default 500 rows) and sent to `bulk_solve`. Multiple batches per partition run concurrently in a thread pool (default 4).
|
|
70
|
+
3. **Result assembly:** rule outputs are coerced to the declared Spark types, collided input columns are overwritten, errors land in `_rb_error`.
|
|
71
|
+
|
|
72
|
+
Effective parallelism = `num_executors × max_concurrent_requests_per_partition`.
|
|
73
|
+
|
|
74
|
+
## API
|
|
75
|
+
|
|
76
|
+
### `solve(df, rule, *, api_key, ...) -> DataFrame`
|
|
77
|
+
|
|
78
|
+
| Argument | Default | Description |
|
|
79
|
+
| --------------------------------------- | ------------------------ | ------------------------------------------------------------------ |
|
|
80
|
+
| `df` | — | Input Spark DataFrame. |
|
|
81
|
+
| `rule` | — | Rulebricks rule slug. |
|
|
82
|
+
| `api_key` | — | Your Rulebricks API key. |
|
|
83
|
+
| `base_url` | `https://rulebricks.com` | Override for self-hosted deployments. |
|
|
84
|
+
| `batch_size` | `500` | Rows per `bulk_solve` call (max 1000). |
|
|
85
|
+
| `max_concurrent_requests_per_partition` | `4` | Concurrent batches per Spark partition. |
|
|
86
|
+
| `input_mapping` | `None` | `{df_column: rule_field}` for renaming inputs. |
|
|
87
|
+
| `output_schema` | `None` | Explicit `StructType` to skip schema inference. |
|
|
88
|
+
| `on_conflict` | `"overwrite"` | `"error"` or `"overwrite"` when output names collide with inputs. |
|
|
89
|
+
| `on_missing_input` | `"default"` | `"error"` or `"default"` (use rule's declared defaults). |
|
|
90
|
+
| `on_error` | `"continue"` | `"raise"` to fail the job; `"continue"` to isolate in `_rb_error`. |
|
|
91
|
+
| `auto_repartition` | `False` | Repartition a single-partition DataFrame for parallelism. |
|
|
92
|
+
| `timeout_seconds` | `60.0` | Per-request timeout. |
|
|
93
|
+
| `max_retries` | `3` | Retry attempts for 408/429/5xx/timeout/reset. |
|
|
94
|
+
|
|
95
|
+
Returns a new DataFrame. Spark DataFrames are immutable — `solve()` does not mutate the input.
|
|
96
|
+
|
|
97
|
+
## Common patterns
|
|
98
|
+
|
|
99
|
+
### Column names don't match the rule's inputs
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
solve(
|
|
103
|
+
df,
|
|
104
|
+
rule="eligibility",
|
|
105
|
+
api_key=key,
|
|
106
|
+
input_mapping={
|
|
107
|
+
"fin_impact": "financial_impact",
|
|
108
|
+
"dso": "days_since_occurrence",
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Unmapped columns pass through as-is.
|
|
114
|
+
|
|
115
|
+
### Call from SQL via Unity Catalog
|
|
116
|
+
|
|
117
|
+
Register the scored DataFrame as a view, or wrap `solve()` in a Python function that your analysts can use:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
def triage_breaches(input_table: str, output_table: str):
|
|
121
|
+
df = spark.table(input_table)
|
|
122
|
+
scored = solve(df, rule="breach-triage", api_key=get_key())
|
|
123
|
+
scored.write.mode("overwrite").saveAsTable(output_table)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Self-hosted Rulebricks
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
solve(
|
|
130
|
+
df,
|
|
131
|
+
rule="underwriting",
|
|
132
|
+
api_key=key,
|
|
133
|
+
base_url="https://rules.acme.internal",
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### High-throughput tuning
|
|
138
|
+
|
|
139
|
+
For large batch jobs against a self-hosted Rulebricks instance:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
solve(
|
|
143
|
+
df.repartition(32), # match executor count
|
|
144
|
+
rule="score",
|
|
145
|
+
api_key=key,
|
|
146
|
+
batch_size=1000, # max
|
|
147
|
+
max_concurrent_requests_per_partition=8,
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Effective throughput: 32 partitions × 8 concurrent × 1000 rows = 256k rows in flight.
|
|
152
|
+
|
|
153
|
+
### Inspecting errors
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
scored = solve(df, rule="score", api_key=key, on_error="continue")
|
|
157
|
+
|
|
158
|
+
# Successful rows
|
|
159
|
+
scored.filter("_rb_error IS NULL")
|
|
160
|
+
|
|
161
|
+
# Failed rows, for investigation
|
|
162
|
+
scored.filter("_rb_error IS NOT NULL").select("report_id", "_rb_error").show(truncate=False)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Explicit output schema (typed arrays / structs)
|
|
166
|
+
|
|
167
|
+
By default, rule outputs of type `list` and `object` are serialized as JSON strings. To get typed `ArrayType` or `StructType` columns, pass an explicit `output_schema`:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from pyspark.sql.types import StructType, StructField, StringType, ArrayType, DoubleType
|
|
171
|
+
|
|
172
|
+
schema = StructType([
|
|
173
|
+
StructField("priority", StringType()),
|
|
174
|
+
StructField("tags", ArrayType(StringType())),
|
|
175
|
+
StructField("score", DoubleType()),
|
|
176
|
+
])
|
|
177
|
+
|
|
178
|
+
solve(df, rule="triage", api_key=key, output_schema=schema)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Performance notes
|
|
182
|
+
|
|
183
|
+
- **Batch size**: 500 is a reasonable default. Raise to 1000 for simple rules, and drop to 100–200 for rules involving large payloads.
|
|
184
|
+
- **Concurrency**: each executor hits the Rulebricks API in parallel. Against the managed cloud, rate and account limits may kick in before you saturate workers — watch for 429s in `_rb_error`. Against a self-hosted cluster, the autoscaler handles it. [Read more about self-hosted Rulebricks for production volumes](#self-hosted-rulebricks-for-production-volumes).
|
|
185
|
+
- **Partition count**: `solve()` inherits the input DataFrame's partitioning. If your DataFrame has one huge partition, either call `.repartition(n)` yourself or pass `auto_repartition=True`.
|
|
186
|
+
- **Schema inference cost**: a single driver-side API call at job start. Cache the schema or pass `output_schema` explicitly to skip it for repeated runs.
|
|
187
|
+
|
|
188
|
+
## Error handling
|
|
189
|
+
|
|
190
|
+
The `_rb_error` column is always present in the output.
|
|
191
|
+
|
|
192
|
+
- `on_error="continue"` (default): batch failures mark every row in the failed batch with the error string, leaving rule output columns `null`. The job completes.
|
|
193
|
+
- `on_error="raise"`: the Spark task fails on the first batch error, propagating to job failure.
|
|
194
|
+
|
|
195
|
+
Retries run automatically for 408 / 425 / 429 / 500 / 502 / 503 / 504, timeouts, and connection resets, using exponential backoff (2^attempt seconds, capped at 30).
|
|
196
|
+
|
|
197
|
+
## Requirements
|
|
198
|
+
|
|
199
|
+
- Python 3.9+
|
|
200
|
+
- PySpark 3.3+
|
|
201
|
+
- `rulebricks` SDK 1.0+
|
|
202
|
+
- `pandas` 1.5+
|
|
203
|
+
- `pyarrow` 10+
|
|
204
|
+
|
|
205
|
+
## Development
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
git clone https://github.com/rulebricks/spark
|
|
209
|
+
cd rulebricks-spark
|
|
210
|
+
pip install -e ".[dev]"
|
|
211
|
+
pytest
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Run only the fast unit tests:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
pytest -m "not requires_spark"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Run the end-to-end tests (requires PySpark and a JVM):
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
pytest -m requires_spark
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT — see [LICENSE](LICENSE).
|
|
229
|
+
|
|
230
|
+
## Links
|
|
231
|
+
|
|
232
|
+
- [Rulebricks Homepage](https://rulebricks.com)
|
|
233
|
+
- [Rulebricks User Guide](https://rulebricks.com/docs)
|
|
234
|
+
- [Python SDK reference](https://rulebricks.com/docs/api-reference#sdk/python)
|
|
235
|
+
- [Issues & feature requests](https://github.com/rulebricks/spark/issues)
|
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Runnable Databricks notebooks demonstrating `rulebricks-spark` across the
|
|
4
|
+
three most common patterns.
|
|
5
|
+
|
|
6
|
+
| Example | Pattern |
|
|
7
|
+
|---|---|
|
|
8
|
+
| [`breach-triage/`](./breach-triage/) | Canonical batch: score a Delta table end-to-end. Start here. |
|
|
9
|
+
| [`streaming/`](./streaming/) | Apply a rule to a Structured Streaming DataFrame with a Delta sink. |
|
|
10
|
+
| [`sql-callable/`](./sql-callable/) | Register a SQL-callable UDF for use in `%sql` cells and DBSQL. |
|
|
11
|
+
|
|
12
|
+
Each folder has its own README with the rule schema the notebook expects,
|
|
13
|
+
where to find your rule slug and API key, and how to wire the notebook into
|
|
14
|
+
Databricks (secrets, cluster config, `%pip install`).
|
|
15
|
+
|
|
16
|
+
## Prerequisites (all examples)
|
|
17
|
+
|
|
18
|
+
- A published rule in your Rulebricks workspace.
|
|
19
|
+
- A Rulebricks API key.
|
|
20
|
+
- A PySpark environment — Databricks, EMR, Synapse, or a local PySpark
|
|
21
|
+
install. Examples are formatted as Databricks notebooks but the core
|
|
22
|
+
`solve()` and `register_udf()` calls work anywhere PySpark runs.
|
|
23
|
+
|
|
24
|
+
## Running outside Databricks
|
|
25
|
+
|
|
26
|
+
The `%pip`, `dbutils`, `display`, and `%sql` cells are Databricks-specific
|
|
27
|
+
conveniences. For local or non-Databricks runs:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import os
|
|
31
|
+
api_key = os.environ["RULEBRICKS_API_KEY"]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Replace `display(df)` with `df.show()` and skip the `%sql` cells (or wrap
|
|
35
|
+
them in `spark.sql("...").show()`).
|
|
36
|
+
|
|
37
|
+
## A note on rule slugs
|
|
38
|
+
|
|
39
|
+
Rulebricks slugs are short opaque IDs — roughly 10 characters of nanoid-
|
|
40
|
+
style text, e.g. `abc123xyz9`. They appear in the rule editor URL and on
|
|
41
|
+
each rule card in the Dashboard. Copy yours into the `RULE_SLUG` constant
|
|
42
|
+
at the top of each notebook.
|