A modern, typed, Pythonic toolkit for building and modifying Microsoft Access databases (.accdb) and,
progressively, whole Access applications without raw pywin32 calls, DAO magic numbers or orphaned
MSACCESS.EXE processes.
from pyaccesskit import AccessDatabase, Column, Expr, Vba, cm
with AccessDatabase.create("crm.accdb") as db:
db.tables.create(
"Customers",
columns=[
Column.autonumber("CustomerID", primary_key=True),
Column.text("CustomerName", length=200, required=True),
Column.text("Email", length=255, unique=True),
Column.date_time("CreatedAt", default=Expr("Now()")),
],
)
db.tables.create(
"Orders",
columns=[
Column.autonumber("OrderID", primary_key=True),
Column.number("CustomerID", required=True), # Long Integer, as in Access
Column.currency("Total", default=0),
],
)
db.relationships.create("Customers.CustomerID", "Orders.CustomerID", cascade_delete=True)
db.queries.create("qryCustomers", "SELECT * FROM Customers ORDER BY CustomerName;")
with db.forms.create("frmCustomers", record_source="Customers", caption="Customers") as form:
form.textbox("CustomerName", label="Customer name", width=cm(8))
form.textbox("Email")
form.button("cmdClose", caption="Close", on_click=Vba("DoCmd.Close acForm, Me.Name"))When the with block ends, the database file appears at crm.accdb, and any Access process PyAccessKit
started is closed. If the block raises, no file is created and nothing is left running.
Status: alpha (
0.1.0). The API may still change between 0.x releases.
pip install pyaccesskitRequirements:
- Windows 10 or 11 and CPython 3.11–3.14 (64- or 32-bit; free-threaded builds are not supported).
- Microsoft Access 2016 or later, including Microsoft 365. Alternatively, the Microsoft 365 Access Runtime with a Python of the same bitness, for schema and data work only.
Check your machine with:
pyaccesskit doctor # which engines work here, and why not
pyaccesskit doctor --probe # also builds a scratch database end to endThe spec models (TableSpec, Column, FormSpec, units, layout…) are pure Python and also import on
Linux and macOS, so you can validate specs in CI without Access.
| Area | Highlights | Stability |
|---|---|---|
| Lifecycle | Atomic create(), open() (read-only/shared, exclusive, password), guaranteed cleanup, db.raw escape hatch |
stable-track |
| Tables | Every common column type, defaults as Python values or Expr, validation rules, captions, formats, input masks; indexes (composite, descending, unique, primary); add, drop and rename columns and indexes; to_spec() round-trips |
stable-track |
| Relationships | Single and composite keys, referential integrity, cascades, join types, all checked before Access sees them | stable-track |
| Queries & data | Saved queries (select, action, union, crosstab, pass-through), parameters bound by name, execute() / fetch_all() |
stable-track |
| Forms | Labels, text boxes, check boxes, combo boxes and buttons; stacked or tabular layout; VBA event procedures; atomic build-then-swap replacement | provisional |
| Modules & text I/O | Standard and class modules; SaveAsText/LoadFromText for forms, reports, macros, queries and modules, stored as UTF-8 |
provisional |
| Decimal columns | Column.decimal(precision=…, scale=…) via ADO DDL |
provisional |
Reports, linked tables, VBA references, and the declarative build/plan/apply workflow are planned
(see the roadmap).
PyAccessKit reaches the database in one of three ways and picks one for you (engine="auto"):
| Engine | When | Notes |
|---|---|---|
| In-process DAO | Python and Office have the same bitness | Fastest; never starts MSACCESS.EXE; enough for tables, relationships, queries and data |
| Access-hosted DAO | Bitness differs, or engine="access" |
A hidden Access instance that PyAccessKit owns hosts DAO; the database is not opened in the Access UI, so no startup code runs |
| Design session | First use of forms, modules or text I/O | The database becomes Access's current database; handles such as db.tables["X"] keep working across the switch |
| Python | Office | In-process DAO | Access transports |
|---|---|---|---|
| 64-bit | 64-bit | ✅ | ✅ |
| 32-bit | 32-bit | ✅ | ✅ |
| 64-bit | 32-bit (common with Microsoft 365) | ❌ | ✅ |
| 32-bit | 64-bit | ❌ | ✅ |
pyaccesskit doctor explains what applies to your machine.
- Never touches your own Access windows. PyAccessKit always starts a new Access instance
(
CoCreateInstanceEx, local server). It never attaches to a running Access (whichDispatch()does), so it can never close your work. - No orphaned
MSACCESS.EXE. Every Access process it starts is identified by PID, creation time and image, and placed in a kill-on-close job object. If Python crashes, Windows ends the process. The process is also recorded in an ownership ledger:pyaccesskit cleanupends leftovers from crashed sessions, and only when their Python owner is gone. Processes it did not start are never touched. - Exceptions clean up too. Closing runs every cleanup step even if one fails. Cleanup failures are attached as notes to the exception that caused the close, never masking it.
- Atomic creation.
AccessDatabase.create()builds in a hidden sibling file and moves it into place only on success. Tables are created all-or-nothing, and forms are built under a temporary name and swapped in. - No hangs on hidden dialogs. A watchdog watches the windows of our Access process. Unexpected
dialogs are dismissed and reported as
AccessDialogErrorwith their text. Calls that exceedcall_timeoutend the owned process, and Ctrl+C works during long calls. - No startup code by default. Databases are opened with macros disabled. Read-only sessions never open
the database in the Access UI, so
AutoExecand startup forms do not run.
COM errors are translated into specific exceptions. Each one carries what PyAccessKit was doing, the object involved, and the original Access or DAO error:
from pyaccesskit import AccessDatabase, IntegrityViolationError
with AccessDatabase.open("crm.accdb") as db:
try:
db.execute("INSERT INTO Orders (CustomerID, Total) VALUES (999, 10)")
except IntegrityViolationError as exc:
print(exc) # what failed, with Access's own explanation
print(exc.details.number) # 3201: there is no related record in CustomersMost mistakes are caught before Access is involved at all. A duplicate table name, an unknown column in a
relationship, or incompatible key types each raise a precise ObjectExistsError, SpecError or
RelationshipError.
pyaccesskit doctor [--json] [--probe] # environment report (exit code 3 if nothing works)
pyaccesskit inspect DB [--json] [--counts] # tables, relationships, queries, objects; read-only
pyaccesskit cleanup [--dry-run] [--json] # end orphaned Access processes started by PyAccessKit
pyaccesskit guide [--path] # the guide for AI coding agents
pyaccesskit schema [KIND] # JSON Schema of the specsExit codes: 0 success, 1 error, 2 usage error, 3 environment unusable.
- Getting started and recipes
- Building with AI agents:
pyaccesskit guideprints a version-matched guide that lets coding agents write Access applications with PyAccessKit;pyaccesskit schemaprints the JSON Schemas of the specs; the site publishesllms.txtandllms-full.txt. - Concepts: engines, lifecycle, specs
- Guides: tables, relationships, queries, forms, modules, text I/O
- Reference: errors, data types, command line, and an API reference generated from the docstrings
- Environment & troubleshooting, FAQ, contributing, architecture decisions
- Examples:
examples/(04_inventory_app.pyis the complete, tested application the agent guide walks through)
MIT. See LICENSE.