pyaccesskit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyaccesskit/AGENT_GUIDE.md +455 -0
- pyaccesskit/__init__.py +167 -0
- pyaccesskit/__main__.py +6 -0
- pyaccesskit/_backends/__init__.py +0 -0
- pyaccesskit/_backends/access/__init__.py +1 -0
- pyaccesskit/_backends/access/design.py +415 -0
- pyaccesskit/_backends/dao/__init__.py +1 -0
- pyaccesskit/_backends/dao/profile.py +40 -0
- pyaccesskit/_backends/dao/schema.py +805 -0
- pyaccesskit/_backends/dao/typemap.py +390 -0
- pyaccesskit/_backends/fake/__init__.py +3 -0
- pyaccesskit/_backends/fake/backend.py +680 -0
- pyaccesskit/_backends/protocols.py +339 -0
- pyaccesskit/_com/__init__.py +1 -0
- pyaccesskit/_com/constants.py +394 -0
- pyaccesskit/_com/dispatch.py +50 -0
- pyaccesskit/_com/errors.py +184 -0
- pyaccesskit/_com/gateway.py +199 -0
- pyaccesskit/_com/raw.py +164 -0
- pyaccesskit/_com/runtime.py +39 -0
- pyaccesskit/_com/variants.py +72 -0
- pyaccesskit/_engines/__init__.py +48 -0
- pyaccesskit/_engines/access.py +300 -0
- pyaccesskit/_engines/inproc.py +148 -0
- pyaccesskit/_engines/probe.py +231 -0
- pyaccesskit/_ledger.py +158 -0
- pyaccesskit/_ops/__init__.py +0 -0
- pyaccesskit/_ops/design.py +127 -0
- pyaccesskit/_ops/schema.py +471 -0
- pyaccesskit/_session/__init__.py +1 -0
- pyaccesskit/_session/protocols.py +78 -0
- pyaccesskit/_session/session.py +354 -0
- pyaccesskit/_text/__init__.py +0 -0
- pyaccesskit/_text/codec.py +114 -0
- pyaccesskit/_version.py +3 -0
- pyaccesskit/_win/__init__.py +1 -0
- pyaccesskit/_win/access_process.py +348 -0
- pyaccesskit/_win/console.py +56 -0
- pyaccesskit/_win/inspector.py +53 -0
- pyaccesskit/_win/job.py +65 -0
- pyaccesskit/_win/processes.py +159 -0
- pyaccesskit/_win/watchdog.py +253 -0
- pyaccesskit/cli/__init__.py +10 -0
- pyaccesskit/cli/_output.py +101 -0
- pyaccesskit/cli/agent.py +99 -0
- pyaccesskit/cli/app.py +54 -0
- pyaccesskit/cli/cleanup.py +56 -0
- pyaccesskit/cli/doctor.py +101 -0
- pyaccesskit/cli/inspection.py +223 -0
- pyaccesskit/database.py +296 -0
- pyaccesskit/diagnostics.py +319 -0
- pyaccesskit/enums.py +258 -0
- pyaccesskit/errors.py +407 -0
- pyaccesskit/forms/__init__.py +45 -0
- pyaccesskit/forms/builder.py +295 -0
- pyaccesskit/forms/collection.py +117 -0
- pyaccesskit/forms/controls.py +157 -0
- pyaccesskit/forms/layout.py +300 -0
- pyaccesskit/forms/spec.py +169 -0
- pyaccesskit/forms/vba.py +138 -0
- pyaccesskit/maintenance.py +32 -0
- pyaccesskit/modules.py +101 -0
- pyaccesskit/objects.py +81 -0
- pyaccesskit/options.py +40 -0
- pyaccesskit/properties.py +74 -0
- pyaccesskit/py.typed +0 -0
- pyaccesskit/queries.py +190 -0
- pyaccesskit/relationships.py +143 -0
- pyaccesskit/schema/__init__.py +73 -0
- pyaccesskit/schema/_base.py +55 -0
- pyaccesskit/schema/_reserved_words.py +55 -0
- pyaccesskit/schema/columns.py +609 -0
- pyaccesskit/schema/compat.py +57 -0
- pyaccesskit/schema/expressions.py +162 -0
- pyaccesskit/schema/indexes.py +114 -0
- pyaccesskit/schema/names.py +122 -0
- pyaccesskit/schema/queries.py +192 -0
- pyaccesskit/schema/relationships.py +132 -0
- pyaccesskit/schema/tables.py +178 -0
- pyaccesskit/tables.py +333 -0
- pyaccesskit/units.py +301 -0
- pyaccesskit-0.1.0.dist-info/METADATA +201 -0
- pyaccesskit-0.1.0.dist-info/RECORD +86 -0
- pyaccesskit-0.1.0.dist-info/WHEEL +4 -0
- pyaccesskit-0.1.0.dist-info/entry_points.txt +2 -0
- pyaccesskit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""Environment diagnostics: which engines PyAccessKit can use on this machine, and why not.
|
|
2
|
+
|
|
3
|
+
``pyaccesskit doctor`` prints this report; :func:`diagnose` returns it as data (:meth:`Diagnosis.to_dict`
|
|
4
|
+
gives a JSON-ready form). Without ``probe=True`` nothing is started: the report comes from the registry, file
|
|
5
|
+
headers and loading DAO in-process. With ``probe=True`` a scratch database is built with each usable engine,
|
|
6
|
+
in an Access process owned (and closed) by PyAccessKit.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import dataclasses
|
|
12
|
+
import importlib.metadata
|
|
13
|
+
import platform
|
|
14
|
+
import struct
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import time
|
|
18
|
+
import warnings
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Literal
|
|
22
|
+
|
|
23
|
+
from pyaccesskit._version import __version__
|
|
24
|
+
from pyaccesskit.errors import AccessNameWarning, PyAccessKitError
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"AccessInstallation",
|
|
28
|
+
"Diagnosis",
|
|
29
|
+
"EngineCheck",
|
|
30
|
+
"OwnedProcessReport",
|
|
31
|
+
"ProbeReport",
|
|
32
|
+
"diagnose",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
EngineName = Literal["dao", "access"]
|
|
36
|
+
|
|
37
|
+
_INSTALL_ADVICE = (
|
|
38
|
+
"Install Microsoft Access (Microsoft 365, 2016 or later) for full functionality, or the Microsoft 365 "
|
|
39
|
+
"Access Runtime with a Python of the same bitness for DAO-only work."
|
|
40
|
+
)
|
|
41
|
+
_AC_SYSCMD_RUNTIME = 6
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class EngineCheck:
|
|
46
|
+
"""Whether one engine can be used from this Python, with an explanation."""
|
|
47
|
+
|
|
48
|
+
engine: EngineName
|
|
49
|
+
available: bool
|
|
50
|
+
detail: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class AccessInstallation:
|
|
55
|
+
"""Microsoft Access as registered for automation."""
|
|
56
|
+
|
|
57
|
+
progid: str
|
|
58
|
+
registered_version: str | None
|
|
59
|
+
executable: str | None
|
|
60
|
+
version: str | None
|
|
61
|
+
bits: int | None
|
|
62
|
+
click_to_run: bool
|
|
63
|
+
products: tuple[str, ...] = ()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class OwnedProcessReport:
|
|
68
|
+
"""An Access process recorded in the ownership ledger, and what ``cleanup`` would do with it."""
|
|
69
|
+
|
|
70
|
+
pid: int
|
|
71
|
+
owner_pid: int
|
|
72
|
+
database: str | None
|
|
73
|
+
status: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class ProbeReport:
|
|
78
|
+
"""The outcome of building a scratch database with one engine."""
|
|
79
|
+
|
|
80
|
+
engine: EngineName
|
|
81
|
+
ok: bool
|
|
82
|
+
seconds: float
|
|
83
|
+
detail: str
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class Diagnosis:
|
|
88
|
+
"""Everything ``pyaccesskit doctor`` reports.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
auto_engine: What ``engine="auto"`` selects here (``None``: nothing is usable).
|
|
92
|
+
engines: Availability of in-process DAO and of Microsoft Access.
|
|
93
|
+
ace_oledb: ACE OLEDB providers loadable in-process (used for Decimal columns with in-process DAO).
|
|
94
|
+
owned_processes: Ledger entries of Access processes started by PyAccessKit.
|
|
95
|
+
probes: Results of ``probe=True`` runs.
|
|
96
|
+
problems: Reasons PyAccessKit cannot work (or cannot work fully).
|
|
97
|
+
notes: Useful facts and advice that are not problems.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
pyaccesskit_version: str
|
|
101
|
+
python_version: str
|
|
102
|
+
python_bits: int
|
|
103
|
+
python_executable: str
|
|
104
|
+
operating_system: str
|
|
105
|
+
pywin32_version: str | None
|
|
106
|
+
access: AccessInstallation | None
|
|
107
|
+
engines: tuple[EngineCheck, ...]
|
|
108
|
+
ace_oledb: tuple[str, ...]
|
|
109
|
+
auto_engine: EngineName | None
|
|
110
|
+
owned_processes: tuple[OwnedProcessReport, ...]
|
|
111
|
+
probes: tuple[ProbeReport, ...]
|
|
112
|
+
problems: tuple[str, ...]
|
|
113
|
+
notes: tuple[str, ...]
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def usable(self) -> bool:
|
|
117
|
+
"""At least one engine is available and every probe that ran succeeded."""
|
|
118
|
+
return self.auto_engine is not None and all(probe.ok for probe in self.probes)
|
|
119
|
+
|
|
120
|
+
def to_dict(self) -> dict[str, Any]:
|
|
121
|
+
"""A JSON-ready dictionary (includes ``usable``)."""
|
|
122
|
+
data = dataclasses.asdict(self)
|
|
123
|
+
data["usable"] = self.usable
|
|
124
|
+
return data
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _package_version(name: str) -> str | None:
|
|
128
|
+
try:
|
|
129
|
+
return importlib.metadata.version(name)
|
|
130
|
+
except importlib.metadata.PackageNotFoundError:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def diagnose(*, probe: bool = False, progid: str = "Access.Application") -> Diagnosis:
|
|
135
|
+
"""Inspect this machine and report which PyAccessKit engines work.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
probe: Also build a scratch database with every available engine (starts and closes an owned
|
|
139
|
+
Access process; takes a few seconds).
|
|
140
|
+
progid: The Access ProgID to check (e.g. ``"Access.Application.16"`` on multi-version machines).
|
|
141
|
+
"""
|
|
142
|
+
bits = struct.calcsize("P") * 8
|
|
143
|
+
base: dict[str, Any] = {
|
|
144
|
+
"pyaccesskit_version": __version__,
|
|
145
|
+
"python_version": platform.python_version(),
|
|
146
|
+
"python_bits": bits,
|
|
147
|
+
"python_executable": sys.executable,
|
|
148
|
+
"operating_system": platform.platform(),
|
|
149
|
+
"pywin32_version": _package_version("pywin32"),
|
|
150
|
+
}
|
|
151
|
+
problems: list[str] = []
|
|
152
|
+
notes: list[str] = []
|
|
153
|
+
empty: dict[str, Any] = {
|
|
154
|
+
"access": None,
|
|
155
|
+
"engines": (),
|
|
156
|
+
"ace_oledb": (),
|
|
157
|
+
"auto_engine": None,
|
|
158
|
+
"owned_processes": (),
|
|
159
|
+
"probes": (),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if sys.platform != "win32":
|
|
163
|
+
problems.append(
|
|
164
|
+
"Microsoft Access automation needs Windows; here PyAccessKit can only build and validate specs."
|
|
165
|
+
)
|
|
166
|
+
return Diagnosis(**base, **empty, problems=tuple(problems), notes=())
|
|
167
|
+
gil_check = getattr(sys, "_is_gil_enabled", None)
|
|
168
|
+
if gil_check is not None and not gil_check():
|
|
169
|
+
problems.append(
|
|
170
|
+
"free-threaded Python builds are not supported (COM apartments need the GIL)"
|
|
171
|
+
)
|
|
172
|
+
try:
|
|
173
|
+
from pyaccesskit._engines import probe as facts
|
|
174
|
+
except ImportError as exc:
|
|
175
|
+
problems.append(f"pywin32 is not usable ({exc}); install it with 'pip install pywin32'")
|
|
176
|
+
return Diagnosis(**base, **empty, problems=tuple(problems), notes=())
|
|
177
|
+
|
|
178
|
+
access_facts = facts.access_facts(progid)
|
|
179
|
+
c2r = facts.click_to_run()
|
|
180
|
+
access = (
|
|
181
|
+
AccessInstallation(
|
|
182
|
+
progid=progid,
|
|
183
|
+
registered_version=access_facts.current_version,
|
|
184
|
+
executable=access_facts.executable,
|
|
185
|
+
version=access_facts.version,
|
|
186
|
+
bits=access_facts.bits,
|
|
187
|
+
click_to_run=c2r is not None,
|
|
188
|
+
products=c2r.products if c2r else (),
|
|
189
|
+
)
|
|
190
|
+
if access_facts.registered
|
|
191
|
+
else None
|
|
192
|
+
)
|
|
193
|
+
dao = facts.inproc_dao()
|
|
194
|
+
dao_check = EngineCheck(
|
|
195
|
+
"dao",
|
|
196
|
+
dao.available,
|
|
197
|
+
f"DAO.DBEngine.120 loads in this {bits}-bit Python" if dao.available else dao.reason,
|
|
198
|
+
)
|
|
199
|
+
if access is None:
|
|
200
|
+
access_detail = f"{progid} is not registered"
|
|
201
|
+
elif access.executable is None:
|
|
202
|
+
access_detail = f"{progid} is registered but its executable was not found"
|
|
203
|
+
else:
|
|
204
|
+
access_detail = f"Access {access.version or '(unknown version)'}, {access.bits or '?'}-bit"
|
|
205
|
+
access_check = EngineCheck("access", access is not None, access_detail)
|
|
206
|
+
ace = tuple(name for name in facts.ACE_OLEDB_PROGIDS if facts.inproc_registration(name)[bits])
|
|
207
|
+
|
|
208
|
+
auto: EngineName | None = "dao" if dao.available else ("access" if access else None)
|
|
209
|
+
if dao.available and access:
|
|
210
|
+
notes.append(
|
|
211
|
+
"engine='auto' uses in-process DAO for schema work and switches to Microsoft Access for forms, "
|
|
212
|
+
"modules and text import/export."
|
|
213
|
+
)
|
|
214
|
+
elif dao.available:
|
|
215
|
+
notes.append(
|
|
216
|
+
"Forms, modules and text import/export need Microsoft Access (not the Runtime)."
|
|
217
|
+
)
|
|
218
|
+
if not ace:
|
|
219
|
+
notes.append(
|
|
220
|
+
"No ACE OLEDB provider is registered for this Python's bitness: Decimal columns cannot be "
|
|
221
|
+
"created in-process."
|
|
222
|
+
)
|
|
223
|
+
elif access:
|
|
224
|
+
other = access.bits if access.bits and access.bits != bits else None
|
|
225
|
+
advice = f" Install a {other}-bit Python to use DAO in-process (faster)." if other else ""
|
|
226
|
+
notes.append(
|
|
227
|
+
"engine='auto' reaches DAO through Microsoft Access, so every session starts an owned, hidden "
|
|
228
|
+
f"MSACCESS.EXE.{advice}"
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
problems.append(f"Neither in-process DAO nor Microsoft Access is usable. {_INSTALL_ADVICE}")
|
|
232
|
+
if access and access.version and not access.version.startswith("16."):
|
|
233
|
+
notes.append(
|
|
234
|
+
f"Access {access.version} is older than the supported floor (16.0, Access 2016); it may work."
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
owned = _owned_processes()
|
|
238
|
+
orphans = sum(1 for entry in owned if entry.status == "would-terminate")
|
|
239
|
+
if orphans:
|
|
240
|
+
notes.append(
|
|
241
|
+
f"{orphans} orphaned Access process(es) from crashed PyAccessKit sessions are still running; "
|
|
242
|
+
"run 'pyaccesskit cleanup' to end them."
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
probes: list[ProbeReport] = []
|
|
246
|
+
if probe:
|
|
247
|
+
if dao.available:
|
|
248
|
+
probes.append(_probe("dao"))
|
|
249
|
+
if access:
|
|
250
|
+
probes.append(_probe("access"))
|
|
251
|
+
problems.extend(f"{p.engine} probe failed: {p.detail}" for p in probes if not p.ok)
|
|
252
|
+
|
|
253
|
+
return Diagnosis(
|
|
254
|
+
**base,
|
|
255
|
+
access=access,
|
|
256
|
+
engines=(dao_check, access_check),
|
|
257
|
+
ace_oledb=ace,
|
|
258
|
+
auto_engine=auto,
|
|
259
|
+
owned_processes=owned,
|
|
260
|
+
probes=tuple(probes),
|
|
261
|
+
problems=tuple(problems),
|
|
262
|
+
notes=tuple(notes),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _owned_processes() -> tuple[OwnedProcessReport, ...]:
|
|
267
|
+
from pyaccesskit.maintenance import reap_orphans
|
|
268
|
+
|
|
269
|
+
return tuple(
|
|
270
|
+
OwnedProcessReport(
|
|
271
|
+
pid=result.entry.pid,
|
|
272
|
+
owner_pid=result.entry.owner_pid,
|
|
273
|
+
database=result.entry.database,
|
|
274
|
+
status=result.action,
|
|
275
|
+
)
|
|
276
|
+
for result in reap_orphans(dry_run=True)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _probe(engine: EngineName) -> ProbeReport:
|
|
281
|
+
"""Build a scratch database end to end with ``engine``."""
|
|
282
|
+
from pyaccesskit.database import AccessDatabase
|
|
283
|
+
from pyaccesskit.schema import Column
|
|
284
|
+
|
|
285
|
+
start = time.perf_counter()
|
|
286
|
+
detail = "created a table, wrote and read a row"
|
|
287
|
+
try:
|
|
288
|
+
with (
|
|
289
|
+
warnings.catch_warnings(),
|
|
290
|
+
tempfile.TemporaryDirectory(
|
|
291
|
+
prefix="pyaccesskit-probe-", ignore_cleanup_errors=True
|
|
292
|
+
) as folder,
|
|
293
|
+
AccessDatabase.create(Path(folder) / "probe.accdb", engine=engine) as db,
|
|
294
|
+
):
|
|
295
|
+
warnings.simplefilter("ignore", AccessNameWarning)
|
|
296
|
+
db.tables.create(
|
|
297
|
+
"PakProbe",
|
|
298
|
+
columns=[
|
|
299
|
+
Column.autonumber("ProbeID", primary_key=True),
|
|
300
|
+
Column.text("ProbeNote", length=20),
|
|
301
|
+
],
|
|
302
|
+
)
|
|
303
|
+
db.execute("INSERT INTO PakProbe (ProbeNote) VALUES ([note])", {"note": "ok"})
|
|
304
|
+
if db.fetch_all("SELECT ProbeNote FROM PakProbe") != [{"ProbeNote": "ok"}]:
|
|
305
|
+
raise PyAccessKitError("the probe row did not read back correctly")
|
|
306
|
+
if engine == "access":
|
|
307
|
+
db.modules.create(
|
|
308
|
+
"modPakProbe", "Public Function Ping() As Long\n Ping = 1\nEnd Function\n"
|
|
309
|
+
)
|
|
310
|
+
app = db.raw.access
|
|
311
|
+
runtime = bool(app.SysCmd(_AC_SYSCMD_RUNTIME))
|
|
312
|
+
detail = (
|
|
313
|
+
f"Access {app.Version} (build {app.Build}){' Runtime' if runtime else ''}: "
|
|
314
|
+
"created a table and a VBA module, wrote and read a row"
|
|
315
|
+
)
|
|
316
|
+
except Exception as exc:
|
|
317
|
+
message = str(exc) if isinstance(exc, PyAccessKitError) else f"{type(exc).__name__}: {exc}"
|
|
318
|
+
return ProbeReport(engine, False, round(time.perf_counter() - start, 2), message)
|
|
319
|
+
return ProbeReport(engine, True, round(time.perf_counter() - start, 2), detail)
|
pyaccesskit/enums.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Semantic enumerations used throughout the public API.
|
|
2
|
+
|
|
3
|
+
These enums describe Access concepts in Python terms. They are deliberately *not* the raw COM constants
|
|
4
|
+
(``acTextBox = 109`` etc.); the COM adapters translate between the two, so no magic numbers ever appear in
|
|
5
|
+
user code. Every enum is a :class:`~enum.StrEnum`, which keeps specs readable when serialized to JSON/YAML.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ControlKind",
|
|
14
|
+
"DataType",
|
|
15
|
+
"DialogPolicy",
|
|
16
|
+
"Engine",
|
|
17
|
+
"FormView",
|
|
18
|
+
"JoinType",
|
|
19
|
+
"LayoutKind",
|
|
20
|
+
"MacroSecurity",
|
|
21
|
+
"ModuleKind",
|
|
22
|
+
"NumberSize",
|
|
23
|
+
"ObjectKind",
|
|
24
|
+
"PropertyType",
|
|
25
|
+
"QueryKind",
|
|
26
|
+
"RowSourceType",
|
|
27
|
+
"ScrollBars",
|
|
28
|
+
"Section",
|
|
29
|
+
"Transport",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Engine(StrEnum):
|
|
34
|
+
"""Which automation engine a session may use (see ``AccessDatabase.open(engine=...)``)."""
|
|
35
|
+
|
|
36
|
+
AUTO = "auto"
|
|
37
|
+
"""In-process DAO when available, otherwise Access; upgrades to Access for design features."""
|
|
38
|
+
DAO = "dao"
|
|
39
|
+
"""In-process DAO only. Never starts ``MSACCESS.EXE``; design features raise ``CapabilityError``."""
|
|
40
|
+
ACCESS = "access"
|
|
41
|
+
"""Microsoft Access automation (works with any Python/Office bitness combination)."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Transport(StrEnum):
|
|
45
|
+
"""How a session currently reaches the database (reported by ``AccessDatabase.transport``)."""
|
|
46
|
+
|
|
47
|
+
DAO_INPROC = "dao-inproc"
|
|
48
|
+
"""DAO loaded into the Python process."""
|
|
49
|
+
ACCESS_HOSTED = "access-hosted"
|
|
50
|
+
"""DAO hosted by a hidden Access instance; the database is not opened in the Access UI."""
|
|
51
|
+
ACCESS_DESIGN = "access-design"
|
|
52
|
+
"""The database is Access's current database; forms, reports and modules are available."""
|
|
53
|
+
MEMORY = "memory"
|
|
54
|
+
"""In-memory fake used for tests and dry runs."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DataType(StrEnum):
|
|
58
|
+
"""Access field data types, named after the Access table designer."""
|
|
59
|
+
|
|
60
|
+
TEXT = "text"
|
|
61
|
+
"""Short Text (up to 255 characters)."""
|
|
62
|
+
LONG_TEXT = "long_text"
|
|
63
|
+
"""Long Text (formerly Memo)."""
|
|
64
|
+
NUMBER = "number"
|
|
65
|
+
"""Number; the storage size is given by :class:`NumberSize`."""
|
|
66
|
+
DECIMAL = "decimal"
|
|
67
|
+
"""Number with Field Size = Decimal (fixed precision and scale)."""
|
|
68
|
+
CURRENCY = "currency"
|
|
69
|
+
AUTONUMBER = "autonumber"
|
|
70
|
+
DATE_TIME = "date_time"
|
|
71
|
+
YES_NO = "yes_no"
|
|
72
|
+
HYPERLINK = "hyperlink"
|
|
73
|
+
OLE_OBJECT = "ole_object"
|
|
74
|
+
ATTACHMENT = "attachment"
|
|
75
|
+
CALCULATED = "calculated"
|
|
76
|
+
LARGE_NUMBER = "large_number"
|
|
77
|
+
DATE_TIME_EXTENDED = "date_time_extended"
|
|
78
|
+
BINARY = "binary"
|
|
79
|
+
"""Fixed/variable binary (not creatable from the Access UI)."""
|
|
80
|
+
MULTI_VALUE = "multi_value"
|
|
81
|
+
"""A multi-valued (complex) lookup field."""
|
|
82
|
+
UNKNOWN = "unknown"
|
|
83
|
+
"""A type PyAccessKit does not model yet; the raw DAO type is preserved on introspection."""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class NumberSize(StrEnum):
|
|
87
|
+
"""The *Field Size* of a Number field, exactly as offered by the Access table designer.
|
|
88
|
+
|
|
89
|
+
Note that Access's ``INTEGER`` is a **16-bit** integer; the default, ``LONG_INTEGER``, is 32-bit.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
BYTE = "byte"
|
|
93
|
+
INTEGER = "integer"
|
|
94
|
+
LONG_INTEGER = "long_integer"
|
|
95
|
+
SINGLE = "single"
|
|
96
|
+
DOUBLE = "double"
|
|
97
|
+
REPLICATION_ID = "replication_id"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class JoinType(StrEnum):
|
|
101
|
+
"""Default join type of a relationship (as shown in the Relationships window)."""
|
|
102
|
+
|
|
103
|
+
INNER = "inner"
|
|
104
|
+
LEFT = "left"
|
|
105
|
+
"""Include all records from the primary table."""
|
|
106
|
+
RIGHT = "right"
|
|
107
|
+
"""Include all records from the foreign (related) table."""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class QueryKind(StrEnum):
|
|
111
|
+
"""Kind of a saved query (derived from DAO ``QueryDef.Type``)."""
|
|
112
|
+
|
|
113
|
+
SELECT = "select"
|
|
114
|
+
CROSSTAB = "crosstab"
|
|
115
|
+
DELETE = "delete"
|
|
116
|
+
UPDATE = "update"
|
|
117
|
+
APPEND = "append"
|
|
118
|
+
MAKE_TABLE = "make_table"
|
|
119
|
+
DDL = "ddl"
|
|
120
|
+
PASS_THROUGH = "pass_through"
|
|
121
|
+
UNION = "union"
|
|
122
|
+
PASS_THROUGH_BULK = "pass_through_bulk"
|
|
123
|
+
COMPOUND = "compound"
|
|
124
|
+
PROCEDURE = "procedure"
|
|
125
|
+
ACTION = "action"
|
|
126
|
+
UNKNOWN = "unknown"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ObjectKind(StrEnum):
|
|
130
|
+
"""Kinds of objects stored in an Access database."""
|
|
131
|
+
|
|
132
|
+
TABLE = "table"
|
|
133
|
+
QUERY = "query"
|
|
134
|
+
FORM = "form"
|
|
135
|
+
REPORT = "report"
|
|
136
|
+
MACRO = "macro"
|
|
137
|
+
MODULE = "module"
|
|
138
|
+
# Sub-objects used in error messages.
|
|
139
|
+
FIELD = "field"
|
|
140
|
+
INDEX = "index"
|
|
141
|
+
RELATIONSHIP = "relationship"
|
|
142
|
+
PROPERTY = "property"
|
|
143
|
+
CONTROL = "control"
|
|
144
|
+
DATABASE = "database"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ModuleKind(StrEnum):
|
|
148
|
+
"""VBA module kinds."""
|
|
149
|
+
|
|
150
|
+
STANDARD = "standard"
|
|
151
|
+
CLASS = "class"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class FormView(StrEnum):
|
|
155
|
+
"""A form's *Default View*."""
|
|
156
|
+
|
|
157
|
+
SINGLE = "single"
|
|
158
|
+
CONTINUOUS = "continuous"
|
|
159
|
+
DATASHEET = "datasheet"
|
|
160
|
+
SPLIT = "split"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ScrollBars(StrEnum):
|
|
164
|
+
"""A form's *Scroll Bars* setting."""
|
|
165
|
+
|
|
166
|
+
NEITHER = "neither"
|
|
167
|
+
HORIZONTAL = "horizontal"
|
|
168
|
+
VERTICAL = "vertical"
|
|
169
|
+
BOTH = "both"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Section(StrEnum):
|
|
173
|
+
"""Form sections a control can be placed in."""
|
|
174
|
+
|
|
175
|
+
DETAIL = "detail"
|
|
176
|
+
HEADER = "header"
|
|
177
|
+
FOOTER = "footer"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class LayoutKind(StrEnum):
|
|
181
|
+
"""How a form's controls are arranged when they have no explicit position."""
|
|
182
|
+
|
|
183
|
+
AUTO = "auto"
|
|
184
|
+
"""``STACKED`` for single/split forms, ``TABULAR`` for continuous and datasheet forms."""
|
|
185
|
+
STACKED = "stacked"
|
|
186
|
+
"""One control per row with its label on the left (like Access's stacked layout)."""
|
|
187
|
+
TABULAR = "tabular"
|
|
188
|
+
"""Labels in the form header and one row of controls in the detail section."""
|
|
189
|
+
NONE = "none"
|
|
190
|
+
"""No automatic layout: every control must be positioned with ``at=``."""
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class RowSourceType(StrEnum):
|
|
194
|
+
"""Where a combo box gets its rows from."""
|
|
195
|
+
|
|
196
|
+
TABLE_QUERY = "table_query"
|
|
197
|
+
"""A table name, query name or SQL statement."""
|
|
198
|
+
VALUE_LIST = "value_list"
|
|
199
|
+
"""A semicolon-separated list of values."""
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class ControlKind(StrEnum):
|
|
203
|
+
"""Kinds of form controls (for building and introspection)."""
|
|
204
|
+
|
|
205
|
+
LABEL = "label"
|
|
206
|
+
TEXTBOX = "textbox"
|
|
207
|
+
CHECKBOX = "checkbox"
|
|
208
|
+
COMBOBOX = "combobox"
|
|
209
|
+
LISTBOX = "listbox"
|
|
210
|
+
BUTTON = "button"
|
|
211
|
+
OPTION_GROUP = "option_group"
|
|
212
|
+
OPTION_BUTTON = "option_button"
|
|
213
|
+
TOGGLE_BUTTON = "toggle_button"
|
|
214
|
+
SUBFORM = "subform"
|
|
215
|
+
IMAGE = "image"
|
|
216
|
+
LINE = "line"
|
|
217
|
+
RECTANGLE = "rectangle"
|
|
218
|
+
TAB_CONTROL = "tab_control"
|
|
219
|
+
PAGE = "page"
|
|
220
|
+
ATTACHMENT = "attachment"
|
|
221
|
+
OTHER = "other"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class MacroSecurity(StrEnum):
|
|
225
|
+
"""What Access may run when PyAccessKit opens a database as the current database."""
|
|
226
|
+
|
|
227
|
+
DISABLE = "disable"
|
|
228
|
+
"""Disable all macros and VBA (``msoAutomationSecurityForceDisable``). Default; deterministic."""
|
|
229
|
+
USE_UI = "ui"
|
|
230
|
+
"""Follow the user's Trust Center settings (``msoAutomationSecurityByUI``)."""
|
|
231
|
+
ENABLE = "enable"
|
|
232
|
+
"""Enable all macros and VBA, including AutoExec (``msoAutomationSecurityLow``)."""
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class DialogPolicy(StrEnum):
|
|
236
|
+
"""What to do when Access shows a modal dialog during an automated call."""
|
|
237
|
+
|
|
238
|
+
FAIL = "fail"
|
|
239
|
+
"""Dismiss the dialog and raise ``AccessDialogError`` (default)."""
|
|
240
|
+
WARN = "warn"
|
|
241
|
+
"""Dismiss the dialog and emit an ``AccessDialogWarning``."""
|
|
242
|
+
OFF = "off"
|
|
243
|
+
"""Do not watch for dialogs (a blocking dialog can then hang until ``call_timeout``)."""
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class PropertyType(StrEnum):
|
|
247
|
+
"""Data types for user-defined DAO properties (``CreateProperty``)."""
|
|
248
|
+
|
|
249
|
+
BOOLEAN = "boolean"
|
|
250
|
+
BYTE = "byte"
|
|
251
|
+
INTEGER = "integer"
|
|
252
|
+
LONG = "long"
|
|
253
|
+
CURRENCY = "currency"
|
|
254
|
+
SINGLE = "single"
|
|
255
|
+
DOUBLE = "double"
|
|
256
|
+
DATE_TIME = "date_time"
|
|
257
|
+
TEXT = "text"
|
|
258
|
+
MEMO = "memo"
|