dazzle-lib 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dustin Darcy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: dazzle-lib
3
+ Version: 0.1.0
4
+ Summary: The DazzleLib bedrock: shared Protocols, TypedDict payload schemas, and exception root for the dazzle-* library stack. Stdlib-only by charter.
5
+ Author-email: djdarcy <djdarcy@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/DazzleLib/dazzle-lib
8
+ Project-URL: Repository, https://github.com/DazzleLib/dazzle-lib
9
+ Project-URL: Issues, https://github.com/DazzleLib/dazzle-lib/issues
10
+ Keywords: dazzlelib,protocols,typeddict,serialization,bedrock,stack
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # dazzle-lib
32
+
33
+ **The DazzleLib stack's bedrock: shared Protocols, TypedDict payload schemas, and the exception root.**
34
+
35
+ [![PyPI](https://img.shields.io/pypi/v/dazzle-lib)](https://pypi.org/project/dazzle-lib/)
36
+ [![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://pypi.org/project/dazzle-lib/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
38
+ [![Platforms](https://img.shields.io/badge/platforms-all-brightgreen)](docs/platform-support.md)
39
+
40
+ Every `dazzle-*` library ([the stack](https://github.com/DazzleLib/.github/blob/main/docs/STACK-MAP.md)) builds on this package: it defines what stack objects can be expected to do (view themselves, serialize themselves) and what shapes cross-layer payloads have. **Types only** -- by charter this package contains no I/O, no path handling, no platform probing, and no behavior, forever.
41
+
42
+ ```bash
43
+ pip install dazzle-lib
44
+ ```
45
+
46
+ ## What's inside (all of it)
47
+
48
+ | Module | Contents |
49
+ |---|---|
50
+ | `dazzle_lib.protocols` | `Viewable` (`summary()`/`__str__`), `Serializable` (`to_dict`/`from_dict`/`to_json`, `SCHEMA_VERSION`) -- structural `Protocol`s, `runtime_checkable`, nothing is forced to subclass |
51
+ | `dazzle_lib.payloads` | The cross-layer TypedDict schemas: `FileMetadataDict`, `TimestampsDict`, `WindowsMetadataDict`, `UnixMetadataDict`, `LinkTargetDict`, `HashResultDict` -- mirroring what `dazzle-filekit` actually produces |
52
+ | `dazzle_lib.exceptions` | `DazzleError` root + per-domain bases (`PathIdentityError`, `FileOperationError`, `LinkError`, `PreserveError`) |
53
+ | `dazzle_lib.mixins` | `DazzleDataMixin` -- derives `to_json`/`summary`/`__str__` from your `to_dict` |
54
+
55
+ ## The idea: the dict is the interface
56
+
57
+ Rich objects in upper layers know how to become plain dicts; lower-layer functions take and return those dicts. The TypedDicts here are the agreed shapes, so a manifest object in `dazzle-preservelib` and a metadata collector in `dazzle-filekit` speak the same payload without sharing a class hierarchy:
58
+
59
+ ```python
60
+ from dataclasses import dataclass
61
+ from dazzle_lib import DazzleDataMixin, Serializable, FileMetadataDict
62
+
63
+ @dataclass
64
+ class TransferResult(DazzleDataMixin):
65
+ SCHEMA_VERSION = 1
66
+ path: str
67
+ metadata: FileMetadataDict
68
+
69
+ def to_dict(self):
70
+ return {"schema_version": self.SCHEMA_VERSION,
71
+ "path": self.path, "metadata": dict(self.metadata)}
72
+
73
+ @classmethod
74
+ def from_dict(cls, data):
75
+ return cls(path=data["path"], metadata=data["metadata"])
76
+
77
+ result = TransferResult("a.txt", {"mode": 0o644, "size": 10, "timestamps": {}})
78
+ assert isinstance(result, Serializable) # structural -- no subclassing needed
79
+ print(result.summary()) # one-liner for logs
80
+ ```
81
+
82
+ And one catchable root for the whole stack:
83
+
84
+ ```python
85
+ from dazzle_lib import DazzleError
86
+ try:
87
+ ... # any dazzle-* library call
88
+ except DazzleError as e:
89
+ ... # caught, whichever layer raised it
90
+ ```
91
+
92
+ ## The charter (enforced by tests)
93
+
94
+ This package is **stdlib-only forever** and contains **no behavior**. `tests/test_charter.py` fails on any banned import (`os`, `shutil`, `pathlib`, `subprocess`, ...) anywhere in the package -- a PR that needs to weaken that test is adding something that belongs in a higher layer. Admission follows the **rule of two**: a Protocol or TypedDict enters the bedrock only when two or more stack libraries need it.
95
+
96
+ ## The stack
97
+
98
+ | Layer | Library | Role |
99
+ |---|---|---|
100
+ | B | **dazzle-lib** (this) | bedrock contracts |
101
+ | L0 | [dazzle-unctools](https://github.com/DazzleLib/UNCtools) | path identity (UNC/drive/origin) |
102
+ | L1 | [dazzle-filekit](https://github.com/DazzleLib/dazzle-filekit) | filesystem primitives |
103
+ | L2 | dazzle-linklib *(planned)* | link serialization |
104
+ | L3 | dazzle-preservelib *(planned)* | operation orchestration |
105
+ | ⊥ | [dazzle-treelib](https://github.com/DazzleLib/dazzle-tree-lib) | traversal engine |
106
+
107
+ Full architecture contract: [STACK-MAP.md](https://github.com/DazzleLib/.github/blob/main/docs/STACK-MAP.md). API stability policy: [docs/api-stability.md](docs/api-stability.md).
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ pip install -e ".[dev]"
113
+ python -m pytest tests/ -v
114
+ ```
115
+
116
+ ## License
117
+
118
+ MIT -- the bedrock sits beneath MIT and GPL stack members alike, so it carries the permissive license.
@@ -0,0 +1,88 @@
1
+ # dazzle-lib
2
+
3
+ **The DazzleLib stack's bedrock: shared Protocols, TypedDict payload schemas, and the exception root.**
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/dazzle-lib)](https://pypi.org/project/dazzle-lib/)
6
+ [![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://pypi.org/project/dazzle-lib/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+ [![Platforms](https://img.shields.io/badge/platforms-all-brightgreen)](docs/platform-support.md)
9
+
10
+ Every `dazzle-*` library ([the stack](https://github.com/DazzleLib/.github/blob/main/docs/STACK-MAP.md)) builds on this package: it defines what stack objects can be expected to do (view themselves, serialize themselves) and what shapes cross-layer payloads have. **Types only** -- by charter this package contains no I/O, no path handling, no platform probing, and no behavior, forever.
11
+
12
+ ```bash
13
+ pip install dazzle-lib
14
+ ```
15
+
16
+ ## What's inside (all of it)
17
+
18
+ | Module | Contents |
19
+ |---|---|
20
+ | `dazzle_lib.protocols` | `Viewable` (`summary()`/`__str__`), `Serializable` (`to_dict`/`from_dict`/`to_json`, `SCHEMA_VERSION`) -- structural `Protocol`s, `runtime_checkable`, nothing is forced to subclass |
21
+ | `dazzle_lib.payloads` | The cross-layer TypedDict schemas: `FileMetadataDict`, `TimestampsDict`, `WindowsMetadataDict`, `UnixMetadataDict`, `LinkTargetDict`, `HashResultDict` -- mirroring what `dazzle-filekit` actually produces |
22
+ | `dazzle_lib.exceptions` | `DazzleError` root + per-domain bases (`PathIdentityError`, `FileOperationError`, `LinkError`, `PreserveError`) |
23
+ | `dazzle_lib.mixins` | `DazzleDataMixin` -- derives `to_json`/`summary`/`__str__` from your `to_dict` |
24
+
25
+ ## The idea: the dict is the interface
26
+
27
+ Rich objects in upper layers know how to become plain dicts; lower-layer functions take and return those dicts. The TypedDicts here are the agreed shapes, so a manifest object in `dazzle-preservelib` and a metadata collector in `dazzle-filekit` speak the same payload without sharing a class hierarchy:
28
+
29
+ ```python
30
+ from dataclasses import dataclass
31
+ from dazzle_lib import DazzleDataMixin, Serializable, FileMetadataDict
32
+
33
+ @dataclass
34
+ class TransferResult(DazzleDataMixin):
35
+ SCHEMA_VERSION = 1
36
+ path: str
37
+ metadata: FileMetadataDict
38
+
39
+ def to_dict(self):
40
+ return {"schema_version": self.SCHEMA_VERSION,
41
+ "path": self.path, "metadata": dict(self.metadata)}
42
+
43
+ @classmethod
44
+ def from_dict(cls, data):
45
+ return cls(path=data["path"], metadata=data["metadata"])
46
+
47
+ result = TransferResult("a.txt", {"mode": 0o644, "size": 10, "timestamps": {}})
48
+ assert isinstance(result, Serializable) # structural -- no subclassing needed
49
+ print(result.summary()) # one-liner for logs
50
+ ```
51
+
52
+ And one catchable root for the whole stack:
53
+
54
+ ```python
55
+ from dazzle_lib import DazzleError
56
+ try:
57
+ ... # any dazzle-* library call
58
+ except DazzleError as e:
59
+ ... # caught, whichever layer raised it
60
+ ```
61
+
62
+ ## The charter (enforced by tests)
63
+
64
+ This package is **stdlib-only forever** and contains **no behavior**. `tests/test_charter.py` fails on any banned import (`os`, `shutil`, `pathlib`, `subprocess`, ...) anywhere in the package -- a PR that needs to weaken that test is adding something that belongs in a higher layer. Admission follows the **rule of two**: a Protocol or TypedDict enters the bedrock only when two or more stack libraries need it.
65
+
66
+ ## The stack
67
+
68
+ | Layer | Library | Role |
69
+ |---|---|---|
70
+ | B | **dazzle-lib** (this) | bedrock contracts |
71
+ | L0 | [dazzle-unctools](https://github.com/DazzleLib/UNCtools) | path identity (UNC/drive/origin) |
72
+ | L1 | [dazzle-filekit](https://github.com/DazzleLib/dazzle-filekit) | filesystem primitives |
73
+ | L2 | dazzle-linklib *(planned)* | link serialization |
74
+ | L3 | dazzle-preservelib *(planned)* | operation orchestration |
75
+ | ⊥ | [dazzle-treelib](https://github.com/DazzleLib/dazzle-tree-lib) | traversal engine |
76
+
77
+ Full architecture contract: [STACK-MAP.md](https://github.com/DazzleLib/.github/blob/main/docs/STACK-MAP.md). API stability policy: [docs/api-stability.md](docs/api-stability.md).
78
+
79
+ ## Development
80
+
81
+ ```bash
82
+ pip install -e ".[dev]"
83
+ python -m pytest tests/ -v
84
+ ```
85
+
86
+ ## License
87
+
88
+ MIT -- the bedrock sits beneath MIT and GPL stack members alike, so it carries the permissive license.
@@ -0,0 +1,51 @@
1
+ """dazzle-lib -- the DazzleLib stack's bedrock.
2
+
3
+ Shared Protocols, TypedDict payload schemas, and the exception root that every
4
+ ``dazzle-*`` library builds on. Types only: this package is stdlib-only forever
5
+ and, by charter, contains no I/O, no path handling, no platform probing, and no
6
+ "utils". See the architecture contract:
7
+ https://github.com/DazzleLib/.github/blob/main/docs/STACK-MAP.md
8
+ """
9
+
10
+ from ._version import PIP_VERSION, __app_name__, __version__
11
+ from .exceptions import (
12
+ DazzleError,
13
+ FileOperationError,
14
+ LinkError,
15
+ PathIdentityError,
16
+ PreserveError,
17
+ )
18
+ from .mixins import DazzleDataMixin
19
+ from .payloads import (
20
+ FileMetadataDict,
21
+ HashResultDict,
22
+ LinkTargetDict,
23
+ TimestampsDict,
24
+ UnixMetadataDict,
25
+ WindowsMetadataDict,
26
+ )
27
+ from .protocols import Serializable, Viewable
28
+
29
+ __all__ = [
30
+ "__version__",
31
+ "__app_name__",
32
+ "PIP_VERSION",
33
+ # protocols
34
+ "Viewable",
35
+ "Serializable",
36
+ # payload schemas
37
+ "TimestampsDict",
38
+ "WindowsMetadataDict",
39
+ "UnixMetadataDict",
40
+ "FileMetadataDict",
41
+ "LinkTargetDict",
42
+ "HashResultDict",
43
+ # exceptions
44
+ "DazzleError",
45
+ "PathIdentityError",
46
+ "FileOperationError",
47
+ "LinkError",
48
+ "PreserveError",
49
+ # mixin
50
+ "DazzleDataMixin",
51
+ ]
@@ -0,0 +1,11 @@
1
+ """``python -m dazzle_lib`` -- print version and charter (a library, not a tool)."""
2
+
3
+ from ._version import DISPLAY_VERSION, __app_name__
4
+
5
+ if __name__ == "__main__":
6
+ print(f"{__app_name__} {DISPLAY_VERSION}")
7
+ print(
8
+ "The DazzleLib stack's bedrock: Protocols, TypedDict payload schemas, "
9
+ "and the exception root.\nTypes only -- by charter this package "
10
+ "contains no behavior. https://github.com/DazzleLib/dazzle-lib"
11
+ )
@@ -0,0 +1,92 @@
1
+ """
2
+ Version information for dazzle-lib.
3
+
4
+ This file is the canonical source for version numbers.
5
+ The __version__ string is automatically updated by git hooks
6
+ with build metadata (branch, build number, date, commit hash).
7
+
8
+ Format: MAJOR.MINOR.PATCH[-PHASE]_BRANCH_BUILD-YYYYMMDD-COMMITHASH
9
+ Example: 0.1.0_main_1-20260101-a1b2c3d4
10
+
11
+ Version levels:
12
+ PROJECT_PHASE: Global project maturity (prealpha -> alpha -> beta -> stable).
13
+ Changes rarely, when the overall project hits a threshold.
14
+ PHASE: Per-MINOR feature set maturity (alpha -> beta -> "" for stable).
15
+ Drops when a MINOR's feature set is complete.
16
+ """
17
+
18
+ # Version components - edit these for version bumps
19
+ MAJOR = 0
20
+ MINOR = 1
21
+ PATCH = 0
22
+ PHASE = "" # Per-MINOR feature set: "" (stable), "alpha", "beta", "rc1", etc.
23
+
24
+ # Project-level phase (independent of version phase)
25
+ PROJECT_PHASE = "" # "prealpha", "alpha", "beta", "stable", or ""
26
+
27
+ # Auto-updated by git hooks - do not edit manually
28
+ __version__ = "0.1.0_main_5-20260611-b3174573"
29
+ __app_name__ = "dazzle-lib"
30
+
31
+
32
+ def get_version():
33
+ """Return the full version string including branch and build info."""
34
+ return __version__
35
+
36
+
37
+ def get_base_version():
38
+ """Return the semantic version string (MAJOR.MINOR.PATCH[-PHASE])."""
39
+ if "_" in __version__:
40
+ return __version__.split("_")[0]
41
+ base = f"{MAJOR}.{MINOR}.{PATCH}"
42
+ if PHASE:
43
+ base = f"{base}-{PHASE}"
44
+ return base
45
+
46
+
47
+ def get_display_version():
48
+ """Return a human-friendly version string with project phase.
49
+
50
+ Example: 'PREALPHA 0.1.0-alpha' or 'BETA 0.5.1' or '1.0.0'
51
+ """
52
+ base = get_base_version()
53
+ if PROJECT_PHASE and PROJECT_PHASE != "stable":
54
+ return f"{PROJECT_PHASE.upper()} {base}"
55
+ return base
56
+
57
+
58
+ def get_pip_version():
59
+ """
60
+ Return PEP 440 compliant version for pip/setuptools.
61
+
62
+ Converts our version format to PEP 440:
63
+ - Main branch: 0.1.0_main_3-20260404-hash -> 0.1.0
64
+ - Dev branch: 0.1.0_dev_3-20260404-hash -> 0.1.0.dev3
65
+ - Alpha: 0.1.0-alpha_main_3 -> 0.1.0a0
66
+ """
67
+ base = f"{MAJOR}.{MINOR}.{PATCH}"
68
+
69
+ # Map phase to PEP 440 pre-release segment
70
+ phase_map = {"alpha": "a0", "beta": "b0"}
71
+ if PHASE:
72
+ base += phase_map.get(PHASE, PHASE)
73
+
74
+ if "_" not in __version__:
75
+ return base
76
+
77
+ parts = __version__.split("_")
78
+ branch = parts[1] if len(parts) > 1 else "unknown"
79
+
80
+ if branch == "main":
81
+ return base
82
+ else:
83
+ build_info = "_".join(parts[2:]) if len(parts) > 2 else ""
84
+ build_num = build_info.split("-")[0] if "-" in build_info else "0"
85
+ return f"{base}.dev{build_num}"
86
+
87
+
88
+ # For convenience in imports
89
+ VERSION = get_version()
90
+ BASE_VERSION = get_base_version()
91
+ PIP_VERSION = get_pip_version()
92
+ DISPLAY_VERSION = get_display_version()
@@ -0,0 +1,43 @@
1
+ """The DazzleLib exception bedrock.
2
+
3
+ One catchable root (:class:`DazzleError`) for the whole stack, plus one base
4
+ per layer domain. Each stack library derives ITS OWN exceptions from the
5
+ matching domain base (e.g. dazzle-preservelib's ``ManifestVersionError`` would
6
+ subclass :class:`PreserveError`), so consumers can choose their catch
7
+ granularity: a specific error, a domain, or the whole stack.
8
+
9
+ Charter reminder: these are plain exception types. No behavior beyond
10
+ ``__init__``-style state, no I/O.
11
+ """
12
+
13
+ __all__ = [
14
+ "DazzleError",
15
+ "PathIdentityError",
16
+ "FileOperationError",
17
+ "LinkError",
18
+ "PreserveError",
19
+ ]
20
+
21
+
22
+ class DazzleError(Exception):
23
+ """Root of every exception raised by a DazzleLib stack library."""
24
+
25
+
26
+ class PathIdentityError(DazzleError):
27
+ """Domain base for path-identity failures (dazzle-unctools, L0):
28
+ UNC/drive mapping, origin classification, identity probing."""
29
+
30
+
31
+ class FileOperationError(DazzleError):
32
+ """Domain base for filesystem-primitive failures (dazzle-filekit, L1):
33
+ copy/move, metadata collect/apply, hashing, link creation."""
34
+
35
+
36
+ class LinkError(DazzleError):
37
+ """Domain base for link-serialization failures (dazzle-linklib, L2):
38
+ .dazzlelink parsing, export/import, rebase."""
39
+
40
+
41
+ class PreserveError(DazzleError):
42
+ """Domain base for orchestration failures (dazzle-preservelib, L3):
43
+ manifests, transactional copy/move/restore/verify, conflict policy."""
@@ -0,0 +1,42 @@
1
+ """The one permitted mixin: derive presentation from ``to_dict``.
2
+
3
+ :class:`DazzleDataMixin` is convenience, not contract -- objects may satisfy
4
+ the protocols without it. It exists so simple data objects don't each rewrite
5
+ ``to_json``/``__str__``/``summary`` plumbing. Anything fancier belongs in the
6
+ object itself, not here (charter: this module stays this small).
7
+ """
8
+
9
+ import json
10
+ from typing import Any, Dict
11
+
12
+ __all__ = ["DazzleDataMixin"]
13
+
14
+
15
+ class DazzleDataMixin:
16
+ """Derives ``to_json``, ``summary`` and ``__str__`` from ``to_dict``.
17
+
18
+ The host class supplies ``to_dict()`` (and thereby decides what is
19
+ JSON-safe); the mixin only formats. Combined with a ``from_dict``
20
+ classmethod and a ``SCHEMA_VERSION`` attribute, a host class satisfies
21
+ both :class:`~dazzle_lib.protocols.Serializable` and
22
+ :class:`~dazzle_lib.protocols.Viewable`.
23
+ """
24
+
25
+ def to_dict(self) -> Dict[str, Any]: # pragma: no cover - host overrides
26
+ raise NotImplementedError(
27
+ f"{type(self).__name__} must implement to_dict() to use DazzleDataMixin"
28
+ )
29
+
30
+ def to_json(self, *, indent: int = 2, sort_keys: bool = False) -> str:
31
+ """JSON form of :meth:`to_dict` (``default=str`` catches stragglers)."""
32
+ return json.dumps(
33
+ self.to_dict(), indent=indent, sort_keys=sort_keys, default=str
34
+ )
35
+
36
+ def summary(self) -> str:
37
+ """One-line description: class name plus top-level keys."""
38
+ keys = ", ".join(list(self.to_dict().keys())[:6])
39
+ return f"{type(self).__name__}({keys})"
40
+
41
+ def __str__(self) -> str:
42
+ return self.to_json()
@@ -0,0 +1,127 @@
1
+ """TypedDict payload schemas shared across the DazzleLib stack.
2
+
3
+ These are THE cross-layer payload shapes (STACK-MAP D10): when one stack
4
+ library hands file metadata, timestamps, link descriptions, or hash results to
5
+ another, the value conforms to a shape defined here. Rich objects in upper
6
+ layers serialize themselves INTO these shapes; primitive functions in lower
7
+ layers take and return them directly.
8
+
9
+ The shapes are not invented -- they mirror what ``dazzle-filekit`` actually
10
+ produces today (``collect_file_metadata`` / ``collect_timestamp_info`` /
11
+ ``calculate_file_hash``), so adopting them is a typing change, not a behavior
12
+ change. Admission policy (rule of two): a shape lives here only once two or
13
+ more stack libraries need it.
14
+
15
+ Charter reminder: this module is types-only. No I/O, no behavior.
16
+ """
17
+
18
+ from typing import Dict, Optional, TypedDict
19
+
20
+ __all__ = [
21
+ "TimestampsDict",
22
+ "WindowsMetadataDict",
23
+ "UnixMetadataDict",
24
+ "FileMetadataDict",
25
+ "LinkTargetDict",
26
+ "HashResultDict",
27
+ ]
28
+
29
+
30
+ class TimestampsDict(TypedDict, total=False):
31
+ """File timestamps, epoch floats plus ISO-8601 projections.
32
+
33
+ Mirrors ``dazzle_filekit.metadata.collect_timestamp_info``. Note that
34
+ ``created`` carries ``st_ctime``, which is creation time on Windows but
35
+ inode-change time on Unix -- consumers must not assume birth-time
36
+ semantics cross-platform.
37
+ """
38
+
39
+ created: float
40
+ modified: float
41
+ accessed: float
42
+ created_iso: str
43
+ modified_iso: str
44
+ accessed_iso: str
45
+
46
+
47
+ class WindowsMetadataDict(TypedDict, total=False):
48
+ """Windows-specific metadata (mirrors filekit's ``_collect_windows_metadata``).
49
+
50
+ With pywin32 available the rich fields are present (``attributes``,
51
+ owner/group + SIDs, ``security_descriptor_sddl``); without it the
52
+ ``attrib``-fallback path fills only the boolean flags and
53
+ ``attrib_output``. ``security_descriptor_sddl`` may be present-but-None
54
+ when the descriptor could not be stringified.
55
+ """
56
+
57
+ attributes: int
58
+ is_hidden: bool
59
+ is_system: bool
60
+ is_readonly: bool
61
+ is_archive: bool
62
+ owner: str
63
+ group: str
64
+ owner_sid: str
65
+ group_sid: str
66
+ security_descriptor_sddl: Optional[str]
67
+ attrib_output: str
68
+
69
+
70
+ class UnixMetadataDict(TypedDict, total=False):
71
+ """Unix-specific metadata (mirrors filekit's unix branch)."""
72
+
73
+ uid: int
74
+ gid: int
75
+
76
+
77
+ class _FileMetadataRequired(TypedDict):
78
+ mode: int
79
+ size: int
80
+ timestamps: TimestampsDict
81
+
82
+
83
+ class FileMetadataDict(_FileMetadataRequired, total=False):
84
+ """A file's preservable metadata snapshot.
85
+
86
+ Mirrors ``dazzle_filekit.metadata.collect_file_metadata``: ``mode``,
87
+ ``size``, and ``timestamps`` are always present; exactly one of
88
+ ``windows`` / ``unix`` appears depending on platform; ``xattrs`` (name ->
89
+ base64-encoded value) appears on Unix when extended attributes exist.
90
+ """
91
+
92
+ windows: WindowsMetadataDict
93
+ unix: UnixMetadataDict
94
+ xattrs: Dict[str, str]
95
+
96
+
97
+ class LinkTargetDict(TypedDict, total=False):
98
+ """An intrinsic description of a filesystem link, as data.
99
+
100
+ The cross-layer shape for "what is this link": produced by link analysis
101
+ (dazzle-filekit's ``analyze_link``), embedded in serialized link files
102
+ (dazzle-linklib), and consumed by orchestration policy (dazzle-preservelib).
103
+ Intrinsic properties only -- anything relative to a specific operation
104
+ (e.g. "does this link point inside the move destination") is computed at
105
+ the orchestration layer and does NOT belong here.
106
+
107
+ ``kind`` is one of ``"symlink"``, ``"junction"``, ``"hardlink"``.
108
+ ``raw_target`` is the stored target text exactly as written; ``resolved_target``
109
+ is its absolute resolution (when resolvable). ``target_is_directory`` is
110
+ best-effort (False when the target is missing).
111
+ """
112
+
113
+ kind: str
114
+ raw_target: str
115
+ resolved_target: str
116
+ is_broken: bool
117
+ is_circular: bool
118
+ target_is_directory: bool
119
+
120
+
121
+ HashResultDict = Dict[str, str]
122
+ """Hash results keyed by algorithm name, hex digests as values.
123
+
124
+ Mirrors ``dazzle_filekit.verification.calculate_file_hash`` (e.g.
125
+ ``{"sha256": "ab12...", "md5": "cd34..."}``). A plain ``Dict`` alias rather
126
+ than a TypedDict because the key set is open (any hashlib algorithm).
127
+ """
@@ -0,0 +1,60 @@
1
+ """Structural protocols every DazzleLib stack object is expected to satisfy.
2
+
3
+ These are :class:`typing.Protocol` definitions -- STRUCTURAL contracts, not base
4
+ classes. Nothing in the stack is required to subclass anything here; an object
5
+ satisfies a protocol simply by implementing its methods (and can be checked at
6
+ runtime with ``isinstance`` because both are ``@runtime_checkable``).
7
+
8
+ The design stance (STACK-MAP D10): "the dict is the interface; objects know how
9
+ to become dicts." Rich objects in upper layers (manifest, link-data, results)
10
+ serialize themselves INTO the plain TypedDict payload shapes defined in
11
+ :mod:`dazzle_lib.payloads`; lower-layer functions take and return those dicts.
12
+
13
+ Charter reminder: this module is types-only. No I/O, no behavior.
14
+ """
15
+
16
+ from typing import Any, Dict, Protocol, runtime_checkable
17
+
18
+ __all__ = ["Viewable", "Serializable"]
19
+
20
+
21
+ @runtime_checkable
22
+ class Viewable(Protocol):
23
+ """An object that can present itself to a human.
24
+
25
+ Expectations:
26
+
27
+ - ``summary()`` returns a SHORT one-line description suitable for list
28
+ views, log lines, and progress output.
29
+ - ``__str__`` may be longer (multi-line is fine) but must never raise.
30
+ """
31
+
32
+ def summary(self) -> str:
33
+ """One-line human-readable description of this object."""
34
+ ...
35
+
36
+
37
+ @runtime_checkable
38
+ class Serializable(Protocol):
39
+ """An object that can round-trip through a plain, JSON-safe dict.
40
+
41
+ Expectations:
42
+
43
+ - ``to_dict()`` returns a dict containing only JSON-safe values
44
+ (str/int/float/bool/None/list/dict). Where a shared payload shape
45
+ exists in :mod:`dazzle_lib.payloads`, the dict conforms to it.
46
+ - ``from_dict(data)`` is a classmethod constructing an equivalent object.
47
+ - ``SCHEMA_VERSION`` identifies the dict layout so readers can migrate
48
+ old serialized forms. Bump it on any breaking shape change.
49
+ """
50
+
51
+ SCHEMA_VERSION: int
52
+
53
+ def to_dict(self) -> Dict[str, Any]:
54
+ """Return a JSON-safe dict representation of this object."""
55
+ ...
56
+
57
+ @classmethod
58
+ def from_dict(cls, data: Dict[str, Any]) -> "Serializable":
59
+ """Construct an instance from a dict produced by :meth:`to_dict`."""
60
+ ...
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: dazzle-lib
3
+ Version: 0.1.0
4
+ Summary: The DazzleLib bedrock: shared Protocols, TypedDict payload schemas, and exception root for the dazzle-* library stack. Stdlib-only by charter.
5
+ Author-email: djdarcy <djdarcy@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/DazzleLib/dazzle-lib
8
+ Project-URL: Repository, https://github.com/DazzleLib/dazzle-lib
9
+ Project-URL: Issues, https://github.com/DazzleLib/dazzle-lib/issues
10
+ Keywords: dazzlelib,protocols,typeddict,serialization,bedrock,stack
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # dazzle-lib
32
+
33
+ **The DazzleLib stack's bedrock: shared Protocols, TypedDict payload schemas, and the exception root.**
34
+
35
+ [![PyPI](https://img.shields.io/pypi/v/dazzle-lib)](https://pypi.org/project/dazzle-lib/)
36
+ [![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://pypi.org/project/dazzle-lib/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
38
+ [![Platforms](https://img.shields.io/badge/platforms-all-brightgreen)](docs/platform-support.md)
39
+
40
+ Every `dazzle-*` library ([the stack](https://github.com/DazzleLib/.github/blob/main/docs/STACK-MAP.md)) builds on this package: it defines what stack objects can be expected to do (view themselves, serialize themselves) and what shapes cross-layer payloads have. **Types only** -- by charter this package contains no I/O, no path handling, no platform probing, and no behavior, forever.
41
+
42
+ ```bash
43
+ pip install dazzle-lib
44
+ ```
45
+
46
+ ## What's inside (all of it)
47
+
48
+ | Module | Contents |
49
+ |---|---|
50
+ | `dazzle_lib.protocols` | `Viewable` (`summary()`/`__str__`), `Serializable` (`to_dict`/`from_dict`/`to_json`, `SCHEMA_VERSION`) -- structural `Protocol`s, `runtime_checkable`, nothing is forced to subclass |
51
+ | `dazzle_lib.payloads` | The cross-layer TypedDict schemas: `FileMetadataDict`, `TimestampsDict`, `WindowsMetadataDict`, `UnixMetadataDict`, `LinkTargetDict`, `HashResultDict` -- mirroring what `dazzle-filekit` actually produces |
52
+ | `dazzle_lib.exceptions` | `DazzleError` root + per-domain bases (`PathIdentityError`, `FileOperationError`, `LinkError`, `PreserveError`) |
53
+ | `dazzle_lib.mixins` | `DazzleDataMixin` -- derives `to_json`/`summary`/`__str__` from your `to_dict` |
54
+
55
+ ## The idea: the dict is the interface
56
+
57
+ Rich objects in upper layers know how to become plain dicts; lower-layer functions take and return those dicts. The TypedDicts here are the agreed shapes, so a manifest object in `dazzle-preservelib` and a metadata collector in `dazzle-filekit` speak the same payload without sharing a class hierarchy:
58
+
59
+ ```python
60
+ from dataclasses import dataclass
61
+ from dazzle_lib import DazzleDataMixin, Serializable, FileMetadataDict
62
+
63
+ @dataclass
64
+ class TransferResult(DazzleDataMixin):
65
+ SCHEMA_VERSION = 1
66
+ path: str
67
+ metadata: FileMetadataDict
68
+
69
+ def to_dict(self):
70
+ return {"schema_version": self.SCHEMA_VERSION,
71
+ "path": self.path, "metadata": dict(self.metadata)}
72
+
73
+ @classmethod
74
+ def from_dict(cls, data):
75
+ return cls(path=data["path"], metadata=data["metadata"])
76
+
77
+ result = TransferResult("a.txt", {"mode": 0o644, "size": 10, "timestamps": {}})
78
+ assert isinstance(result, Serializable) # structural -- no subclassing needed
79
+ print(result.summary()) # one-liner for logs
80
+ ```
81
+
82
+ And one catchable root for the whole stack:
83
+
84
+ ```python
85
+ from dazzle_lib import DazzleError
86
+ try:
87
+ ... # any dazzle-* library call
88
+ except DazzleError as e:
89
+ ... # caught, whichever layer raised it
90
+ ```
91
+
92
+ ## The charter (enforced by tests)
93
+
94
+ This package is **stdlib-only forever** and contains **no behavior**. `tests/test_charter.py` fails on any banned import (`os`, `shutil`, `pathlib`, `subprocess`, ...) anywhere in the package -- a PR that needs to weaken that test is adding something that belongs in a higher layer. Admission follows the **rule of two**: a Protocol or TypedDict enters the bedrock only when two or more stack libraries need it.
95
+
96
+ ## The stack
97
+
98
+ | Layer | Library | Role |
99
+ |---|---|---|
100
+ | B | **dazzle-lib** (this) | bedrock contracts |
101
+ | L0 | [dazzle-unctools](https://github.com/DazzleLib/UNCtools) | path identity (UNC/drive/origin) |
102
+ | L1 | [dazzle-filekit](https://github.com/DazzleLib/dazzle-filekit) | filesystem primitives |
103
+ | L2 | dazzle-linklib *(planned)* | link serialization |
104
+ | L3 | dazzle-preservelib *(planned)* | operation orchestration |
105
+ | ⊥ | [dazzle-treelib](https://github.com/DazzleLib/dazzle-tree-lib) | traversal engine |
106
+
107
+ Full architecture contract: [STACK-MAP.md](https://github.com/DazzleLib/.github/blob/main/docs/STACK-MAP.md). API stability policy: [docs/api-stability.md](docs/api-stability.md).
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ pip install -e ".[dev]"
113
+ python -m pytest tests/ -v
114
+ ```
115
+
116
+ ## License
117
+
118
+ MIT -- the bedrock sits beneath MIT and GPL stack members alike, so it carries the permissive license.
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ dazzle_lib/__init__.py
5
+ dazzle_lib/__main__.py
6
+ dazzle_lib/_version.py
7
+ dazzle_lib/exceptions.py
8
+ dazzle_lib/mixins.py
9
+ dazzle_lib/payloads.py
10
+ dazzle_lib/protocols.py
11
+ dazzle_lib.egg-info/PKG-INFO
12
+ dazzle_lib.egg-info/SOURCES.txt
13
+ dazzle_lib.egg-info/dependency_links.txt
14
+ dazzle_lib.egg-info/requires.txt
15
+ dazzle_lib.egg-info/top_level.txt
16
+ tests/test_charter.py
17
+ tests/test_import_stability.py
18
+ tests/test_protocols_and_mixin.py
19
+ tests/test_version.py
@@ -0,0 +1,4 @@
1
+
2
+ [dev]
3
+ pytest>=7.0.0
4
+ pytest-cov>=4.0.0
@@ -0,0 +1 @@
1
+ dazzle_lib
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dazzle-lib"
7
+ dynamic = ["version"]
8
+ description = "The DazzleLib bedrock: shared Protocols, TypedDict payload schemas, and exception root for the dazzle-* library stack. Stdlib-only by charter."
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "djdarcy", email = "djdarcy@users.noreply.github.com"},
14
+ ]
15
+ keywords = ["dazzlelib", "protocols", "typeddict", "serialization", "bedrock", "stack"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Software Development :: Libraries",
28
+ "Typing :: Typed",
29
+ ]
30
+
31
+ dependencies = []
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest>=7.0.0",
36
+ "pytest-cov>=4.0.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/DazzleLib/dazzle-lib"
41
+ Repository = "https://github.com/DazzleLib/dazzle-lib"
42
+ Issues = "https://github.com/DazzleLib/dazzle-lib/issues"
43
+
44
+ [tool.setuptools.dynamic]
45
+ version = {attr = "dazzle_lib._version.PIP_VERSION"}
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["."]
49
+ include = ["dazzle_lib", "dazzle_lib.*"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ python_files = "test_*.py"
54
+ python_functions = "test_*"
55
+ python_classes = "Test*"
56
+
57
+ [tool.repokit-common]
58
+ version-source = "dazzle_lib/_version.py"
59
+ changelog = "CHANGELOG.md"
60
+ repo-url = "https://github.com/DazzleLib/dazzle-lib"
61
+ tag-prefix = "v"
62
+ private-patterns = ["private/", "local/", ".env"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,96 @@
1
+ """The charter test: dazzle-lib is types-only, stdlib-only, behavior-free.
2
+
3
+ This is the god-library guard from the STACK-MAP (D10). It fails the moment
4
+ anyone adds I/O, path handling, platform probing, or subprocess use to the
5
+ bedrock. A PR that needs to weaken this test is, by definition, adding
6
+ behavior that belongs in a higher layer.
7
+ """
8
+
9
+ import ast
10
+ from pathlib import Path
11
+
12
+ import dazzle_lib
13
+
14
+ PACKAGE_DIR = Path(dazzle_lib.__file__).parent
15
+
16
+ # Modules whose import (at any level) means behavior crept in.
17
+ BANNED_IMPORTS = {
18
+ "os", # I/O + platform probing
19
+ "io",
20
+ "shutil",
21
+ "pathlib", # path handling is L1's domain (charter test uses it; the package may not)
22
+ "subprocess",
23
+ "socket",
24
+ "platform",
25
+ "ctypes",
26
+ "tempfile",
27
+ "glob",
28
+ "fnmatch",
29
+ "stat",
30
+ "sys", # no interpreter poking either; types don't need it
31
+ }
32
+
33
+ # _version.py is generated/managed by repokit hooks and exempt (it reads nothing).
34
+ EXEMPT_FILES = {"_version.py"}
35
+
36
+
37
+ def _module_files():
38
+ return [
39
+ p for p in PACKAGE_DIR.glob("*.py")
40
+ if p.name not in EXEMPT_FILES
41
+ ]
42
+
43
+
44
+ def _imports_of(path: Path):
45
+ tree = ast.parse(path.read_text(encoding="utf-8"))
46
+ found = set()
47
+ for node in ast.walk(tree):
48
+ if isinstance(node, ast.Import):
49
+ for alias in node.names:
50
+ found.add(alias.name.split(".")[0])
51
+ elif isinstance(node, ast.ImportFrom):
52
+ if node.module and node.level == 0:
53
+ found.add(node.module.split(".")[0])
54
+ return found
55
+
56
+
57
+ def test_no_banned_imports():
58
+ violations = {}
59
+ for path in _module_files():
60
+ bad = _imports_of(path) & BANNED_IMPORTS
61
+ if bad:
62
+ violations[path.name] = sorted(bad)
63
+ assert not violations, (
64
+ f"CHARTER VIOLATION -- behavior-bearing imports in the bedrock: {violations}. "
65
+ f"dazzle-lib is types-only; this capability belongs in a higher layer."
66
+ )
67
+
68
+
69
+ def test_stdlib_only():
70
+ """No third-party imports, ever (the package must not even import filekit)."""
71
+ allowed = {"json", "typing", "enum", "dataclasses", "abc", "collections",
72
+ "datetime", "ast", "dazzle_lib"}
73
+ violations = {}
74
+ for path in _module_files():
75
+ extra = _imports_of(path) - allowed - BANNED_IMPORTS
76
+ if extra:
77
+ violations[path.name] = sorted(extra)
78
+ assert not violations, (
79
+ f"Unexpected imports in the bedrock (stdlib-only, and only the boring "
80
+ f"parts): {violations}. If legitimately needed, add to the allowlist "
81
+ f"in this test WITH a charter justification in the commit message."
82
+ )
83
+
84
+
85
+ def test_no_filesystem_calls_in_source_text():
86
+ """Belt-and-braces: no open()/Path( usage even without an import."""
87
+ for path in _module_files():
88
+ text = path.read_text(encoding="utf-8")
89
+ tree = ast.parse(text)
90
+ for node in ast.walk(tree):
91
+ if isinstance(node, ast.Call):
92
+ fn = node.func
93
+ name = getattr(fn, "id", getattr(fn, "attr", ""))
94
+ assert name != "open", (
95
+ f"CHARTER VIOLATION -- open() call in {path.name}"
96
+ )
@@ -0,0 +1,73 @@
1
+ """Import-stability canary (see docs/api-stability.md).
2
+
3
+ Every symbol listed here is part of the locked public API. If this test
4
+ fails, a consumer somewhere breaks: do NOT silently fix the test -- follow
5
+ the api-stability.md process (deprecate with a noisy shim, register it,
6
+ slate removal).
7
+ """
8
+
9
+ import importlib
10
+
11
+ LOCKED_SURFACE = {
12
+ "dazzle_lib": [
13
+ "__version__",
14
+ "__app_name__",
15
+ "Viewable",
16
+ "Serializable",
17
+ "TimestampsDict",
18
+ "WindowsMetadataDict",
19
+ "UnixMetadataDict",
20
+ "FileMetadataDict",
21
+ "LinkTargetDict",
22
+ "HashResultDict",
23
+ "DazzleError",
24
+ "PathIdentityError",
25
+ "FileOperationError",
26
+ "LinkError",
27
+ "PreserveError",
28
+ "DazzleDataMixin",
29
+ ],
30
+ "dazzle_lib.protocols": ["Viewable", "Serializable"],
31
+ "dazzle_lib.payloads": [
32
+ "TimestampsDict",
33
+ "WindowsMetadataDict",
34
+ "UnixMetadataDict",
35
+ "FileMetadataDict",
36
+ "LinkTargetDict",
37
+ "HashResultDict",
38
+ ],
39
+ "dazzle_lib.exceptions": [
40
+ "DazzleError",
41
+ "PathIdentityError",
42
+ "FileOperationError",
43
+ "LinkError",
44
+ "PreserveError",
45
+ ],
46
+ "dazzle_lib.mixins": ["DazzleDataMixin"],
47
+ }
48
+
49
+
50
+ def test_locked_surface_importable():
51
+ missing = []
52
+ for module_name, symbols in LOCKED_SURFACE.items():
53
+ module = importlib.import_module(module_name)
54
+ for symbol in symbols:
55
+ if not hasattr(module, symbol):
56
+ missing.append(f"{module_name}.{symbol}")
57
+ assert not missing, (
58
+ f"Locked API symbols missing: {missing} -- see docs/api-stability.md "
59
+ f"before changing the public surface."
60
+ )
61
+
62
+
63
+ def test_exception_hierarchy_rooted():
64
+ from dazzle_lib import (
65
+ DazzleError,
66
+ FileOperationError,
67
+ LinkError,
68
+ PathIdentityError,
69
+ PreserveError,
70
+ )
71
+ for exc in (PathIdentityError, FileOperationError, LinkError, PreserveError):
72
+ assert issubclass(exc, DazzleError)
73
+ assert issubclass(DazzleError, Exception)
@@ -0,0 +1,84 @@
1
+ """Protocol satisfaction + mixin behavior: a plain dataclass with to_dict/
2
+ from_dict/SCHEMA_VERSION satisfies Serializable WITHOUT subclassing anything --
3
+ the structural-typing promise the bedrock makes."""
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from typing import Any, Dict
8
+
9
+ from dazzle_lib import DazzleDataMixin, Serializable, Viewable
10
+
11
+
12
+ @dataclass
13
+ class SampleResult(DazzleDataMixin):
14
+ SCHEMA_VERSION = 1
15
+ name: str = "demo"
16
+ count: int = 3
17
+
18
+ def to_dict(self) -> Dict[str, Any]:
19
+ return {"schema_version": self.SCHEMA_VERSION, "name": self.name,
20
+ "count": self.count}
21
+
22
+ @classmethod
23
+ def from_dict(cls, data: Dict[str, Any]) -> "SampleResult":
24
+ return cls(name=data["name"], count=data["count"])
25
+
26
+
27
+ class BareDuck:
28
+ """No mixin, no inheritance -- pure structural satisfaction."""
29
+
30
+ SCHEMA_VERSION = 2
31
+
32
+ def to_dict(self):
33
+ return {"x": 1}
34
+
35
+ @classmethod
36
+ def from_dict(cls, data):
37
+ return cls()
38
+
39
+ def summary(self):
40
+ return "a bare duck"
41
+
42
+
43
+ def test_dataclass_with_mixin_satisfies_serializable():
44
+ s = SampleResult()
45
+ assert isinstance(s, Serializable)
46
+ assert isinstance(s, Viewable)
47
+
48
+
49
+ def test_bare_class_satisfies_protocols_structurally():
50
+ d = BareDuck()
51
+ assert isinstance(d, Serializable)
52
+ assert isinstance(d, Viewable)
53
+
54
+
55
+ def test_non_conforming_object_rejected():
56
+ assert not isinstance(object(), Serializable)
57
+ assert not isinstance(object(), Viewable)
58
+
59
+
60
+ def test_round_trip():
61
+ s = SampleResult(name="rt", count=7)
62
+ again = SampleResult.from_dict(s.to_dict())
63
+ assert again == s
64
+
65
+
66
+ def test_mixin_to_json_and_str():
67
+ s = SampleResult(name="js", count=1)
68
+ parsed = json.loads(s.to_json())
69
+ assert parsed == {"schema_version": 1, "name": "js", "count": 1}
70
+ assert json.loads(str(s)) == parsed
71
+
72
+
73
+ def test_mixin_summary_is_one_line():
74
+ s = SampleResult()
75
+ assert "\n" not in s.summary()
76
+ assert "SampleResult" in s.summary()
77
+
78
+
79
+ def test_payload_typeddicts_are_constructible():
80
+ from dazzle_lib import FileMetadataDict, TimestampsDict
81
+
82
+ ts: TimestampsDict = {"modified": 1.0, "accessed": 2.0, "created": 3.0}
83
+ md: FileMetadataDict = {"mode": 0o644, "size": 10, "timestamps": ts}
84
+ assert md["timestamps"]["modified"] == 1.0
@@ -0,0 +1,50 @@
1
+ """Tests for version module."""
2
+
3
+ from dazzle_lib._version import (
4
+ MAJOR, MINOR, PATCH, PHASE, PROJECT_PHASE,
5
+ get_version, get_base_version, get_display_version, get_pip_version,
6
+ __app_name__,
7
+ )
8
+
9
+
10
+ def test_app_name():
11
+ assert __app_name__ == "dazzle-lib"
12
+
13
+
14
+ def test_version_components():
15
+ assert isinstance(MAJOR, int)
16
+ assert isinstance(MINOR, int)
17
+ assert isinstance(PATCH, int)
18
+
19
+
20
+ def test_phase_valid():
21
+ """PHASE is empty string (stable release) or a string like 'alpha', 'beta', 'rc1'."""
22
+ assert isinstance(PHASE, str)
23
+
24
+
25
+ def test_get_version_returns_string():
26
+ v = get_version()
27
+ assert isinstance(v, str)
28
+ assert len(v) > 0
29
+
30
+
31
+ def test_base_version_format():
32
+ base = get_base_version()
33
+ assert base.startswith(f"{MAJOR}.{MINOR}.{PATCH}")
34
+
35
+
36
+ def test_display_version_includes_project_phase():
37
+ display = get_display_version()
38
+ if PROJECT_PHASE and PROJECT_PHASE != "stable":
39
+ assert PROJECT_PHASE.upper() in display
40
+ else:
41
+ assert display == get_base_version()
42
+
43
+
44
+ def test_pip_version_pep440():
45
+ pip_v = get_pip_version()
46
+ assert "-" not in pip_v
47
+ if PHASE:
48
+ assert any(c.isalpha() for c in pip_v.split(".")[-1])
49
+ else:
50
+ assert all(c.isdigit() or c == "." for c in pip_v)