Guide
This guide follows Getting Started, which installs barca and runs two
assets. The examples here were checked against barca 0.27.0. Each section names the manual topic
(barca docs <topic>) that has the full rules.
1. Assets and inputs
Section titled “1. Assets and inputs”An asset is a Python function whose result barca caches. It names its inputs with inputs=:
each key is a parameter of the function, and each value is the upstream function whose result
it receives.
from barca import asset
@asset()def raw_data() -> list[dict]: return [{"x": 1}, {"x": 2}, {"x": 3}]
@asset(inputs={"data": raw_data})def summary(data: list[dict]) -> dict: return {"count": len(data), "total": sum(d["x"] for d in data)}barca get pipeline.py # every asset in the file; prints the last asset's artifact pathbarca get summary pipeline.py # one asset and what it depends on- The target comes first, then the files.
- An asset runs again when its own code changes or when one of its inputs does. Edit
summaryand onlysummaryruns. Editraw_dataand both run. inputs=has to be written literally in the decorator, because barca reads it from the source without importing the file.inputs=passed through a variable or a call is an error (exit 2); inputs built in a loop are not seen.- The decorator returns the function unchanged, so
summarycan be imported and called in a unit test like any other function. - Assets that do not depend on each other run in parallel, in separate worker processes.
More: barca docs assets.
2. Where results live
Section titled “2. Where results live”A run creates .barca/ in the project root:
.barca/metadata.db run history and the record of what is cached.barca/artifacts/<node>/<run_hash>.<ext> one file per result: .json, .pkl or .parquet.barca/envs/<env>/ the same two, for each --env other than the default- The project root is the nearest directory at or above the current one that holds a
barca.toml(an empty file is enough). Without one, it is the current directory. - Do not commit
.barca/. Barca writes a.gitignoreinside it, so git already ignores it and nothing needs adding to your own. - Results are cached by run hash. The run hash covers the function’s code, the helpers it uses from your project, and the run hashes of its inputs. It does not cover the bytes of the output.
- The format follows the returned value. A dict, list, string or number is stored as
json. A DataFrame, Arrow table or DuckDB relation is stored as parquet. Anything else is
pickled (
barca docs types). - Results are written locally first. A remote store is this machine’s backup and
read-through cache: set
BARCA_REMOTE_URI(or[remote] uriinbarca.toml) and every result and a backup of the history are uploaded there. See Remote storage. Sharing results between machines means a server they all point at (barca docs server-mode, Deploying). --env <name>(orBARCA_ENV) keeps a separate cache and history, so dev and prod do not share results.
Do not delete files under .barca/ to force a recompute. Use --refresh (section 4). Every
file under .barca/ is listed in Configuration.
More: barca docs cache, barca docs remote.
3. Seeing what barca sees
Section titled “3. Seeing what barca sees”None of these commands runs a step.
barca list # every asset, sensor and task, with its inputsbarca status # per node: cached or stale and why, last run, shape of the resultbarca get summary --dry-run # what this exact command would run or serve from cachebarca plan pipeline.py # the execution plan as JSON: phases, and the steps in eachbarca sql "select * from raw_data" # query a cached result with DuckDBGetting Started shows the output of status and
--dry-run. --dry-run works on get and run and takes the same flags, so it also
previews a --refresh.
barca sql runs a DuckDB query over the results already on disk. Every node with a json or
parquet result is a view named after its function:
$ barca sql "select count(*) as n, sum(x) as total from raw_data"n total3 6It returns at most 100 rows unless you pass --limit N or --all, shows the last result even
when the asset is stale (stderr says so). Install SQL support with uv add 'barca[sql]'. See barca sql.
get, run, list, status, sql, history and stats print a table or summary in a
terminal and JSON when piped or captured; --json and --pretty override that. Progress and
errors go to stderr. The JSON shapes and exit codes are in the
CLI reference, and the agent skill
is a short guide for AI agents.
More: barca docs status, barca docs sql.
4. Sensors: data from outside
Section titled “4. Sensors: data from outside”An asset that reads a file, a bucket or a table in its own body has the same code and the same inputs on every run. Barca computes it once and then serves it from cache, even after the data changes.
Put a @sensor in front of the asset. Return an mtime, ETag or other version value
and Barca compares it on each poll. When you already know whether the source changed,
return True/False, or (changed, value) to also pass data. A change advances freshness
without requiring a payload; an unchanged signal retains the previous observation.
import hashlibfrom pathlib import Path
from barca import asset, sensor
@sensor()def orders_version() -> str: # anything that identifies the version of the data: an etag, a last-modified time return hashlib.md5(Path("orders.csv").read_bytes()).hexdigest()
@asset(inputs={"version": orders_version})def orders(version: str) -> list: return Path("orders.csv").read_text().splitlines()[1:]
@asset(inputs={"rows": orders})def order_count(rows: list) -> int: return len(rows)With an orders.csv of a header and two rows:
barca get order_count pipeline.py # 3 steps run; the value is 2barca get order_count pipeline.py # 1 step runs: the sensor. orders and order_count are cached# ... a row is appended to orders.csv ...barca get order_count pipeline.py # 3 steps run; the value is 3- The asset receives only
value(hereversion: str), not the tuple. Theboolis not used for caching. - Return only what identifies the data. A value that changes on every run, such as a timestamp, re-runs the sensor’s consumers every time.
- A sensor has no inputs.
- The cache is keyed by the value, not by time. If the sensor returns a value it returned before, the result computed for that value is served.
--dry-runandbarca statusrun nothing, so they assume the sensor returns the value it returned last time.
Sensors and External Observations walks through each of these with output.
Recomputing by hand: --refresh
Section titled “Recomputing by hand: --refresh”barca get order_count pipeline.py --refresh orders # orders and everything downstream of itbarca get order_count pipeline.py --refresh orders --no-cascade # only orders; barca warns that order_count is out of datebarca get order_count pipeline.py --refresh-all # every asset the target depends on--refresh takes one comma-separated list (--refresh a,b), and barca run takes the same
three flags. Use it also after a change the run hash does not see: an
installed package, a module outside the project root, or an environment variable the
function reads without declaring it in @asset(env=[...]).
More: barca docs cache has the complete list, under “Not followed”.
The run hash covers the function and every project definition it uses, followed through
imports, decorators, defaults and the other places Python can reach a helper. Where barca
cannot tell statically which definition a name means (a module-level if, a star import), it
includes more rather than less, so an edit nearby can re-run a step that did not need it. The
exact rules are in barca docs cache, “What the definition covers” and “Followed
conservatively”.
5. Tasks, and get versus run
Section titled “5. Tasks, and get versus run”A task is a step that does something: deploy, notify, upload. It is never cached.
from barca import asset, task
@asset()def daily_report() -> dict: return {"revenue": 42000, "orders": 150}
@task(inputs={"report": daily_report})def notify(report: dict) -> None: print(f"Daily revenue: {report['revenue']}")$ barca run notify pipeline.py[pipeline.py:notify] Daily revenue: 42000[barca] 1/2 steps | done in 0.0sRun 14d08771a6ca | ran 'notify' in 0.167s (1 step, 1 phase)
Value:nullbarca get <asset> [files] |
barca run <task> [files] |
|
|---|---|---|
| Target | an asset or a sensor | a task |
| The target itself | runs only if it is not cached | always runs |
| Assets it depends on | run only if not cached | run only if not cached |
| No target | every asset and sensor; tasks are skipped and named on stderr | not allowed |
| Several targets | barca get a,b |
barca run a,b |
- Using the wrong command, or putting a file before the target, exits 2 with a message that names the right command.
- A task can depend on assets, sensors and other tasks. An asset or a sensor cannot depend on a task.
- An input whose name starts with
_is for ordering only: the step runs after it, the value is not loaded, and the parameter receivesNone.
More: barca docs tasks.
6. Large inputs
Section titled “6. Large inputs”A DataFrame, Arrow table or DuckDB relation is stored as parquet. How a downstream step reads it depends on the annotation of the parameter that receives it:
| Annotation | What is read |
|---|---|
none, pd.DataFrame, pl.DataFrame, pyarrow.Table |
the whole file, before the function runs (no annotation means pandas) |
duckdb.DuckDBPyRelation, pl.LazyFrame |
nothing before the function runs; then the columns the step’s query uses |
A declared input is loaded whether or not the function uses it, and barca warns at plan time
about one the function never mentions. barca get on an asset stored as parquet prints a
pointer to the file, not the rows; use barca sql to look at it.
Large inputs has a worked example and the limits.
More: barca docs big-inputs, barca docs types.
7. Partitions
Section titled “7. Partitions”Partitions run one asset once per key. The keys run in parallel, and each key is cached on
its own. After the first successful run, ordinary DAG runs reuse validated freshness
before expanding keys: one changed key schedules only its affected chain. Sensors that
return all keys still discover them all; a changed collect consumer still reads all its
inputs. Batching is independent and needs no special setting for incremental planning.
from barca import asset, collect, partitions, partitions_from
REGIONS = ["emea", "amer", "apac"]
@asset(partitions={"region": partitions(REGIONS)})def sales(region: str) -> dict: return {"region": region, "revenue": len(region) * 100}
@asset(partitions={"region": partitions_from(sales)}) # same keys as salesdef margin(region: str, sales: dict) -> dict: return {"region": region, "margin": sales["revenue"] * 0.2}
@asset(inputs={"all_sales": collect(sales), "margins": collect(margin)})def total(all_sales: list[dict], margins: list[dict]) -> dict: return {"revenue": sum(s["revenue"] for s in all_sales), "margin": sum(m["margin"] for m in margins)}barca get total pipeline.py # 7 steps: three keys each of sales and margin, then totalbarca get total pipeline.py # 0 steps# ... "latam" is added to REGIONS ...barca get total pipeline.py # 3 steps: sales and margin for latam, then total- The key is passed as the parameter named in
partitions={...}, hereregion. partitions_from(sales)gives another asset the same keys. Each key receives that key’s result ofsales, as the parameter namedsales.collect(sales)gives a downstream asset every key’s result as one list. An unpartitioned asset that names a partitioned one ininputs=withoutcollect()is a usage error (exit 2).- An unpartitioned asset or a sensor in the
inputs=of a partitioned asset is passed whole to every key. Changing it re-runs every key. barca statusshows the asset as one row, and aspartialwhen only some keys are cached.- In
barca sqla partitioned asset is one view over every key, with apartitioncolumn:
$ barca sql "select * from sales"partition region revenueregion=amer amer 400region=apac apac 400region=emea emea 400region=latam latam 500Limits:
- A new key runs alone however the keys are written: a literal list inside the decorator,
a module-level constant as above, another expression, or
partitions_from(...). barca get salesreturns every key: one artifact path per key (-o valuefor the values). Past 1000 keys it lists only the keys the run computed and counts the rest (many partition keys).--partition region=emeacomputes and returns only that key, and scopes--refresh salesto it. It narrows upstream partitioned assets too, and skips acollect()fan-in over them.- A removed key’s result stays on disk until the cache size limit evicts it, and its runs stay
in the history.
barca sqlshows only the current keys: withlatamremoved again, the view above has three rows.
Parametrized Assets and Partitions shows
each of these with output. More: barca docs partitions.
8. Projects with several files
Section titled “8. Projects with several files”With no file arguments, barca reads every .py file under the project root that imports
barca and builds one graph from all of them.
my_project/ barca.toml empty; marks the project root helpers.py a plain module, no barca import pipelines/ sources.py @asset rows report.py @asset total, which reads rowsfrom barca import asset
from helpers import clean
@asset()def rows() -> list: return clean([1, 0, 2])from barca import asset
from pipelines.sources import rows
@asset(inputs={"rows": rows})def total(rows: list) -> int: return sum(rows)barca get total # from the root or any directory below itbarca get total pipelines/ # read only the files under pipelines/- An input from another file is imported by its path from the project root
(
from pipelines.sources import rows) and then named ininputs=. Barca resolves the import from the source. A barefrom sources import rowsnext to it is a usage error (exit 2) that names the qualified import to use, because the same file would have two module names. - A node’s id is
<file>:<function>, with the file relative to the project root (pipelines/report.py:total). A bare name such astotalworks as a target when only one file defines it. - Steps run with the project root as their working directory.
helpers.pyis never passed on the command line, but it is part of the cache key: an asset’s run hash covers the definitions it uses from your project’s modules. Editingclean, or anythingcleancalls, re-runsrowsand what depends on it. Editing another function inhelpers.pyre-runs nothing.- The standard library, installed packages, modules outside the project root
and imports built at run time are not followed. After changing one of those, use
--refresh.
Which directories and files a walk skips, and the [discovery] settings that change it, are
in Discovery.
More: barca docs discovery, barca docs cache.
9. Schedules
Section titled “9. Schedules”freshness=Schedule("<cron>") on an asset, task or sensor makes barca serve run it on that
cron schedule. barca get and barca run do not look at the clock.
from barca import task, Schedule
@task(freshness=Schedule("*/10 * * * *")) # every 10 minutesdef refresh() -> None: print("tick")barca list job.py # shows each schedule and its next fire timebarca serve job.py --timezone utc # scheduler, HTTP API and web UI on 127.0.0.1:8274A scheduled task runs on every tick. A scheduled asset is brought up to date on every tick,
so it runs only if it is not cached: outside data still has to come in through a sensor
(section 4). The same tick also brings up to date every Always node below the scheduled
one (Always is the default), so a schedule on a sensor keeps what reads it current. A Manual
node is refreshed by hand only: ticks skip it, and what reads it uses its last result.
Scheduling covers what a tick does, Always and Manual, and failed ticks, and
Deploying covers running the server.
Where to go next
Section titled “Where to go next”barca docsin the terminal: the manual, by topic.barca <command> --helpends with examples.- CLI reference and Configuration.
- Patterns for common shapes and what to avoid.
barca historyandbarca stats <asset>for past runs and timings.