|
| 1 | +""" |
| 2 | +Legacy attribute adapter module. |
| 3 | +
|
| 4 | +This module provides backward compatibility for the deprecated AttributeAdapter class. |
| 5 | +New code should use :class:`datajoint.AttributeType` instead. |
| 6 | +
|
| 7 | +.. deprecated:: 0.15 |
| 8 | + Use :class:`datajoint.AttributeType` with ``encode``/``decode`` methods. |
| 9 | +""" |
| 10 | + |
1 | 11 | import re |
| 12 | +import warnings |
| 13 | +from typing import Any |
2 | 14 |
|
3 | | -from .errors import DataJointError, _support_adapted_types |
| 15 | +from .attribute_type import AttributeType, get_type, is_type_registered |
| 16 | +from .errors import DataJointError |
4 | 17 |
|
5 | 18 |
|
6 | | -class AttributeAdapter: |
| 19 | +class AttributeAdapter(AttributeType): |
7 | 20 | """ |
8 | | - Base class for adapter objects for user-defined attribute types. |
| 21 | + Legacy base class for attribute adapters. |
| 22 | +
|
| 23 | + .. deprecated:: 0.15 |
| 24 | + Use :class:`datajoint.AttributeType` with ``encode``/``decode`` methods instead. |
| 25 | +
|
| 26 | + This class provides backward compatibility for existing adapters that use |
| 27 | + the ``attribute_type``, ``put()``, and ``get()`` API. |
| 28 | +
|
| 29 | + Migration guide:: |
| 30 | +
|
| 31 | + # Old style (deprecated): |
| 32 | + class GraphAdapter(dj.AttributeAdapter): |
| 33 | + attribute_type = "longblob" |
| 34 | +
|
| 35 | + def put(self, graph): |
| 36 | + return list(graph.edges) |
| 37 | +
|
| 38 | + def get(self, edges): |
| 39 | + return nx.Graph(edges) |
| 40 | +
|
| 41 | + # New style (recommended): |
| 42 | + @dj.register_type |
| 43 | + class GraphType(dj.AttributeType): |
| 44 | + type_name = "graph" |
| 45 | + dtype = "longblob" |
| 46 | +
|
| 47 | + def encode(self, graph, *, key=None): |
| 48 | + return list(graph.edges) |
| 49 | +
|
| 50 | + def decode(self, edges, *, key=None): |
| 51 | + return nx.Graph(edges) |
9 | 52 | """ |
10 | 53 |
|
| 54 | + # Subclasses can set this as a class attribute instead of property |
| 55 | + attribute_type: str = None # type: ignore |
| 56 | + |
| 57 | + def __init__(self): |
| 58 | + # Emit deprecation warning on instantiation |
| 59 | + warnings.warn( |
| 60 | + f"{self.__class__.__name__} uses the deprecated AttributeAdapter API. " |
| 61 | + "Migrate to AttributeType with encode/decode methods.", |
| 62 | + DeprecationWarning, |
| 63 | + stacklevel=2, |
| 64 | + ) |
| 65 | + |
11 | 66 | @property |
12 | | - def attribute_type(self): |
| 67 | + def type_name(self) -> str: |
13 | 68 | """ |
14 | | - :return: a supported DataJoint attribute type to use; e.g. "longblob", "blob@store" |
| 69 | + Infer type name from class name for legacy adapters. |
| 70 | +
|
| 71 | + Legacy adapters were identified by their variable name in the context dict, |
| 72 | + not by a property. For backward compatibility, we use the lowercase class name. |
15 | 73 | """ |
16 | | - raise NotImplementedError("Undefined attribute adapter") |
| 74 | + # Check if a _type_name was explicitly set (for context-based lookup) |
| 75 | + if hasattr(self, "_type_name"): |
| 76 | + return self._type_name |
| 77 | + # Fall back to class name |
| 78 | + return self.__class__.__name__.lower() |
17 | 79 |
|
18 | | - def get(self, value): |
| 80 | + @property |
| 81 | + def dtype(self) -> str: |
| 82 | + """Map legacy attribute_type to new dtype property.""" |
| 83 | + attr_type = self.attribute_type |
| 84 | + if attr_type is None: |
| 85 | + raise NotImplementedError( |
| 86 | + f"{self.__class__.__name__} must define 'attribute_type' " |
| 87 | + "(or migrate to AttributeType with 'dtype')" |
| 88 | + ) |
| 89 | + return attr_type |
| 90 | + |
| 91 | + def encode(self, value: Any, *, key: dict | None = None) -> Any: |
| 92 | + """Delegate to legacy put() method.""" |
| 93 | + return self.put(value) |
| 94 | + |
| 95 | + def decode(self, stored: Any, *, key: dict | None = None) -> Any: |
| 96 | + """Delegate to legacy get() method.""" |
| 97 | + return self.get(stored) |
| 98 | + |
| 99 | + def put(self, obj: Any) -> Any: |
19 | 100 | """ |
20 | | - convert value retrieved from the the attribute in a table into the adapted type |
| 101 | + Convert an object of the adapted type into a storable value. |
| 102 | +
|
| 103 | + .. deprecated:: 0.15 |
| 104 | + Override ``encode()`` instead. |
21 | 105 |
|
22 | | - :param value: value from the database |
| 106 | + Args: |
| 107 | + obj: An object of the adapted type. |
23 | 108 |
|
24 | | - :return: object of the adapted type |
| 109 | + Returns: |
| 110 | + Value to store in the database. |
25 | 111 | """ |
26 | | - raise NotImplementedError("Undefined attribute adapter") |
| 112 | + raise NotImplementedError( |
| 113 | + f"{self.__class__.__name__} must implement put() or migrate to encode()" |
| 114 | + ) |
27 | 115 |
|
28 | | - def put(self, obj): |
| 116 | + def get(self, value: Any) -> Any: |
29 | 117 | """ |
30 | | - convert an object of the adapted type into a value that DataJoint can store in a table attribute |
| 118 | + Convert a value from the database into the adapted type. |
| 119 | +
|
| 120 | + .. deprecated:: 0.15 |
| 121 | + Override ``decode()`` instead. |
| 122 | +
|
| 123 | + Args: |
| 124 | + value: Value from the database. |
31 | 125 |
|
32 | | - :param obj: an object of the adapted type |
33 | | - :return: value to store in the database |
| 126 | + Returns: |
| 127 | + Object of the adapted type. |
34 | 128 | """ |
35 | | - raise NotImplementedError("Undefined attribute adapter") |
| 129 | + raise NotImplementedError( |
| 130 | + f"{self.__class__.__name__} must implement get() or migrate to decode()" |
| 131 | + ) |
36 | 132 |
|
37 | 133 |
|
38 | | -def get_adapter(context, adapter_name): |
| 134 | +def get_adapter(context: dict | None, adapter_name: str) -> AttributeType: |
39 | 135 | """ |
40 | | - Extract the AttributeAdapter object by its name from the context and validate. |
| 136 | + Get an attribute type/adapter by name. |
| 137 | +
|
| 138 | + This function provides backward compatibility by checking both: |
| 139 | + 1. The global type registry (new system) |
| 140 | + 2. The schema context dict (legacy system) |
| 141 | +
|
| 142 | + Args: |
| 143 | + context: Schema context dictionary (for legacy adapters). |
| 144 | + adapter_name: The adapter/type name, with or without angle brackets. |
| 145 | +
|
| 146 | + Returns: |
| 147 | + The AttributeType instance. |
| 148 | +
|
| 149 | + Raises: |
| 150 | + DataJointError: If the adapter is not found or invalid. |
41 | 151 | """ |
42 | | - if not _support_adapted_types(): |
43 | | - raise DataJointError("Support for Adapted Attribute types is disabled.") |
44 | 152 | adapter_name = adapter_name.lstrip("<").rstrip(">") |
| 153 | + |
| 154 | + # First, check the global type registry (new system) |
| 155 | + if is_type_registered(adapter_name): |
| 156 | + return get_type(adapter_name) |
| 157 | + |
| 158 | + # Fall back to context-based lookup (legacy system) |
| 159 | + if context is None: |
| 160 | + raise DataJointError( |
| 161 | + f"Attribute type <{adapter_name}> is not registered. " |
| 162 | + "Use @dj.register_type to register custom types." |
| 163 | + ) |
| 164 | + |
45 | 165 | try: |
46 | 166 | adapter = context[adapter_name] |
47 | 167 | except KeyError: |
48 | | - raise DataJointError("Attribute adapter '{adapter_name}' is not defined.".format(adapter_name=adapter_name)) |
49 | | - if not isinstance(adapter, AttributeAdapter): |
50 | 168 | raise DataJointError( |
51 | | - "Attribute adapter '{adapter_name}' must be an instance of datajoint.AttributeAdapter".format( |
52 | | - adapter_name=adapter_name |
53 | | - ) |
| 169 | + f"Attribute type <{adapter_name}> is not defined. " |
| 170 | + "Register it with @dj.register_type or include it in the schema context." |
54 | 171 | ) |
55 | | - if not isinstance(adapter.attribute_type, str) or not re.match(r"^\w", adapter.attribute_type): |
| 172 | + |
| 173 | + # Validate it's an AttributeType (or legacy AttributeAdapter) |
| 174 | + if not isinstance(adapter, AttributeType): |
56 | 175 | raise DataJointError( |
57 | | - "Invalid attribute type {type} in attribute adapter '{adapter_name}'".format( |
58 | | - type=adapter.attribute_type, adapter_name=adapter_name |
59 | | - ) |
| 176 | + f"Attribute adapter '{adapter_name}' must be an instance of " |
| 177 | + "datajoint.AttributeType (or legacy datajoint.AttributeAdapter)" |
60 | 178 | ) |
| 179 | + |
| 180 | + # For legacy adapters from context, store the name they were looked up by |
| 181 | + if isinstance(adapter, AttributeAdapter): |
| 182 | + adapter._type_name = adapter_name |
| 183 | + |
| 184 | + # Validate the dtype/attribute_type |
| 185 | + dtype = adapter.dtype |
| 186 | + if not isinstance(dtype, str) or not re.match(r"^\w", dtype): |
| 187 | + raise DataJointError( |
| 188 | + f"Invalid dtype '{dtype}' in attribute type <{adapter_name}>" |
| 189 | + ) |
| 190 | + |
61 | 191 | return adapter |
0 commit comments