Bauta
Put a mask on production: a safe, realistic stand-in for your production data, in whichever database you need it. Bauta masks what it copies, copies only the slice you need with every relationship intact, generates what may not be copied at all, and moves data between databases on a schedule you already run, with nothing to host.
- Masking: consistent across tables and runs, one-to-one for keys (NIST FF1 where policy requires it), applied before anything reaches the target, and every column must be covered.
- Discovery, subsets and synthetic data: propose a masking policy from a live schema, copy a referentially complete slice of production, create the copy's tables in whichever database it goes to, and fill tables that can't be copied with generated rows.
- Audit: report what every job does with data and what a reviewer should question, and seal each run's masking manifest so it can be verified later.
- Six databases: Oracle, SQL Server, PostgreSQL, MySQL, MariaDB and SQLite, as source or target in any combination.
- Streaming: memory stays flat however large the table, and PostgreSQL and SQL Server targets load in bulk.
- Fast: ten million rows of six masked columns in under two minutes on one core with the optional native masker, and under eight without. Either way the masks are the same.
- Incremental loads: extract only what changed since the last successful run.
- A dependency graph: jobs run in order, concurrently where they can, each in its own process with an optional timeout.
- Operable: webhook alerts; run state, history and manifests each in a file or a table; and passwords from a command for cloud IAM tokens.
- No infrastructure: a
pip install, some YAML, and a command you run from cron.
Install
Python 3.10 or newer. Choose the drivers you need as extras; each is loaded only when a connection uses it.
pip install "bauta[postgresql,oracle]"
| Extra | Installs | Needs besides pip |
|---|---|---|
mysql, mariadb |
mysql-connector-python | nothing |
postgresql |
psycopg 3, with its own libpq | nothing |
oracle |
oracledb, in thin mode | nothing — no Oracle client |
mssql |
pymssql | nothing |
sqlite |
Python's own sqlite3 |
nothing |
fpe |
cryptography, for the fpe masking strategy |
nothing; oracle already brings it |
native |
bauta-rs, the native masker (below) |
nothing on Linux (x86-64, ARM) or macOS; elsewhere, Rust 1.83 or newer |
all |
every driver above | nothing |
The native masker (optional). bauta-rs masks in Rust: four to five times the throughput, identical masks, nothing to configure. pip install "bauta[postgresql,native]" installs the version that matches, which is the only one Bauta uses. Without it, everything works, only slower. See the native masker.
Quickstart
Start from the configuration in example/starter/configuration/, from a clone or downloaded from GitHub:
mkdir configuration
cp example/starter/configuration/*.yaml configuration/
Edit configuration/database.yaml and configuration/jobs.yaml for your databases, then supply the credentials they reference:
export SOURCE_DB_PASSWORD=... TARGET_DB_PASSWORD=... MASKING_KEY=...
bauta validate # check the configuration, offline
bauta run --dry-run # check connections and tables, moving nothing
bauta run # run every job once
To see it work without any of that, the demos in a clone of this repository use throwaway SQLite databases:
git clone https://github.com/ribeiro11075/bauta.git && cd bauta
pip install -e ".[fpe]"
python example/walkthrough/demo.py # the whole workflow: discover, subset, audit, mask, verify, synthesize
python example/incremental/demo.py # streaming and incremental loads
python example/masking/demo.py # masking, discovery and a subset, from Python
python example/native-masking/demo.py # the same job masked in Python and in Rust, compared
The command
bauta run run data jobs once, masking any with a `masking` section
bauta validate check configuration without connecting
bauta jobs show the job graph and what's due
bauta history show recent job outcomes recorded with --history
bauta discover propose a masking policy for tables
bauta subset generate jobs that copy a referentially complete subset
bauta schema create target tables from source ones, in the target's dialect
bauta synthesize fill tables with generated rows, for data that can't be copied
bauta clear empty the target tables of jobs, children first
bauta audit report what each job does with data, and what to question
bauta verify-manifest check a manifest is unaltered, and who signed it
| Exit code | Meaning |
|---|---|
0 |
every job completed |
1 |
a job failed, or was skipped because a predecessor failed, or the command failed on a database error |
2 |
invalid configuration or usage |
130 |
interrupted by a signal: running jobs finished, the rest were skipped |
run makes one pass and exits, so it fits under cron or a Kubernetes CronJob. A second run sharing the same run state refuses to start while the first is still going. The useful flags:
| Flag | |
|---|---|
--config DIR |
where the YAML lives; default ./configuration |
--job NAME |
run only this job — without its predecessors, which it warns about |
--force |
ignore refresh windows |
--forever |
stay running; for freshness under a minute |
--log-format json |
structured logs for a collector |
--log FILE |
also log to a file, in addition to stderr (--quiet silences stderr) |
--memory FILE |
where run state (last runs, watermarks) is kept, overriding jobs.yaml's memory |
--memory-database ALIAS |
keep run state in a database table instead |
--history FILE |
append each job's outcome to a JSON-lines history (--history-database ALIAS for a table), overriding jobs.yaml's history |
--notify-url URL |
post to a webhook when a run doesn't succeed; default $BAUTA_NOTIFY_URL |
--rules FILE |
your own rules for recognising personal data, for discover, audit and synthesize; default configuration/discovery.yaml (more) |
--accept-key-change |
run upsert jobs whose masking key changed since their last run |
--manifest FILE |
write a sealed JSON record of what was masked, and how (--manifest-database ALIAS for a table), overriding jobs.yaml's manifest; signed if $BAUTA_MANIFEST_KEY is set |
Documentation
| Configuration | every field, how credentials are read from the environment, and connection options such as TLS |
| Masking | strategies, consistent masks across tables, the key, the manifest, audit, discover, subset, schema, synthesize and clear |
| How it works | streaming, incremental loads, retries, scheduling, and the masking design |
| Operating it | run state, history and notifications |
| Security model | what masking protects and what it doesn't, the constructions, keys, and a deployment checklist |
| Library | embedding it in Python, results, memory backends |
| Development | running the tests, including against real databases |
Layout
bauta/ |
the package; runner.py runs jobs, masking.py masks, databaseDialects.py holds per-database SQL |
mask-rs/ |
the optional native masker, in Rust — see its README |
example/ |
runnable demos, each with its configuration/, and a starter configuration — see its README |
docs/ |
the documentation above |
tests/ |
the test suite |
License
Release files for bauta 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bauta-0.1.2.tar.gz | 242.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bauta-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 371.2 kB
Release files / bauta-0.1.2.tar.gz
| Download URL | bauta-0.1.2.tar.gz |
|---|---|
| Size | 242.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
68f0408a1c04582d358f95e3f68bb666ded02992e87913b7cbc066f0c37c61d3
|
|
BLAKE2b-256 checksum How to use checksums |
26d6665a6944d3bc93904d59bd01946cc825c858a24c270905c5b614ed5af66b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency logRelease files / bauta-0.1.2-py3-none-any.whl
| Download URL | bauta-0.1.2-py3-none-any.whl |
|---|---|
| Size | 128.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cb00f2e1cf7e779c5a6809041b6413c852f1cd337ff62a7af52356091d70ee8a
|
|
BLAKE2b-256 checksum How to use checksums |
452510ea1c13056ccf3c9df76e3e8a8adeb26c77631d446ee7964e4f4e8ae982
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency log