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.
Files changed (86) hide show
  1. pyaccesskit/AGENT_GUIDE.md +455 -0
  2. pyaccesskit/__init__.py +167 -0
  3. pyaccesskit/__main__.py +6 -0
  4. pyaccesskit/_backends/__init__.py +0 -0
  5. pyaccesskit/_backends/access/__init__.py +1 -0
  6. pyaccesskit/_backends/access/design.py +415 -0
  7. pyaccesskit/_backends/dao/__init__.py +1 -0
  8. pyaccesskit/_backends/dao/profile.py +40 -0
  9. pyaccesskit/_backends/dao/schema.py +805 -0
  10. pyaccesskit/_backends/dao/typemap.py +390 -0
  11. pyaccesskit/_backends/fake/__init__.py +3 -0
  12. pyaccesskit/_backends/fake/backend.py +680 -0
  13. pyaccesskit/_backends/protocols.py +339 -0
  14. pyaccesskit/_com/__init__.py +1 -0
  15. pyaccesskit/_com/constants.py +394 -0
  16. pyaccesskit/_com/dispatch.py +50 -0
  17. pyaccesskit/_com/errors.py +184 -0
  18. pyaccesskit/_com/gateway.py +199 -0
  19. pyaccesskit/_com/raw.py +164 -0
  20. pyaccesskit/_com/runtime.py +39 -0
  21. pyaccesskit/_com/variants.py +72 -0
  22. pyaccesskit/_engines/__init__.py +48 -0
  23. pyaccesskit/_engines/access.py +300 -0
  24. pyaccesskit/_engines/inproc.py +148 -0
  25. pyaccesskit/_engines/probe.py +231 -0
  26. pyaccesskit/_ledger.py +158 -0
  27. pyaccesskit/_ops/__init__.py +0 -0
  28. pyaccesskit/_ops/design.py +127 -0
  29. pyaccesskit/_ops/schema.py +471 -0
  30. pyaccesskit/_session/__init__.py +1 -0
  31. pyaccesskit/_session/protocols.py +78 -0
  32. pyaccesskit/_session/session.py +354 -0
  33. pyaccesskit/_text/__init__.py +0 -0
  34. pyaccesskit/_text/codec.py +114 -0
  35. pyaccesskit/_version.py +3 -0
  36. pyaccesskit/_win/__init__.py +1 -0
  37. pyaccesskit/_win/access_process.py +348 -0
  38. pyaccesskit/_win/console.py +56 -0
  39. pyaccesskit/_win/inspector.py +53 -0
  40. pyaccesskit/_win/job.py +65 -0
  41. pyaccesskit/_win/processes.py +159 -0
  42. pyaccesskit/_win/watchdog.py +253 -0
  43. pyaccesskit/cli/__init__.py +10 -0
  44. pyaccesskit/cli/_output.py +101 -0
  45. pyaccesskit/cli/agent.py +99 -0
  46. pyaccesskit/cli/app.py +54 -0
  47. pyaccesskit/cli/cleanup.py +56 -0
  48. pyaccesskit/cli/doctor.py +101 -0
  49. pyaccesskit/cli/inspection.py +223 -0
  50. pyaccesskit/database.py +296 -0
  51. pyaccesskit/diagnostics.py +319 -0
  52. pyaccesskit/enums.py +258 -0
  53. pyaccesskit/errors.py +407 -0
  54. pyaccesskit/forms/__init__.py +45 -0
  55. pyaccesskit/forms/builder.py +295 -0
  56. pyaccesskit/forms/collection.py +117 -0
  57. pyaccesskit/forms/controls.py +157 -0
  58. pyaccesskit/forms/layout.py +300 -0
  59. pyaccesskit/forms/spec.py +169 -0
  60. pyaccesskit/forms/vba.py +138 -0
  61. pyaccesskit/maintenance.py +32 -0
  62. pyaccesskit/modules.py +101 -0
  63. pyaccesskit/objects.py +81 -0
  64. pyaccesskit/options.py +40 -0
  65. pyaccesskit/properties.py +74 -0
  66. pyaccesskit/py.typed +0 -0
  67. pyaccesskit/queries.py +190 -0
  68. pyaccesskit/relationships.py +143 -0
  69. pyaccesskit/schema/__init__.py +73 -0
  70. pyaccesskit/schema/_base.py +55 -0
  71. pyaccesskit/schema/_reserved_words.py +55 -0
  72. pyaccesskit/schema/columns.py +609 -0
  73. pyaccesskit/schema/compat.py +57 -0
  74. pyaccesskit/schema/expressions.py +162 -0
  75. pyaccesskit/schema/indexes.py +114 -0
  76. pyaccesskit/schema/names.py +122 -0
  77. pyaccesskit/schema/queries.py +192 -0
  78. pyaccesskit/schema/relationships.py +132 -0
  79. pyaccesskit/schema/tables.py +178 -0
  80. pyaccesskit/tables.py +333 -0
  81. pyaccesskit/units.py +301 -0
  82. pyaccesskit-0.1.0.dist-info/METADATA +201 -0
  83. pyaccesskit-0.1.0.dist-info/RECORD +86 -0
  84. pyaccesskit-0.1.0.dist-info/WHEEL +4 -0
  85. pyaccesskit-0.1.0.dist-info/entry_points.txt +2 -0
  86. pyaccesskit-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,339 @@
