datajoint-python icon indicating copy to clipboard operation
datajoint-python copied to clipboard

Attribute Adapter: Declaration context is not set

Open chrisroat opened this issue 4 years ago • 2 comments
trafficstars

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

chrisroat avatar Aug 26 '21 02:08 chrisroat

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.

chrisroat avatar Aug 26 '21 18:08 chrisroat

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.

dimitri-yatsenko avatar Aug 26 '21 21:08 dimitri-yatsenko