|
| 1 | +""" |
| 2 | +Unit tests for the SparkAdapter Codec Protocol (#1458). |
| 3 | +
|
| 4 | +The Protocol is a structural-typing contract — codecs opt in by |
| 5 | +implementing ``to_spark`` and consumers detect support via |
| 6 | +``isinstance(codec, SparkAdapter)``. These tests cover the detection |
| 7 | +behavior, not specific rendering implementations (which live downstream). |
| 8 | +""" |
| 9 | + |
| 10 | +from __future__ import annotations |
| 11 | + |
| 12 | +import datajoint as dj |
| 13 | +from datajoint.spark import SparkAdapter |
| 14 | + |
| 15 | + |
| 16 | +class _SparkAdapterCodec: |
| 17 | + """A minimal codec-like object that opts into the protocol.""" |
| 18 | + |
| 19 | + name = "fake_spark_adapter" |
| 20 | + |
| 21 | + def to_spark(self, decoded, *, key=None): |
| 22 | + return list(decoded) if hasattr(decoded, "__iter__") else decoded |
| 23 | + |
| 24 | + |
| 25 | +class _OpaqueCodec: |
| 26 | + """A minimal codec-like object that does NOT opt into the protocol.""" |
| 27 | + |
| 28 | + name = "fake_opaque" |
| 29 | + |
| 30 | + def encode(self, value, *, key=None, store_name=None): |
| 31 | + return bytes(value) |
| 32 | + |
| 33 | + def decode(self, stored, *, key=None): |
| 34 | + return stored |
| 35 | + |
| 36 | + |
| 37 | +def test_protocol_detects_opt_in(): |
| 38 | + """A class implementing ``to_spark`` is detected as a SparkAdapter.""" |
| 39 | + assert isinstance(_SparkAdapterCodec(), SparkAdapter) |
| 40 | + |
| 41 | + |
| 42 | +def test_protocol_rejects_non_opt_in(): |
| 43 | + """A class without ``to_spark`` is not detected as a SparkAdapter.""" |
| 44 | + assert not isinstance(_OpaqueCodec(), SparkAdapter) |
| 45 | + |
| 46 | + |
| 47 | +def test_protocol_exported_at_top_level(): |
| 48 | + """``dj.SparkAdapter`` is accessible at the top level.""" |
| 49 | + assert dj.SparkAdapter is SparkAdapter |
| 50 | + |
| 51 | + |
| 52 | +def test_protocol_is_runtime_checkable(): |
| 53 | + """The Protocol is decorated with @runtime_checkable (the test fixtures |
| 54 | + above rely on this).""" |
| 55 | + # Direct assertion: classes lacking runtime_checkable would raise TypeError |
| 56 | + # on isinstance(). The previous tests would error rather than fail. |
| 57 | + try: |
| 58 | + isinstance(object(), SparkAdapter) |
| 59 | + except TypeError: |
| 60 | + raise AssertionError("SparkAdapter must be @runtime_checkable") |
| 61 | + |
| 62 | + |
| 63 | +def test_blob_codec_is_not_spark_adapter(): |
| 64 | + """The built-in <blob@> codec is intentionally non-adapting per the spec.""" |
| 65 | + from datajoint.builtin_codecs.blob import BlobCodec |
| 66 | + |
| 67 | + assert not isinstance(BlobCodec(), SparkAdapter) |
| 68 | + |
| 69 | + |
| 70 | +def test_hash_codec_is_not_spark_adapter(): |
| 71 | + """The built-in <hash@> codec is intentionally non-adapting per the spec.""" |
| 72 | + from datajoint.builtin_codecs.hash import HashCodec |
| 73 | + |
| 74 | + assert not isinstance(HashCodec(), SparkAdapter) |
| 75 | + |
| 76 | + |
| 77 | +def test_to_spark_invocation_passes_through(): |
| 78 | + """A codec implementing the method can be invoked and returns its result.""" |
| 79 | + codec = _SparkAdapterCodec() |
| 80 | + assert codec.to_spark([1, 2, 3]) == [1, 2, 3] |
| 81 | + assert codec.to_spark(42) == 42 |
| 82 | + |
| 83 | + |
| 84 | +def test_to_spark_method_accepts_key_kwarg(): |
| 85 | + """The method signature accepts the optional ``key`` keyword argument.""" |
| 86 | + codec = _SparkAdapterCodec() |
| 87 | + # Should not raise |
| 88 | + codec.to_spark([1, 2, 3], key={"some_pk": 1}) |
| 89 | + |
| 90 | + |
| 91 | +def test_subclass_adding_to_spark_becomes_adapter(): |
| 92 | + """A subclass of an opaque codec that adds the method becomes a SparkAdapter.""" |
| 93 | + |
| 94 | + class _OpaqueBase: |
| 95 | + name = "base" |
| 96 | + |
| 97 | + def encode(self, value, *, key=None, store_name=None): |
| 98 | + return b"" |
| 99 | + |
| 100 | + class _TypedSubclass(_OpaqueBase): |
| 101 | + def to_spark(self, decoded, *, key=None): |
| 102 | + return decoded |
| 103 | + |
| 104 | + assert not isinstance(_OpaqueBase(), SparkAdapter) |
| 105 | + assert isinstance(_TypedSubclass(), SparkAdapter) |
0 commit comments