1
+ """The typed seam between PyAccessKit's COM-free core and its COM adapters.
2
+
3
+ Backends exchange **specs and plain data only** — never COM objects. Operations are deliberately sized
4
+ like the steps of a future ``plan``/``apply`` (create table, add column, create relationship...).
5
+
6
+ Implementations:
7
+
8
+ * ``_backends.dao`` — DAO over any transport (in-process, Access-hosted, or ``CurrentDb``);
9
+ * ``_backends.access`` — ``Access.Application`` design features;
10
+ * ``_backends.fake`` — in-memory, used by the test-suite and kept honest by the contract tests.
11
+
12
+ Backends *may* assume their inputs are normalized specs whose names were validated; semantic
13
+ pre-validation (does the table exist? are the relationship types compatible?) happens in ``_ops`` so that
14
+ every backend reports problems the same way. Backends still translate engine errors into
15
+ :mod:`pyaccesskit.errors` exceptions.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from collections.abc import Mapping
21
+ from dataclasses import dataclass
22
+ from pathlib import Path
23
+ from typing import Any, Literal, Protocol
24
+
25
+ from pyaccesskit.enums import (
26
+ ControlKind,
27
+ DataType,
28
+ ObjectKind,
29
+ PropertyType,
30
+ QueryKind,
31
+ Section,
32
+ Transport,
33
+ )
34
+ from pyaccesskit.forms.layout import ResolvedForm
35
+ from pyaccesskit.schema import (
36
+ ColumnSpec,
37
+ IndexSpec,
38
+ PropertyValue,
39
+ QuerySpec,
40
+ RelationshipSpec,
41
+ TableSpec,
42
+ )
43
+ from pyaccesskit.units import Length
44
+
45
+ __all__ = [
46
+ "ControlInfo",
47
+ "DatabaseInfo",
48
+ "DesignBackend",
49
+ "FetchResult",
50
+ "ParameterInfo",
51
+ "PropertyTarget",
52
+ "QueryInfo",
53
+ "SchemaBackend",
54
+ "TableInfo",
55
+ ]
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class TableInfo:
60
+ """Summary of a table (cheap to list)."""
61
+
62
+ name: str
63
+ is_linked: bool = False
64
+ is_system: bool = False
65
+ is_hidden: bool = False
66
+ connect: str | None = None
67
+ source_table: str | None = None
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class QueryInfo:
72
+ """Summary of a saved query."""
73
+
74
+ name: str
75
+ kind: QueryKind
76
+ is_hidden: bool = False
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class ParameterInfo:
81
+ """A query parameter (name without brackets)."""
82
+
83
+ name: str
84
+ data_type: DataType
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class FetchResult:
89
+ """Rows returned by a SELECT: column names plus row tuples."""
90
+
91
+ columns: tuple[str, ...]
92
+ rows: list[tuple[Any, ...]]
93
+
94
+ def as_dicts(self) -> list[dict[str, Any]]:
95
+ """Rows as ``{column: value}`` dictionaries."""
96
+ return [dict(zip(self.columns, row, strict=True)) for row in self.rows]
97
+
98
+
99
+ @dataclass(frozen=True)
100
+ class ControlInfo:
101
+ """A control read back from a saved form."""
102
+
103
+ name: str
104
+ kind: ControlKind
105
+ section: Section | None
106
+ left: Length
107
+ top: Length
108
+ width: Length
109
+ height: Length
110
+ control_source: str | None = None
111
+ caption: str | None = None
112
+ parent: str | None = None
113
+ """For attached labels: the control the label belongs to."""
114
+
115
+
116
+ @dataclass(frozen=True)
117
+ class DatabaseInfo:
118
+ """Facts about the open database."""
119
+
120
+ path: Path
121
+ version: str
122
+ transport: Transport
123
+
124
+
125
+ @dataclass(frozen=True)
126
+ class PropertyTarget:
127
+ """Addresses the object whose DAO ``Properties`` collection is used."""
128
+
129
+ kind: Literal["database", "table", "field", "query"]
130
+ name: str | None = None
131
+ field: str | None = None
132
+
133
+ @classmethod
134
+ def database(cls) -> PropertyTarget:
135
+ """The database itself (``AppTitle``, ``StartUpForm``...)."""
136
+ return cls("database")
137
+
138
+ @classmethod
139
+ def table(cls, name: str) -> PropertyTarget:
140
+ """A table."""
141
+ return cls("table", name)
142
+
143
+ @classmethod
144
+ def column(cls, table: str, field: str) -> PropertyTarget:
145
+ """A field of a table."""
146
+ return cls("field", table, field)
147
+
148
+ @classmethod
149
+ def query(cls, name: str) -> PropertyTarget:
150
+ """A saved query."""
151
+ return cls("query", name)
152
+
153
+ def describe(self) -> str:
154
+ """Human-readable description for messages."""
155
+ if self.kind == "database":
156
+ return "the database"
157
+ if self.kind == "field":
158
+ return f"field {self.field!r} of table {self.name!r}"
159
+ return f"{self.kind} {self.name!r}"
160
+
161
+
162
+ class SchemaBackend(Protocol):
163
+ """Schema and data operations (implemented with DAO)."""
164
+
165
+ def database_info(self) -> DatabaseInfo:
166
+ """Facts about the open database."""
167
+ ...
168
+
169
+ # ------------------------------------------------------------------------------------ tables
170
+ def list_tables(self) -> list[TableInfo]:
171
+ """All tables, including system and linked tables."""
172
+ ...
173
+
174
+ def read_table(self, name: str) -> TableSpec:
175
+ """The normalized spec of a local table (relationship-owned indexes excluded)."""
176
+ ...
177
+
178
+ def count_indexes(self, table: str) -> int:
179
+ """Number of indexes on ``table`` *including* hidden relationship indexes (Access limit: 32)."""
180
+ ...
181
+
182
+ def create_table(self, spec: TableSpec) -> None:
183
+ """Create a table from a normalized spec, atomically (all or nothing)."""
184
+ ...
185
+
186
+ def drop_table(self, name: str) -> None:
187
+ """Delete a table."""
188
+ ...
189
+
190
+ def rename_table(self, old: str, new: str) -> None:
191
+ """Rename a table."""
192
+ ...
193
+
194
+ def add_column(self, table: str, column: ColumnSpec) -> None:
195
+ """Append a column (index shorthands are handled by the caller)."""
196
+ ...
197
+
198
+ def drop_column(self, table: str, column: str) -> None:
199
+ """Delete a column."""
200
+ ...
201
+
202
+ def rename_column(self, table: str, old: str, new: str) -> None:
203
+ """Rename a column."""
204
+ ...
205
+
206
+ def create_index(self, table: str, index: IndexSpec) -> None:
207
+ """Create an index."""
208
+ ...
209
+
210
+ def drop_index(self, table: str, name: str) -> None:
211
+ """Delete an index."""
212
+ ...
213
+
214
+ # ----------------------------------------------------------------------------- relationships
215
+ def list_relationships(self) -> list[RelationshipSpec]:
216
+ """All (non-inherited) relationships, normalized."""
217
+ ...
218
+
219
+ def create_relationship(self, spec: RelationshipSpec) -> None:
220
+ """Create a relationship from a normalized, pre-validated spec."""
221
+ ...
222
+
223
+ def drop_relationship(self, name: str) -> None:
224
+ """Delete a relationship."""
225
+ ...
226
+
227
+ # ----------------------------------------------------------------------------------- queries
228
+ def list_queries(self) -> list[QueryInfo]:
229
+ """All saved queries (including hidden ``~`` queries)."""
230
+ ...
231
+
232
+ def read_query(self, name: str) -> QuerySpec:
233
+ """The spec of a saved query (SQL as Access stored it)."""
234
+ ...
235
+
236
+ def query_parameters(self, name: str) -> list[ParameterInfo]:
237
+ """Declared and implicit parameters of a saved query."""
238
+ ...
239
+
240
+ def create_query(self, spec: QuerySpec) -> None:
241
+ """Create a saved query."""
242
+ ...
243
+
244
+ def set_query_sql(self, name: str, sql: str) -> None:
245
+ """Replace the SQL of a saved query."""
246
+ ...
247
+
248
+ def rename_query(self, old: str, new: str) -> None:
249
+ """Rename a saved query."""
250
+ ...
251
+
252
+ def drop_query(self, name: str) -> None:
253
+ """Delete a saved query."""
254
+ ...
255
+
256
+ # -------------------------------------------------------------------------------- properties
257
+ def get_property(self, target: PropertyTarget, name: str) -> PropertyValue:
258
+ """Read a DAO property. Raises ``ObjectNotFoundError`` (kind PROPERTY) if it does not exist."""
259
+ ...
260
+
261
+ def set_property(
262
+ self,
263
+ target: PropertyTarget,
264
+ name: str,
265
+ value: PropertyValue,
266
+ type: PropertyType | None = None,
267
+ ) -> None:
268
+ """Set (creating if needed) a DAO property."""
269
+ ...
270
+
271
+ def delete_property(self, target: PropertyTarget, name: str) -> None:
272
+ """Delete a user-defined DAO property."""
273
+ ...
274
+
275
+ def list_properties(self, target: PropertyTarget) -> dict[str, PropertyValue]:
276
+ """All readable properties of the target."""
277
+ ...
278
+
279
+ # -------------------------------------------------------------------------------------- data
280
+ def execute(self, sql: str, params: Mapping[str, Any] | None = None) -> int:
281
+ """Run an action statement; returns the number of records affected."""
282
+ ...
283
+
284
+ def fetch(
285
+ self, sql: str, params: Mapping[str, Any] | None = None, *, limit: int | None = None
286
+ ) -> FetchResult:
287
+ """Run a SELECT and return its rows."""
288
+ ...
289
+
290
+ def execute_saved(self, name: str, params: Mapping[str, Any] | None = None) -> int:
291
+ """Run a saved action query."""
292
+ ...
293
+
294
+ def fetch_saved(
295
+ self, name: str, params: Mapping[str, Any] | None = None, *, limit: int | None = None
296
+ ) -> FetchResult:
297
+ """Return the rows of a saved select query."""
298
+ ...
299
+
300
+ # --------------------------------------------------------------------------------- documents
301
+ def list_documents(self, kind: ObjectKind) -> list[str]:
302
+ """Names of forms, reports, macros or modules (read through DAO containers; no Access needed)."""
303
+ ...
304
+
305
+
306
+ class DesignBackend(Protocol):
307
+ """Operations that need Microsoft Access itself (``Access.Application``)."""
308
+
309
+ def list_objects(self, kind: ObjectKind) -> list[str]:
310
+ """Names of the forms, reports, macros or modules in the database."""
311
+ ...
312
+
313
+ def delete_object(self, kind: ObjectKind, name: str) -> None:
314
+ """Delete a form, report, macro or module."""
315
+ ...
316
+
317
+ def rename_object(self, kind: ObjectKind, old: str, new: str) -> None:
318
+ """Rename a form, report, macro or module."""
319
+ ...
320
+
321
+ def export_text(self, kind: ObjectKind, name: str) -> bytes:
322
+ """``SaveAsText`` bytes of an object (native encoding; see ``_text.codec``)."""
323
+ ...
324
+
325
+ def import_text(self, kind: ObjectKind, name: str, data: bytes) -> None:
326
+ """``LoadFromText`` from native-encoded bytes (replaces an existing object)."""
327
+ ...
328
+
329
+ def build_form(self, form: ResolvedForm, *, replace: bool) -> None:
330
+ """Create (or atomically replace) a form from a resolved layout."""
331
+ ...
332
+
333
+ def form_controls(self, name: str) -> list[ControlInfo]:
334
+ """Read the controls of a saved form."""
335
+ ...
336
+
337
+ def check_form_opens(self, name: str) -> None:
338
+ """Open the form in Form view (hidden) and close it again; raises if Access reports an error."""
339
+ ...
@@ -0,0 +1 @@
1
+ """COM boundary (pywin32). Imported lazily; never imported by ``import pyaccesskit``."""