datajoint-python
datajoint-python copied to clipboard
Attribute Adapter: Declaration context is not set
Bug Report
Description
A computed table referencing a table using an AttributeAdapter triggers and assert about a missing context.
Reproducibility
Include:
- OS: MacOS 11.5.2
- Python: 3.9.6
- MySQL Version
- MySQL Deployment Strategy: local-docker
- DataJoint Version: 0.13.2
- Minimum number of steps to reliably reproduce the issue
import os
os.environ["DJ_USER"] = "root"
os.environ["DJ_PASS"] = "simple"
os.environ["DJ_SUPPORT_ADAPTED_TYPES"] = "TRUE"
import datajoint as dj
class PassthroughAdapter(dj.AttributeAdapter):
attribute_type = 'longblob'
def put(self, obj):
return obj
def get(self, value):
return value
schema = dj.schema('dj_adapted_missing_context')
passthrough = PassthroughAdapter()
@schema
class Table1(dj.Manual):
definition = """
id: int
---
data1: <passthrough>
"""
@schema
class Table2(dj.Computed):
definition = """
-> Table1
"""
def make(self, key):
self.insert1(key)
Table2().populate()
- Complete error stack as a result of evaluating the above steps
Error stack
Connecting root@localhost:3306
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
/var/folders/y3/cy1t8xh52sj0mdfp6dh40t440000gn/T/ipykernel_17178/77561555.py in <module>
35 self.insert1(key)
36
---> 37 Table2().populate()
~/dj_adapted_missing_context/datajoint-python/datajoint/autopopulate.py in populate(self, suppress_errors, return_exception_objects, reserve_jobs, order, limit, max_calls, display_progress, *restrictions)
126 old_handler = signal.signal(signal.SIGTERM, handler)
127
--> 128 keys = (self._jobs_to_do(restrictions) - self.target).fetch("KEY", limit=limit)
129 if order == "reverse":
130 keys.reverse()
~/dj_adapted_missing_context/datajoint-python/datajoint/autopopulate.py in _jobs_to_do(self, restrictions)
90 raise DataJointError(
91 'The populate target lacks attribute %s from the primary key of key_source' % next(
---> 92 name for name in todo.heading.primary_key if name not in self.target.heading))
93 except StopIteration:
94 pass
~/dj_adapted_missing_context/datajoint-python/datajoint/heading.py in primary_key(self)
96 @property
97 def primary_key(self):
---> 98 return [k for k, v in self.attributes.items() if v.in_key]
99
100 @property
~/dj_adapted_missing_context/datajoint-python/datajoint/heading.py in attributes(self)
87 def attributes(self):
88 if self._attributes is None:
---> 89 self._init_from_database() # lazy loading from database
90 return self._attributes
91
~/dj_adapted_missing_context/datajoint-python/datajoint/heading.py in _init_from_database(self)
237 # process adapted attribute types
238 if special and TYPE_PATTERN['ADAPTED'].match(attr['type']):
--> 239 assert context is not None, 'Declaration context is not set'
240 adapter_name = special['type']
241 try:
AssertionError: Declaration context is not set
Expected Behavior
The above script should run without error, and the key_source should be empty.
Screenshots
N/A
Additional Research and Context
A similar error appears in #687
Looks like this problem is due to tables being created using FreeTable when a table traverses its parents. A FreeTable has no context, and thus has no way to access the adapter (adapters are retrieved from the context).
https://github.com/datajoint/datajoint-python/blob/5b81247fdb5861ebef098a5d4cbfe26098143eda/datajoint/table.py#L151
I can solve this in a fork by passing the current context to the parents, but it will only work if the context is shared between parent and child -- which is not guaranteed.
It may be safe to ignore this error since it arises during key_source generation, which should yield primary key values and the adapter is never needed. A good fix for this is to somehow defer invoking the adapter until the first fetch or insert. Simply suppressing this error will work too but we need to make sure that nothing else is affected.