chtypes 2.0.0.dev7__tar.gz → 2.0.0.dev9__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.
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/CHANGELOG.md +13 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/PKG-INFO +1 -1
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/pyproject.toml +1 -1
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_decode.py +8 -1
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_input.py +37 -2
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/library.py +1 -1
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/api/test_decode.py +22 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/api/test_input.py +38 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/uv.lock +1 -1
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/.gitignore +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/LICENSE +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/README.md +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/__init__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/__main__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_abi2/_decls.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_abi2/_errmap.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_abi2/_errors.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_abi2/_loader.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_abi2/_vocab.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_codes.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ed25519.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_guard.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_channel.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_constants.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_dsse.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_ensure.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_errors.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_faults.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_goldens.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_hold.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_http.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_layout.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_lock.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_oci.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_prune.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_referrers.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_resolve.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_retired.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_ocifetch/_unpack.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/_setup.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/errors.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/py.typed +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/registry.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/src/chtypes/results.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/_stub_tree.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/conftest.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_conformance.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_dev_fingerprint.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_goldens_runner_stub.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_loader.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_public_library.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_public_open.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_public_registry.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_public_server.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_public_setup_cases.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_reader_rules.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_registry_fetch.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/abi2/test_registry_flight.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/api/__init__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/api/test_trusted_keys.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/api/test_vocab.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/cli/__init__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/cli/conftest.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/cli/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/cli/test_main.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/cli/test_resolve_prune.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/conftest.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/goldens_v1/__init__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/goldens_v1/data/stub-goldens.json +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/goldens_v1/test_runner.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/_bundle_support.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/_registry_support.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/_sign_support.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/conftest.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_cache_roots.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_cache_strict.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_conformance.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_dsse.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_ensure.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_goldens.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_http.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_layout.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_lock.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_oci.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_prodv2_smoke.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_prodv2channel.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_retired.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_seam_gate.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/ocifetch/test_unpack.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/test_abi_identity.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/test_cache_roots_api.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/test_devchannel_registry.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/test_ed25519.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/test_examples_tour.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/test_fetch_options.py +0 -0
- {chtypes-2.0.0.dev7 → chtypes-2.0.0.dev9}/tests/test_support_v2_gen.py +0 -0
|
@@ -54,6 +54,17 @@ The four bindings in this repository are released together and give one answer,
|
|
|
54
54
|
- **Enforcement gate restored** for `26.7` on `darwin-arm64`.
|
|
55
55
|
- **Enforcement gate restored** for `26.8` on `darwin-arm64`.
|
|
56
56
|
- **Enforcement gate restored** for `26.9` on `darwin-arm64`.
|
|
57
|
+
- **Enforcement gate lifted** for `26.3` on `linux-amd64`.
|
|
58
|
+
- **Enforcement gate lifted** for `26.7` on `linux-amd64`.
|
|
59
|
+
- **Enforcement gate lifted** for `26.8` on `linux-amd64`.
|
|
60
|
+
- **Enforcement gate lifted** for `26.9` on `linux-amd64`.
|
|
61
|
+
- **Enforcement gate lifted** for `26.3` on `linux-arm64`.
|
|
62
|
+
- **Enforcement gate lifted** for `26.7` on `linux-arm64`.
|
|
63
|
+
- **Enforcement gate lifted** for `26.8` on `linux-arm64`.
|
|
64
|
+
- **Enforcement gate lifted** for `26.9` on `linux-arm64`.
|
|
65
|
+
- **Enforcement gate lifted** for `26.7` on `darwin-arm64`.
|
|
66
|
+
- **Enforcement gate lifted** for `26.8` on `darwin-arm64`.
|
|
67
|
+
- **Enforcement gate lifted** for `26.9` on `darwin-arm64`.
|
|
57
68
|
|
|
58
69
|
### Removed
|
|
59
70
|
|
|
@@ -61,6 +72,8 @@ The four bindings in this repository are released together and give one answer,
|
|
|
61
72
|
|
|
62
73
|
### Fixed
|
|
63
74
|
|
|
75
|
+
- A settings or query-parameter key or value that is not valid UTF-8 is refused as a usage error; it was silently replaced with U+FFFD, so the library saw a different value. A string holding a lone surrogate (what `surrogateescape` makes of undecodable bytes) was sent as a `\udXXX` escape; the same inputs are refused now, for the per-call zone, the setup defaults and the server profile too. Valid UTF-8 serializes as before.
|
|
76
|
+
- A declined export's payload is absent, as documented: it was an empty payload when the library returned a zero-length buffer for a declined export. `BatchResult.payload` and `spans` are `None` for a declined export. Whether an export was declined is decided from the document's `export_declined`, never from the buffer's length.
|
|
64
77
|
- The layout's `index.json` never gains a second entry for one manifest (#487): concurrent installs of one build each appended an entry (7 or 8 entries for the same digest after 8 concurrent installs, measured on 1.0.2). The merge now deduplicates by digest, as Go and TypeScript do.
|
|
65
78
|
- `--frozen` with a warm, verified cache makes zero requests, and `--frozen --offline` verifies the installed build against the lock with zero network (#414). An installed build whose `verified.json` is for the platform and records exactly the lock's pinned manifest, layer and bundle digests is used as it is. `--frozen --offline` is `CHTYPES_ARTIFACT_MISSING` when the pinned build is not installed, and `CHTYPES_ARTIFACT_PINNED` when there is no lock entry or the installed record does not match the pin; `--offline` without `--frozen` is unchanged. The `frozen-warm-cache-zero-requests`, `frozen-offline-lock-match`, `frozen-offline-lock-mismatch` and `frozen-offline-lock-not-installed` conformance cases hold all four bindings to it, under both corpora. The Python frozen path also compares the record's layer and bundle digests to the pin, and looks in the system directories too.
|
|
66
79
|
- A 2.0.0-dev SDK whose own-fingerprint alias has expired, with a cached build of that fingerprint for the request, now says so in the `CHTYPES_ARTIFACT_UNPUBLISHED` message of `fetch` (#581): `; a cached build for this fingerprint (build <id>) is still usable by open; upgrade the SDK to get newer builds` is appended. The code, the exit status (4) and the cache are unchanged, and all four bindings give the same message (`fp-ahead-cached-own-fp`).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev9
|
|
4
4
|
Summary: Python binding for the chtypes artifacts: ClickHouse's own type system, per version, behind the frozen chs_* C ABI
|
|
5
5
|
Project-URL: Homepage, https://github.com/wave-rf/chtypes
|
|
6
6
|
Project-URL: Repository, https://github.com/wave-rf/chtypes
|
|
@@ -428,6 +428,13 @@ def decode_batch(raw: bytes, export: bytes | None) -> BatchResult:
|
|
|
428
428
|
framing = obj.get("framing")
|
|
429
429
|
engine_rows = None if obj.get("engine_rows") is None else _engine_rows(obj, where)
|
|
430
430
|
spans = None if obj.get("row_spans") is None else _spans(obj, "row_spans", where)
|
|
431
|
+
export_declined = _bytes(obj, "export_declined", where)
|
|
432
|
+
if export_declined:
|
|
433
|
+
# A declined export has no payload and no spans, whatever buffer the call
|
|
434
|
+
# returned: the library returns a NULL out_export for it (builds up to
|
|
435
|
+
# 20261011.105019 returned a zero-length buffer), and the document's
|
|
436
|
+
# export_declined decides, never the buffer.
|
|
437
|
+
export, spans = None, None
|
|
431
438
|
return BatchResult(
|
|
432
439
|
outcome=Outcome.of(_text(obj, "outcome", where)),
|
|
433
440
|
err_code=_int(obj, "code", where),
|
|
@@ -439,7 +446,7 @@ def decode_batch(raw: bytes, export: bytes | None) -> BatchResult:
|
|
|
439
446
|
engine_rows=engine_rows,
|
|
440
447
|
payload=export,
|
|
441
448
|
spans=spans,
|
|
442
|
-
export_declined=
|
|
449
|
+
export_declined=export_declined,
|
|
443
450
|
rows_passed=_u64(obj, "rows_passed", where),
|
|
444
451
|
rows_cut=_u64(obj, "rows_cut", where),
|
|
445
452
|
partition_count=_opt_u64(obj, "partition_count", where),
|
|
@@ -45,6 +45,31 @@ def body_bytes(value: bytes, what: str = "body") -> bytes:
|
|
|
45
45
|
raise TypeError(f"{what} must be bytes, not {type(value).__name__}")
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
_NON_UTF8_HINT = (
|
|
49
|
+
"this ABI generation cannot carry a non-UTF-8 value in a JSON input; "
|
|
50
|
+
"write the bytes as unhex('<hex>') in the expression instead"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _is_utf8(text: str) -> bool:
|
|
55
|
+
"""False for a `str` that cannot be UTF-8 encoded (a lone surrogate, as
|
|
56
|
+
`surrogateescape` makes of undecodable bytes)."""
|
|
57
|
+
try:
|
|
58
|
+
text.encode("utf-8")
|
|
59
|
+
except UnicodeEncodeError:
|
|
60
|
+
return False
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _refuse_non_utf8(what: str, part: str, key: str) -> Exception:
|
|
65
|
+
# The key is shown escaped (ASCII only), never as the raw text.
|
|
66
|
+
return misuse(
|
|
67
|
+
f"{what}: the key {json.dumps(key)} is not valid UTF-8, and {_NON_UTF8_HINT}"
|
|
68
|
+
if part == "key"
|
|
69
|
+
else f"{what}: the value for key {json.dumps(key)} is not valid UTF-8, and {_NON_UTF8_HINT}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
48
73
|
def _string_map(mapping: Mapping[str, str], what: str) -> dict[str, str]:
|
|
49
74
|
out: dict[str, str] = {}
|
|
50
75
|
for key, value in mapping.items():
|
|
@@ -53,6 +78,12 @@ def _string_map(mapping: Mapping[str, str], what: str) -> dict[str, str]:
|
|
|
53
78
|
f"{what} maps strings to strings only; got {type(key).__name__} -> "
|
|
54
79
|
f"{type(value).__name__} for {key!r} (a value is never rewritten)"
|
|
55
80
|
)
|
|
81
|
+
# json.dumps would write a lone surrogate as a \udXXX escape, a value
|
|
82
|
+
# the library does not read as the bytes the caller meant: refuse.
|
|
83
|
+
if not _is_utf8(key):
|
|
84
|
+
raise _refuse_non_utf8(what, "key", key)
|
|
85
|
+
if not _is_utf8(value):
|
|
86
|
+
raise _refuse_non_utf8(what, "value", key)
|
|
56
87
|
out[key] = value
|
|
57
88
|
return out
|
|
58
89
|
|
|
@@ -75,6 +106,8 @@ def settings_json(
|
|
|
75
106
|
if session_timezone is not None:
|
|
76
107
|
if not isinstance(session_timezone, str):
|
|
77
108
|
raise TypeError(f"session_timezone must be str, not {type(session_timezone).__name__}")
|
|
109
|
+
if not _is_utf8(session_timezone):
|
|
110
|
+
raise misuse(f"the zone: the value is not valid UTF-8, and {_NON_UTF8_HINT}")
|
|
78
111
|
if _ZONE_KEY in merged:
|
|
79
112
|
raise misuse(
|
|
80
113
|
f"the {_ZONE_KEY} option and a {_ZONE_KEY} settings key were both given; "
|
|
@@ -141,10 +174,12 @@ def server_profile_json(profile: ServerProfile) -> bytes:
|
|
|
141
174
|
if profile.timezone is not None:
|
|
142
175
|
if not isinstance(profile.timezone, str):
|
|
143
176
|
raise TypeError(f"timezone must be str, not {type(profile.timezone).__name__}")
|
|
177
|
+
if not _is_utf8(profile.timezone):
|
|
178
|
+
raise misuse(f"server profile: the timezone is not valid UTF-8, and {_NON_UTF8_HINT}")
|
|
144
179
|
if profile.timezone != "":
|
|
145
180
|
doc["timezone"] = profile.timezone
|
|
146
181
|
if profile.settings is not None:
|
|
147
|
-
doc["settings"] = _string_map(profile.settings, "settings")
|
|
182
|
+
doc["settings"] = _string_map(profile.settings, "server profile settings")
|
|
148
183
|
if profile.macros is not None:
|
|
149
|
-
doc["macros"] = _string_map(profile.macros, "macros")
|
|
184
|
+
doc["macros"] = _string_map(profile.macros, "server profile macros")
|
|
150
185
|
return _dump(doc)
|
|
@@ -296,7 +296,7 @@ class Schema(_Handle):
|
|
|
296
296
|
zone is its own, fixed here: its WHERE runs in the session this call's
|
|
297
297
|
`session_timezone` (or `settings`) names, on every evaluation."""
|
|
298
298
|
expr_bytes = to_bytes(expr, "expr")
|
|
299
|
-
params_bytes = string_map_json(params, "
|
|
299
|
+
params_bytes = string_map_json(params, "query parameters")
|
|
300
300
|
settings_bytes = settings_json(settings, session_timezone)
|
|
301
301
|
with _hold(self._guard):
|
|
302
302
|
handle = self._library._api.filter_create(
|
|
@@ -177,6 +177,28 @@ BATCH = {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
def test_a_declined_exports_payload_is_absent_whatever_the_buffer() -> None:
|
|
181
|
+
# The library returns a NULL out_export for a declined export (builds up to
|
|
182
|
+
# 20261011.105019 returned a non-NULL, zero-length buffer): the document's
|
|
183
|
+
# export_declined decides, never the buffer.
|
|
184
|
+
for member in (
|
|
185
|
+
{"export_declined": "only accepted batches export"},
|
|
186
|
+
{"export_declined_b64": b64(b"\xff")},
|
|
187
|
+
):
|
|
188
|
+
doc = {"outcome": "rejected", "row_spans": [{"off": 0, "len": 1}], **member}
|
|
189
|
+
for buffer in (b"", b"x"):
|
|
190
|
+
batch = decode_batch(dumps(doc), buffer)
|
|
191
|
+
assert batch.payload is None and batch.spans is None and batch.export_declined
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_an_accepted_zero_row_export_is_present_and_empty() -> None:
|
|
195
|
+
batch = decode_batch(
|
|
196
|
+
dumps({"outcome": "accepted", "row_spans": [], "export_declined": ""}), b""
|
|
197
|
+
)
|
|
198
|
+
assert batch.payload == b"" and batch.spans == () and batch.export_declined == b""
|
|
199
|
+
assert decode_batch(dumps({"outcome": "accepted"}), None).payload is None
|
|
200
|
+
|
|
201
|
+
|
|
180
202
|
def test_a_batch_decodes_rows_payload_framing_and_spans() -> None:
|
|
181
203
|
batch = decode_batch(dumps(BATCH), b"export bytes")
|
|
182
204
|
assert batch.outcome is Outcome.ACCEPTED_POISONED
|
|
@@ -64,3 +64,41 @@ def test_text_inputs_are_utf8_and_bodies_never_accept_text() -> None:
|
|
|
64
64
|
assert body_bytes(memoryview(b"ab")) == b"ab"
|
|
65
65
|
with pytest.raises(TypeError):
|
|
66
66
|
body_bytes("text") # type: ignore[arg-type]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_LONE = "\udcff" # what surrogateescape makes of the byte 0xff
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.mark.parametrize(
|
|
73
|
+
("call", "words"),
|
|
74
|
+
[
|
|
75
|
+
(lambda: settings_json({"k": _LONE}), ("settings", 'the value for key "k"')),
|
|
76
|
+
(lambda: settings_json({"k" + _LONE: "v"}), ("settings", 'the key "k\\udcff"')),
|
|
77
|
+
(lambda: settings_json({"k": _LONE}, "UTC"), ("settings", 'the value for key "k"')),
|
|
78
|
+
(lambda: settings_json(None, _LONE), ("the zone",)),
|
|
79
|
+
(
|
|
80
|
+
lambda: string_map_json({"p": _LONE}, "query parameters"),
|
|
81
|
+
("query parameters", 'the value for key "p"'),
|
|
82
|
+
),
|
|
83
|
+
(
|
|
84
|
+
lambda: string_map_json({_LONE: "v"}, "query parameters"),
|
|
85
|
+
("query parameters", 'the key "\\udcff"'),
|
|
86
|
+
),
|
|
87
|
+
],
|
|
88
|
+
)
|
|
89
|
+
def test_a_non_utf8_key_or_value_is_a_usage_error_naming_the_input_and_the_key(call, words) -> None:
|
|
90
|
+
with pytest.raises(UsageError) as info:
|
|
91
|
+
call()
|
|
92
|
+
message = info.value.message.decode("utf-8")
|
|
93
|
+
for word in (*words, "not valid UTF-8", "unhex('<hex>')"):
|
|
94
|
+
assert word in message
|
|
95
|
+
assert info.value.status == 3 and info.value.ch_code == 0
|
|
96
|
+
assert message.isascii() # the key is escaped, never raw
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_valid_non_ascii_serializes_byte_for_byte_as_before() -> None:
|
|
100
|
+
# Measured before the change on the same inputs: json.dumps, ASCII escapes.
|
|
101
|
+
assert string_map_json({"p": "é日本"}, "query parameters") == b'{"p":"\\u00e9\\u65e5\\u672c"}'
|
|
102
|
+
assert settings_json({"é": "\ufffd"}, "Asia/Tokyo") == (
|
|
103
|
+
b'{"\\u00e9":"\\ufffd","session_timezone":"Asia/Tokyo"}'
|
|
104
|
+
)
|
|
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
|
|
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
|