Quickstart¶
Your first task, running on your laptop in a few minutes, with no Java and no cloud account. For anyone new to Ubunye. The commands in step 2 are run by the test suite exactly as written, on every change, so they work as shown.
New words (use case, pipeline, task, run record) are explained in Concepts.
1. Install¶
The pandas extra is what lets a task run with no Spark and no Java. (Add
[spark] later to run the same task on Spark.)
2. Make a task, check it, run it¶
ubunye init -d pipelines -u demo -p starter -t filter_adults
ubunye plan -d pipelines -u demo -p starter -t filter_adults --backend pandas
ubunye run -d pipelines -u demo -p starter -t filter_adults --backend pandas --lineage
ubunye lineage list -d pipelines -u demo -p starter -t filter_adults
What each one did:
-
initmade a task folder, and the folder is the whole task.-dis the pipelines folder,-uthe use case,-pthe pipeline,-tthe task:pipelines/demo/starter/filter_adults/ config.yaml reads data/people.csv, writes output/adults as Parquet transformations.py keeps the people aged 18 and over data/people.csv eight people, some of them children notebooks/ a notebook for trying things step by stepIn
config.yaml, input and output both sayformat: s3. That is the file connector:s3means any file path, on your disk or in a cloud bucket, not only Amazon S3. -
planchecked everything it could without moving any data: the input file is there, the transform loads, the write mode makes sense, and the pandas backend can do all of it. It exits1if anything would stop the run. -
runread the CSV, kept the adults, and wrotepipelines/demo/starter/filter_adults/output/adults/as Parquet. With--backend pandasthat happens in plain Python, no Java involved. -
lineage listshows the run record the run left (--lineagewrote it): when it ran, how it went, how many rows came out. Each record also keeps a hash of every row read and written, so two runs can be compared exactly.RUN_ID STARTED STATUS DUR ROWS ------------------------------------------------------------ dede6d1c 2026-11-10T10:34:34 success 0.3s in:8 out:5Your run ID and time will differ.
If a command fails because of the machine (no Java, an old pyarrow, a missing
environment variable), ubunye doctor says which, and how to fix it. See
Troubleshooting.
3. Change it¶
Open transformations.py:
class FilterAdults(Task):
def transform(self, sources):
people = sources["people"]
return {"adults": people[people["age"] >= 18]}
Change 18 to 21 and run it again, with --lineage so it leaves a record:
ubunye run -d pipelines -u demo -p starter -t filter_adults --backend pandas --lineage
ubunye lineage list -d pipelines -u demo -p starter -t filter_adults
The list now shows two runs, newest first:
RUN_ID STARTED STATUS DUR ROWS
------------------------------------------------------------
9913a5f1 2026-11-10T10:34:39 success 0.3s in:8 out:4
dede6d1c 2026-11-10T10:34:34 success 0.3s in:8 out:5
Compare them. Put your own two run IDs in place of these:
ubunye lineage compare -d pipelines -u demo -p starter -t filter_adults \
--run-id1 dede6d1c --run-id2 9913a5f1
code_hash: sha256:b6e66360... -> sha256:49d550bd... CHANGED
Input 'people':
row_count: 8 (unchanged)
data_hash: sha256:00d7526f... (unchanged)
Output 'adults':
row_count: 5 -> 4 CHANGED
data_hash: sha256:622fcbc6... -> sha256:7420fc72... CHANGED
(Shortened here: the real output prints full hashes, plus the config, schema and environment hashes.) The code changed, the input stayed the same, and the output lost one row.
The line people[people["age"] >= 18] means the same thing in pandas and in
Spark, which is why this task can run on either.
4. Run the same folder on Spark¶
You need Java (which version) and
pip install "ubunye-engine[spark]". Change 21 back to 18 in
transformations.py, then run without --backend:
Now check that Spark wrote exactly what pandas wrote. List the runs, then compare the Spark run (the newest) with your first pandas run (the oldest, the same code):
ubunye lineage list -d pipelines -u demo -p starter -t filter_adults
ubunye lineage compare -d pipelines -u demo -p starter -t filter_adults \
--run-id1 <pandas run id> --run-id2 <spark run id>
When the two backends agree, every hash says (unchanged) and only the time
differs:
status: success (unchanged)
duration_sec: 0.27769826199801173 -> 2.659492216000217 CHANGED
config_hash: sha256:8bd62c96... (unchanged)
code_hash: sha256:b6e66360... (unchanged)
Input 'people':
row_count: 8 (unchanged)
data_hash: sha256:00d7526f... (unchanged)
Output 'adults':
row_count: 5 (unchanged)
schema_hash: sha256:9c8ec3b8... (unchanged)
data_hash: sha256:622fcbc6... (unchanged)
Same folder, same config, same rows. On Databricks the notebook's session is used automatically.
5. From Python¶
import ubunye
outputs = ubunye.run_task("pipelines/demo/starter/filter_adults", backend="pandas")
print(len(outputs["adults"])) # a pandas DataFrame
Common errors¶
Every error says what went wrong, where, and what to try.
More symptoms and fixes: Troubleshooting.
A typo in a field name
Unknown fields in pipelines/demo/starter/filter_adults/config.yaml:
(top level):
Unknown field 'ENGNE'
Did you mean 'ENGINE'?
A template variable with no value
Template resolution failed for .../config.yaml:
Undefined variable 'region' in config value '.../{{ region }}'. ...
Pass it with --var region=gauteng, or give it a default:
{{ region | default('gauteng') }}.
Something the backend cannot do
[ERROR] Run refused for copy: This task cannot run on the pandas backend:
- input 'orders' uses the 'hive' connector, which needs spark; the pandas backend does not provide it.
ubunye plan --backend pandas finds these before anything runs.
See the full Error Reference.
What's next?¶
| Topic | Link |
|---|---|
| The words, in one page | Concepts |
| How a config is laid out | Config Reference |
| Reading and writing data | Connectors |
| Choosing Spark or pandas | Execution Backends |
| Every command and flag | CLI Reference |
| Running from Python and notebooks | API Reference |
| Deploying to Databricks | Deployment |
| Training and versioning models | Model Contract |