pyOpenVBA 5.1.2__tar.gz → 5.1.3__tar.gz
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.
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/PKG-INFO +1 -1
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/pyproject.toml +1 -1
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/__init__.py +1 -1
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_index.py +6 -3
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_rows.py +30 -3
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/database.py +28 -11
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/.gitignore +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/LICENSE.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/README.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/architecture.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/ms-ovba-implementation-guide_v2.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/power_query.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/research/access_complex/README.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/research/access_designs/README.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/research/access_macros/README.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/research/access_write/README.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/research/pcode/README.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/docs/roadmap.md +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/__main__.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_deflate.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_host.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_oforms_pages.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_oforms_records.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_ppt_container.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/__init__.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_database.accdb +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_database_module.accdb +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_document.docm +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_excel_addin.xlam +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_presentation.pptm +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsb +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsm +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsx +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/engine_skeleton.accdb +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/engine_skeleton.mdb +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/form.blob +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/form.lvprop +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/form.propdata +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/form.prototypes +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/form.typeinfo +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/report.blob +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/report.lvprop +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/report.propdata +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/report.prototypes +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/designs/report.typeinfo +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/__init__.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_alloc.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_btree.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_collation.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_collation_general_legacy.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_compact.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_complex.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_datapage.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_ddl.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_designs.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_facade.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_format.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_lval.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_macros.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_pages.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_props.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_queries.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_schema.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_sql.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_storage.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_tdef.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_validate.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access/_vba.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/access_read.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/cfb.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/excel.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/exceptions.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/forms.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerpoint.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/__init__.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_binary.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_files.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_mashup.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_metadata.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_opc.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_package.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_refresh.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_section.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/_sheets.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/powerquery/workbook.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/vba.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/vba_pcode.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/word.py +0 -0
- {pyopenvba-5.1.2 → pyopenvba-5.1.3}/tests/fuzz_corpus/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyOpenVBA
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.3
|
|
4
4
|
Summary: Read and write VBA macros and Excel Power Query in pure Python, no dependencies.
|
|
5
5
|
Project-URL: Homepage, https://github.com/WilliamSmithEdward/pyOpenVBA
|
|
6
6
|
Project-URL: Repository, https://github.com/WilliamSmithEdward/pyOpenVBA
|
|
@@ -58,7 +58,7 @@ from pyopenvba.access._pages import (
|
|
|
58
58
|
PAGE_SIZE,
|
|
59
59
|
PageStore,
|
|
60
60
|
)
|
|
61
|
-
from pyopenvba.access._rows import decode_datetime
|
|
61
|
+
from pyopenvba.access._rows import decode_datetime, scaled_decimal, scaled_int
|
|
62
62
|
from pyopenvba.access._tdef import (
|
|
63
63
|
TYPE_BIGINT,
|
|
64
64
|
TYPE_BINARY,
|
|
@@ -463,7 +463,10 @@ def _encode_value(column: ColumnDef, value: object) -> bytes:
|
|
|
463
463
|
if code == TYPE_NUMERIC:
|
|
464
464
|
if not isinstance(value, (int, Decimal)):
|
|
465
465
|
raise AccessError(f"column {column.name!r}: {value!r} is not a Decimal")
|
|
466
|
-
|
|
466
|
+
# Through ``scaled_int``, so the key rounds a fractional value the
|
|
467
|
+
# way the row encoder does and carries a 29-digit one without the
|
|
468
|
+
# default context rounding it (GitHub issue #20).
|
|
469
|
+
scaled = scaled_int(Decimal(value), column.scale)
|
|
467
470
|
magnitude = abs(scaled).to_bytes(16, "big")
|
|
468
471
|
return b"\xff" + magnitude if scaled >= 0 else b"\x00" + _invert(magnitude)
|
|
469
472
|
if code == TYPE_GUID:
|
|
@@ -510,5 +513,5 @@ def _decode_fixed(column: ColumnDef, raw: bytes) -> object:
|
|
|
510
513
|
value = -Decimal(int.from_bytes(_invert(magnitude), "big"))
|
|
511
514
|
else:
|
|
512
515
|
raise AccessError(f"column {column.name!r}: Decimal key sign byte {sign:#04x}")
|
|
513
|
-
return value
|
|
516
|
+
return scaled_decimal(value, -column.scale)
|
|
514
517
|
raise AccessError(f"column {column.name!r}: no fixed key codec for {column.type_name}")
|
|
@@ -23,7 +23,7 @@ import datetime as _dt
|
|
|
23
23
|
import struct
|
|
24
24
|
import uuid
|
|
25
25
|
from dataclasses import dataclass
|
|
26
|
-
from decimal import ROUND_HALF_EVEN, Decimal
|
|
26
|
+
from decimal import ROUND_HALF_EVEN, Decimal, localcontext
|
|
27
27
|
|
|
28
28
|
from pyopenvba.access_read import AccessError
|
|
29
29
|
from pyopenvba.access._tdef import (
|
|
@@ -203,6 +203,33 @@ def encode_datetime(value: _dt.datetime) -> bytes:
|
|
|
203
203
|
return struct.pack("<d", number)
|
|
204
204
|
|
|
205
205
|
|
|
206
|
+
#: A Decimal column is a 16-byte magnitude, so a value carries up to 39
|
|
207
|
+
#: digits, while Python's default arithmetic context rounds at 28. Every
|
|
208
|
+
#: Decimal conversion here and in the index key codec runs at this
|
|
209
|
+
#: precision instead: scaling ``2**96 - 1`` through the default context
|
|
210
|
+
#: returns it five too high, which puts a key out of step with the row it
|
|
211
|
+
#: points at (GitHub issue #20).
|
|
212
|
+
DECIMAL_PREC = 64
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def scaled_decimal(value: Decimal | int, scale: int) -> Decimal:
|
|
216
|
+
"""``value`` shifted by ``10 ** scale``, with no context rounding."""
|
|
217
|
+
with localcontext() as ctx:
|
|
218
|
+
ctx.prec = DECIMAL_PREC
|
|
219
|
+
return Decimal(value).scaleb(scale)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def scaled_int(value: Decimal, scale: int) -> int:
|
|
223
|
+
"""``value`` shifted by ``10 ** scale`` and rounded to an integer.
|
|
224
|
+
|
|
225
|
+
Half-even, as Access rounds, and at a precision no Decimal a column
|
|
226
|
+
can hold will overflow.
|
|
227
|
+
"""
|
|
228
|
+
with localcontext() as ctx:
|
|
229
|
+
ctx.prec = DECIMAL_PREC
|
|
230
|
+
return int(Decimal(value).scaleb(scale).to_integral_value())
|
|
231
|
+
|
|
232
|
+
|
|
206
233
|
def decode_numeric(raw: bytes, scale: int) -> Decimal:
|
|
207
234
|
if len(raw) != 17:
|
|
208
235
|
raise AccessError(f"Decimal value is {len(raw)} bytes, not 17")
|
|
@@ -211,12 +238,12 @@ def decode_numeric(raw: bytes, scale: int) -> Decimal:
|
|
|
211
238
|
magnitude = 0
|
|
212
239
|
for word in words:
|
|
213
240
|
magnitude = (magnitude << 32) | word
|
|
214
|
-
value =
|
|
241
|
+
value = scaled_decimal(magnitude, -scale)
|
|
215
242
|
return -value if negative else value
|
|
216
243
|
|
|
217
244
|
|
|
218
245
|
def encode_numeric(value: Decimal, scale: int) -> bytes:
|
|
219
|
-
scaled =
|
|
246
|
+
scaled = scaled_int(value.copy_abs(), scale)
|
|
220
247
|
if scaled >= 1 << 128:
|
|
221
248
|
raise AccessError("Decimal value does not fit 16 bytes")
|
|
222
249
|
words = [(scaled >> shift) & 0xFFFFFFFF for shift in (96, 64, 32, 0)]
|
|
@@ -4156,24 +4156,41 @@ class AccessDatabase:
|
|
|
4156
4156
|
def delete_module(self, name: str) -> None:
|
|
4157
4157
|
"""Remove a module and every structure it occupies."""
|
|
4158
4158
|
module = self.module(name)
|
|
4159
|
-
index = [m.name for m in self.modules()].index(module.name)
|
|
4160
4159
|
encoding = encoding_of(self._vba_dir()[1])
|
|
4161
4160
|
storage = self.table(STORAGE_TABLE)
|
|
4162
4161
|
modules_id, project_id, streams_id = self._vba_storage_ids()
|
|
4163
4162
|
|
|
4164
|
-
#
|
|
4165
|
-
#
|
|
4166
|
-
#
|
|
4167
|
-
|
|
4163
|
+
# The container's own DirData names each module's storage folder,
|
|
4164
|
+
# the way `_delete_design` reads it. This used to take the folder
|
|
4165
|
+
# by position among `modules()`, which is dir-stream order and
|
|
4166
|
+
# includes the module behind a form or report. Those have no
|
|
4167
|
+
# folder here at all, so the two lists were different lengths and
|
|
4168
|
+
# every module after a document one sat a place high: deleting one
|
|
4169
|
+
# took another's folder, or ran off the end (GitHub issue #19).
|
|
4170
|
+
listing = next(
|
|
4168
4171
|
(
|
|
4169
|
-
|
|
4170
|
-
for
|
|
4171
|
-
if _as_int(row["ParentId"]) == modules_id
|
|
4172
|
+
payload
|
|
4173
|
+
for _rid, row in storage.rows_with_ids()
|
|
4174
|
+
if _as_int(row["ParentId"]) == modules_id
|
|
4175
|
+
and str(row["Name"]) == DIR_DATA
|
|
4176
|
+
and isinstance(payload := row.get("Lv"), bytes)
|
|
4172
4177
|
),
|
|
4173
|
-
|
|
4178
|
+
None,
|
|
4179
|
+
)
|
|
4180
|
+
entries = dict(dir_data_entries(listing)) if listing is not None else {}
|
|
4181
|
+
if module.name not in entries:
|
|
4182
|
+
raise AccessError(
|
|
4183
|
+
f"{module.name!r} is the code behind a form or report, which has no "
|
|
4184
|
+
"storage folder of its own; delete the design instead"
|
|
4185
|
+
)
|
|
4186
|
+
folder_name = entries[module.name]
|
|
4187
|
+
folder_rid, folder_id = next(
|
|
4188
|
+
(rid, _as_int(row["Id"]))
|
|
4189
|
+
for rid, row in storage.rows_with_ids()
|
|
4190
|
+
if _as_int(row["ParentId"]) == modules_id
|
|
4191
|
+
and _as_int(row["Type"]) == TYPE_FOLDER
|
|
4192
|
+
and str(row["Name"]) == folder_name
|
|
4174
4193
|
)
|
|
4175
|
-
folder_rid, folder_row = folders[index]
|
|
4176
|
-
folder_name, folder_id = str(folder_row["Name"]), _as_int(folder_row["Id"])
|
|
4177
4194
|
doomed = [folder_rid] + [
|
|
4178
4195
|
rid for rid, row in storage.rows_with_ids() if _as_int(row["ParentId"]) == folder_id
|
|
4179
4196
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_database.accdb
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_database_module.accdb
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_document.docm
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_excel_addin.xlam
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_presentation.pptm
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsb
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsm
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsx
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/engine_skeleton.accdb
RENAMED
|
File without changes
|
{pyopenvba-5.1.2 → pyopenvba-5.1.3}/src/pyopenvba/_templates/blank_files/engine_skeleton.mdb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|