Skip to content

IMPR: Error specificity on empty insert for table with default values #1280

@CBroz1

Description

@CBroz1

Improvement Request

Problem

Table().insert1(dict()) raises an assertion error that says 'Empty Tuple', even if the table has default values for all fields. 'Empty Tuple' may be confusing if the inserted item was an empty dict

Example Script
from datetime import datetime

import datajoint as dj

schema = dj.Schema("cbroz_temp")


@schema
class TestEmptyInsert(dj.Manual):
    definition = """
    id: int auto_increment
    ---
    dt=CURRENT_TIMESTAMP : datetime
    """


if __name__ == "__main__":
    # Accepts either field
    TestEmptyInsert().insert1(dict(id=1))
    TestEmptyInsert().insert1(dict(dt=datetime(2024, 1, 1, 12, 0, 0)))
    # Accepts as None
    TestEmptyInsert().insert1(dict(id=None))
    TestEmptyInsert().insert1(dict(dt=None))
    # Raises AssertionError "Empty tuple"
    TestEmptyInsert().insert1(dict())
Error Stack
AssertionError                            Traceback (most recent call last)
File ~/wrk/spyglass/3pm/temp-insert-empty.py:25
     23 TestEmptyInsert().insert1(dict(dt=None))
     24 # Accepts empty dict
---> 25 TestEmptyInsert().insert1(dict())

File ~/wrk/datajoint-python/datajoint/table.py:349, in Table.insert1(self, row, **kwargs)
    342 def insert1(self, row, **kwargs):
    343     """
    344     Insert one data record into the table. For ``kwargs``, see ``insert()``.
    345
    346     :param row: a numpy record, a dict-like object, or an ordered sequence to be inserted
    347         as one row.
    348     """
--> 349     self.insert((row,), **kwargs)

File ~/wrk/datajoint-python/datajoint/table.py:432, in Table.insert(self, rows, replace, skip_duplicates, ignore_extra_fields, allow_direct_insert)
    430 # collects the field list from first row (passed by reference)
    431 field_list = []
--> 432 rows = list(
    433     self.__make_row_to_insert(row, field_list, ignore_extra_fields)
    434     for row in rows
    435 )
    436 if rows:
    437     try:

File ~/wrk/datajoint-python/datajoint/table.py:433, in <genexpr>(.0)
    430 # collects the field list from first row (passed by reference)
    431 field_list = []
    432 rows = list(
--> 433     self.__make_row_to_insert(row, field_list, ignore_extra_fields)
    434     for row in rows
    435 )
    436 if rows:
    437     try:

File ~/wrk/datajoint-python/datajoint/table.py:940, in Table.__make_row_to_insert(self, row, field_list, ignore_extra_fields)
    937 if ignore_extra_fields:
    938     attributes = [a for a in attributes if a is not None]
--> 940 assert len(attributes), "Empty tuple"
    941 row_to_insert = dict(zip(("names", "placeholders", "values"), zip(*attributes)))
    942 if not field_list:
    943     # first row sets the composition of the field list

AssertionError: Empty tuple

Requirements

Either...

  1. Accept empty entries with tables that have all default-value fields
  2. Improve error message specificity: "Insert must specify at least one key: {row}"

The following could be added to insert1 ~L940 ...

all_defaults=True # Worth caching, maybe as functools.cached_property
for attr in self.heading.attributes.values():
    if attr.default or attr.autoincrement:
        continue
    all_defaults=False
    break

if not all_defaults and not len(attributes):
    raise DataJointError(f"Cannot insert empty row when there are non-default attributes: {row}")

Justification

Allows easier insertion for supported table configurations, specifically tables that log events with inserts

Alternative Considerations

I can specify dict(my_pk=None)

Related Errors

Please include steps to reproduce provided errors as follows:

  • OS: Linux
  • Python Version: 3.11.13
  • MySQL Version: 8.0.34
  • MySQL Deployment Strategy: local-docker
  • DataJoint Version: 0.14.6
  • Minimum number of steps to reliably reproduce the issue: see above
  • Complete error stack as a result of evaluating the above steps: see above

Screenshots

n/a

Additional Research and Context

n/a

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementIndicates new improvementstriageIndicates issues, pull requests, or discussions need to be reviewed for the first time

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions