Skip to content

Recipes

Short, complete answers to common tasks. Each snippet assumes from pathlib import Path and the imports it shows.

Load rows from a CSV file

import csv
from decimal import Decimal
from pathlib import Path

from pyaccesskit import AccessDatabase

with (
    AccessDatabase.open("shop.accdb") as db,
    Path("products.csv").open(newline="", encoding="utf-8") as f,
):
    for row in csv.DictReader(f):
        db.execute(
            "INSERT INTO Products (ProductName, UnitPrice) VALUES ([name], [price])",
            {"name": row["name"], "price": Decimal(row["price"])},
        )

Convert CSV strings to real Python types first (Decimal, int, date.fromisoformat...). Parameters keep their Python type; passing the string "12.50" would leave the conversion to Access, which uses the Windows locale (a decimal comma on many systems).

Change an existing database safely and repeatably

Write changes so that running them twice does no harm, and keep a backup:

import shutil

from pyaccesskit import AccessDatabase, Column, IndexSpec

shutil.copy2("app.accdb", "app.backup.accdb")
with AccessDatabase.open("app.accdb") as db:
    customers = db.tables["Customers"]
    if "Phone" not in customers.fields:
        customers.add_column(Column.text("Phone", length=30))
    if not any(index.name == "ixCity" for index in customers.indexes):
        customers.create_index(IndexSpec.on("ixCity", "City"))
    if "qryCustomersByCity" not in db.queries:
        db.queries.create(
            "qryCustomersByCity", "SELECT * FROM Customers ORDER BY City, CustomerName;"
        )

Changes to an opened database are applied one by one (they are not a single transaction), which is why the backup matters. AccessDatabase.create() is the atomic path: rebuilding from specs is often simpler.

Snapshot a schema as JSON and rebuild it

import json
from pathlib import Path

from pyaccesskit import AccessDatabase, RelationshipSpec, TableSpec

with AccessDatabase.open("app.accdb", readonly=True) as db:
    snapshot = {
        "tables": [spec.model_dump(mode="json") for spec in db.tables.specs()],
        "relationships": [spec.model_dump(mode="json") for spec in db.relationships.specs()],
    }
Path("schema.json").write_text(json.dumps(snapshot, indent=2), encoding="utf-8")

data = json.loads(Path("schema.json").read_text(encoding="utf-8"))
with AccessDatabase.create("copy.accdb", overwrite=True) as db:
    for table in data["tables"]:
        db.tables.create(TableSpec.model_validate(table))
    for relationship in data["relationships"]:
        db.relationships.create(RelationshipSpec.model_validate(relationship))

Export every object as text (for source control)

from pathlib import Path

from pyaccesskit import AccessDatabase

out = Path("src-access")
with AccessDatabase.open("app.accdb") as db:
    for kind in ("form", "report", "macro", "module", "query"):
        folder = out / f"{kind}s"
        folder.mkdir(parents=True, exist_ok=True)
        for name in db.objects.names(kind):
            db.objects.save_text(kind, name, folder / f"{name}.txt")

Text export needs Microsoft Access (the session switches to a design session), so the database is opened writable and exclusively.

Copy forms and modules between databases

from pyaccesskit import AccessDatabase

with AccessDatabase.open("template.accdb") as source:
    texts = {name: source.objects.export_text("form", name) for name in source.forms.names()}
with AccessDatabase.open("app.accdb") as target:
    for name, text in texts.items():
        target.objects.import_text("form", name, text, replace=True)

Forms reference tables and queries by name: create those in the target first.

Lookup lists on a form

from pyaccesskit import AccessDatabase, RowSourceType, cm

with (
    AccessDatabase.open("app.accdb") as db,
    db.forms.create("frmOrders", record_source="Orders", replace=True) as form,
):
    form.combobox(
        "CustomerID",
        label="Customer",
        row_source="SELECT CustomerID, CustomerName FROM Customers ORDER BY CustomerName",
        column_count=2,
        column_widths=[cm(0), cm(6)],  # store the ID, show the name
    )
    form.combobox(
        "Status", row_source='"Open";"Shipped";"Closed"', row_source_type=RowSourceType.VALUE_LIST
    )

Watch Access while debugging

from pyaccesskit import AccessDatabase, DialogPolicy, SessionOptions

debug = SessionOptions(visible=True, dialog_policy=DialogPolicy.WARN, call_timeout=None)
with AccessDatabase.open("app.accdb", engine="access", options=debug) as db:
    ...

With visible=True you see what Access does; DialogPolicy.WARN dismisses dialogs but only warns.

Treat questionable names as errors

import warnings

from pyaccesskit import AccessNameWarning

warnings.simplefilter("error", AccessNameWarning)  # "Name", "Date", spaces... now raise immediately

Password-protected databases

import os

from pyaccesskit import AccessDatabase

with AccessDatabase.open("secret.accdb", password=os.environ["APP_DB_PASSWORD"]) as db:
    print(db.tables.names())

Keep passwords out of source code; pyaccesskit inspect reads PYACCESSKIT_PASSWORD.

Check the environment from code

from pyaccesskit import diagnose

report = diagnose()
if not report.usable:
    raise SystemExit("\n".join(report.problems))
print("engine='auto' will use", report.auto_engine)