dbx-tools-core 0.6.78__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.
- dbx_tools_core-0.6.78/PKG-INFO +33 -0
- dbx_tools_core-0.6.78/README.md +25 -0
- dbx_tools_core-0.6.78/pyproject.toml +19 -0
- dbx_tools_core-0.6.78/pyproject.toml.orig +21 -0
- dbx_tools_core-0.6.78/src/dbx_tools/core/__init__.py +16 -0
- dbx_tools_core-0.6.78/src/dbx_tools/core/hash.py +26 -0
- dbx_tools_core-0.6.78/src/dbx_tools/core/object.py +48 -0
- dbx_tools_core-0.6.78/src/dbx_tools/core/string.py +13 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dbx-tools-core
|
|
3
|
+
Version: 0.6.78
|
|
4
|
+
Summary: Dependency-free cross-runtime identity helpers for dbx-tools Python packages
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Project-URL: Source, https://github.com/reggie-db/dbx-tools/tree/main/packages/py/core
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# `dbx-tools-core`
|
|
10
|
+
|
|
11
|
+
Dependency-free Python helpers shared by dbx-tools packages.
|
|
12
|
+
|
|
13
|
+
Install directly from this monorepo:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install "dbx-tools-core @ git+https://github.com/reggie-db/dbx-tools.git@main#subdirectory=packages/py/core"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The package intentionally contains only the cross-runtime identity primitives
|
|
20
|
+
currently needed by more than one implementation:
|
|
21
|
+
|
|
22
|
+
- `hash.fnv_hash()` — the single-string subset of TypeScript
|
|
23
|
+
`fnvHashWithOptions`, including UTF-16 code-unit hashing and base-32 output;
|
|
24
|
+
- `object.to_stable_key()` — strict structured identity canonicalization;
|
|
25
|
+
- `string.to_identifier()` — readable identifier tokenization, with the same
|
|
26
|
+
hyphen default as TypeScript and an explicit delimiter override for consumers
|
|
27
|
+
such as the underscore-delimited Postgres bus channel.
|
|
28
|
+
|
|
29
|
+
These functions exist so Python packages do not copy the TypeScript algorithms
|
|
30
|
+
locally and silently drift. Add broader helpers only when another Python package
|
|
31
|
+
actually needs them. Their shared behavior is tested from
|
|
32
|
+
`packages/test/polyglot/fixtures/core/fixture.json`, not duplicated in this
|
|
33
|
+
package.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# `dbx-tools-core`
|
|
2
|
+
|
|
3
|
+
Dependency-free Python helpers shared by dbx-tools packages.
|
|
4
|
+
|
|
5
|
+
Install directly from this monorepo:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install "dbx-tools-core @ git+https://github.com/reggie-db/dbx-tools.git@main#subdirectory=packages/py/core"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package intentionally contains only the cross-runtime identity primitives
|
|
12
|
+
currently needed by more than one implementation:
|
|
13
|
+
|
|
14
|
+
- `hash.fnv_hash()` — the single-string subset of TypeScript
|
|
15
|
+
`fnvHashWithOptions`, including UTF-16 code-unit hashing and base-32 output;
|
|
16
|
+
- `object.to_stable_key()` — strict structured identity canonicalization;
|
|
17
|
+
- `string.to_identifier()` — readable identifier tokenization, with the same
|
|
18
|
+
hyphen default as TypeScript and an explicit delimiter override for consumers
|
|
19
|
+
such as the underscore-delimited Postgres bus channel.
|
|
20
|
+
|
|
21
|
+
These functions exist so Python packages do not copy the TypeScript algorithms
|
|
22
|
+
locally and silently drift. Add broader helpers only when another Python package
|
|
23
|
+
actually needs them. Their shared behavior is tested from
|
|
24
|
+
`packages/test/polyglot/fixtures/core/fixture.json`, not duplicated in this
|
|
25
|
+
package.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dbx-tools-core"
|
|
3
|
+
version = "0.6.78"
|
|
4
|
+
description = "Dependency-free cross-runtime identity helpers for dbx-tools Python packages"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[project.urls]
|
|
10
|
+
Source = "https://github.com/reggie-db/dbx-tools/tree/main/packages/py/core"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.11.28,<0.12.0"]
|
|
14
|
+
build-backend = "uv_build"
|
|
15
|
+
|
|
16
|
+
[tool.uv.build-backend]
|
|
17
|
+
module-name = "dbx_tools.core"
|
|
18
|
+
module-root = "src"
|
|
19
|
+
namespace = true
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# ~~ Generated by projen. To modify, edit .projenrc.js and run "bunx projen".
|
|
2
|
+
|
|
3
|
+
[project]
|
|
4
|
+
name = "dbx-tools-core"
|
|
5
|
+
version = "0.6.78"
|
|
6
|
+
description = "Dependency-free cross-runtime identity helpers for dbx-tools Python packages"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = [ ]
|
|
10
|
+
|
|
11
|
+
[project.urls]
|
|
12
|
+
Source = "https://github.com/reggie-db/dbx-tools/tree/main/packages/py/core"
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = [ "uv_build>=0.11.28,<0.12.0" ]
|
|
16
|
+
build-backend = "uv_build"
|
|
17
|
+
|
|
18
|
+
[tool.uv.build-backend]
|
|
19
|
+
module-name = "dbx_tools.core"
|
|
20
|
+
module-root = "src"
|
|
21
|
+
namespace = true
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .hash import fnv_hash
|
|
2
|
+
from .object import to_stable_key
|
|
3
|
+
from .string import to_identifier
|
|
4
|
+
|
|
5
|
+
fnvHash = fnv_hash
|
|
6
|
+
toIdentifier = to_identifier
|
|
7
|
+
toStableKey = to_stable_key
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"fnvHash",
|
|
11
|
+
"fnv_hash",
|
|
12
|
+
"toIdentifier",
|
|
13
|
+
"toStableKey",
|
|
14
|
+
"to_identifier",
|
|
15
|
+
"to_stable_key",
|
|
16
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
_BASE32_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def fnv_hash(value: str, *, length: int = 6) -> str:
|
|
7
|
+
"""Match TypeScript ``fnvHashWithOptions`` for one string value."""
|
|
8
|
+
digest = 0x811C9DC5
|
|
9
|
+
for token in ("[", "string:", value, ",", "]"):
|
|
10
|
+
encoded = token.encode("utf-16-le", "surrogatepass")
|
|
11
|
+
for index in range(0, len(encoded), 2):
|
|
12
|
+
code_unit = encoded[index] | (encoded[index + 1] << 8)
|
|
13
|
+
digest ^= code_unit
|
|
14
|
+
digest = (digest * 0x01000193) & 0xFFFFFFFF
|
|
15
|
+
encoded_digest = _to_base32(digest).rjust(7, _BASE32_ALPHABET[0])
|
|
16
|
+
return encoded_digest[: min(length, 7)]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _to_base32(value: int) -> str:
|
|
20
|
+
if value == 0:
|
|
21
|
+
return _BASE32_ALPHABET[0]
|
|
22
|
+
encoded = ""
|
|
23
|
+
while value:
|
|
24
|
+
encoded = _BASE32_ALPHABET[value & 31] + encoded
|
|
25
|
+
value >>= 5
|
|
26
|
+
return encoded
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from datetime import date, datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def to_stable_key(value: object, seen: set[int] | None = None) -> str:
|
|
9
|
+
"""Build the Python equivalent of shared-core's strict stable identity key."""
|
|
10
|
+
if value is None:
|
|
11
|
+
return "null"
|
|
12
|
+
if isinstance(value, str):
|
|
13
|
+
utf16_length = len(value.encode("utf-16-le", "surrogatepass")) // 2
|
|
14
|
+
return f"string:{utf16_length}:{value}"
|
|
15
|
+
if isinstance(value, bool):
|
|
16
|
+
return f"boolean:{str(value).lower()}"
|
|
17
|
+
if isinstance(value, int):
|
|
18
|
+
return f"number:{value}"
|
|
19
|
+
if isinstance(value, float):
|
|
20
|
+
if not math.isfinite(value):
|
|
21
|
+
raise TypeError("Stable keys require finite numbers")
|
|
22
|
+
if value == 0 and math.copysign(1, value) < 0:
|
|
23
|
+
return "number:-0"
|
|
24
|
+
return f"number:{format(value, '.15g')}"
|
|
25
|
+
if isinstance(value, (datetime, date)):
|
|
26
|
+
timestamp = value.isoformat()
|
|
27
|
+
if isinstance(value, datetime) and value.tzinfo is not None:
|
|
28
|
+
timestamp = timestamp.replace("+00:00", "Z")
|
|
29
|
+
return f"date:{timestamp}"
|
|
30
|
+
seen = seen or set()
|
|
31
|
+
identity = id(value)
|
|
32
|
+
if identity in seen:
|
|
33
|
+
raise TypeError("Stable keys cannot contain cycles")
|
|
34
|
+
seen.add(identity)
|
|
35
|
+
try:
|
|
36
|
+
if isinstance(value, (list, tuple)):
|
|
37
|
+
return f"array:[{','.join(to_stable_key(item, seen) for item in value)}]"
|
|
38
|
+
if isinstance(value, (set, frozenset)):
|
|
39
|
+
return f"set:[{','.join(sorted(to_stable_key(item, seen) for item in value))}]"
|
|
40
|
+
if isinstance(value, Mapping):
|
|
41
|
+
entries = sorted(
|
|
42
|
+
f"{to_stable_key(key, seen)}={to_stable_key(item, seen)}"
|
|
43
|
+
for key, item in value.items()
|
|
44
|
+
)
|
|
45
|
+
return f"object:{{{','.join(entries)}}}"
|
|
46
|
+
finally:
|
|
47
|
+
seen.remove(identity)
|
|
48
|
+
raise TypeError(f"Unsupported stable key type: {type(value).__name__}")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def to_identifier(*values: object, delimiter: str = "-") -> str:
|
|
7
|
+
"""Match TypeScript identifier tokenization with an overridable delimiter."""
|
|
8
|
+
tokens: list[str] = []
|
|
9
|
+
for value in values:
|
|
10
|
+
text_value = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1 \2", str(value))
|
|
11
|
+
text_value = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", text_value)
|
|
12
|
+
tokens.extend(token.lower() for token in re.findall(r"[A-Za-z0-9]+", text_value))
|
|
13
|
+
return delimiter.join(tokens)
|