Tables¶
Column types¶
| Access designer | PyAccessKit | Notes |
|---|---|---|
| Short Text | Column.text(name, length=255) |
allow_zero_length=False, unicode_compression=True, input_mask= |
| Long Text | Column.long_text(name, rich_text=False, append_only=False) |
|
| Number | Column.number(name, size=NumberSize.LONG_INTEGER) |
Sizes: BYTE, INTEGER (16-bit), LONG_INTEGER, SINGLE, DOUBLE, REPLICATION_ID; decimal_places= |
| Number (Decimal) | Column.decimal(name, precision=18, scale=0) |
Created with ADO DDL (DAO cannot create decimals) |
| Currency | Column.currency(name) |
Exact, 4 decimal places |
| AutoNumber | Column.autonumber(name, replication_id=False) |
Incrementing Long Integer, or a GUID (Replication ID) |
| Date/Time | Column.date_time(name) |
|
| Yes/No | Column.yes_no(name) |
Shown as a check box, as in Access |
| Hyperlink | Column.hyperlink(name) |
|
| OLE Object | Column.ole_object(name) |
Every column also accepts required, default, validation_rule, validation_text, description,
caption, format, and properties={...} (other Access field properties, set verbatim). Options that do
not apply to a type are rejected.
Columns PyAccessKit cannot create yet (Attachment, calculated, multi-valued, Large Number, Date/Time
Extended) still appear in to_spec(), as UnsupportedColumn entries that name the Access type, so
introspection never silently drops a field.
Defaults and expressions¶
from datetime import date
from pyaccesskit import Column, Expr
Column.text("Country", default="Belgium") # rendered as "Belgium"
Column.text("Name", default='O"Brien') # quotes are escaped for you
Column.currency("Total", default=0)
Column.yes_no("IsActive", default=True)
Column.date_time("Since", default=date(2026, 1, 1))
Column.date_time("CreatedAt", default=Expr("Now()")) # an Access expression, passed verbatim
On Text columns, a string default is always a value: default="Now()" means the four characters
Now(). On other columns, a string must be a literal of the column's type ("42", "True",
"#2026-01-31#"). Anything else, such as default="Now()" on a Date/Time column, is rejected with a
hint to use Expr(...), which prevents the classic unquoted-default bug.
Keys and indexes¶
from pyaccesskit import Column, IndexSpec
db.tables.create(
"People",
columns=[
Column.autonumber("PersonID", primary_key=True),
Column.text("LastName", length=80, indexed=True), # non-unique index "LastName"
Column.text("FirstName", length=80),
Column.text("Email", length=255, unique=True), # unique index "Email"
],
indexes=[IndexSpec.on("ixFullName", "LastName", "FirstName")],
)
db.tables.create(
"Enrollment",
columns=[Column.number("PersonID"), Column.number("CourseID")],
primary_key=["PersonID", "CourseID"], # composite primary key, named PrimaryKey as in Access
)
IndexSpec.on(name, *columns, unique=False, ignore_nulls=False, required=False) accepts
("Column", "desc") pairs for descending keys. Limits are checked up front: 32 indexes per table,
10 columns per index, one primary key.
Changing tables¶
table = db.tables["People"]
table.add_column(Column.text("Phone", length=30))
table.rename_column("Phone", "Mobile")
table.drop_column("Mobile")
table.create_index(IndexSpec.on("ixEmailLast", "Email", "LastName", unique=True))
table.drop_index("ixEmailLast")
table.description = "Everyone we know"
table.rename("Contacts")
db.tables.drop("Contacts", drop_relationships=True)
Table creation is atomic: the table, its fields and its indexes are built and appended in one step. If
anything fails afterwards (for example, a property), the table is removed again. Changing a column's type
or order is not supported yet. It needs copy-and-migrate, which will come with plan/apply.
Properties¶
Access stores many settings as DAO properties that only exist once set. properties bags read and write
them:
from pyaccesskit import PropertyType
field = db.tables["People"].fields["Email"]
field.properties["Caption"] = "E-mail address"
db.properties["AppTitle"] = "My application"
db.tables["People"].properties.set(
"MyTag", 7, PropertyType.BYTE
) # force the DAO type of a new property
print(db.properties.get("StartUpForm")) # None if not set
Reading a table back¶
table = db.tables["People"]
[field.name for field in table.fields]
table.fields["Email"].data_type, table.fields["Email"].size
table.primary_key, table.indexes
table.record_count()
spec = table.to_spec() # canonical TableSpec; relationship-owned indexes are left out