mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) More helpful messages when formula probably needs to use Table.all
				
					
				
			Summary: Raise an exception with a customised message for two cases when a user tries on operation directly on a table without `.all`: 1. For `Table.Col`, where `Col` is an existing column, suggest `Table.all.Col`. If `Col` doesn't exist as a column, fall back to the standard AttributeError. 2. When iterating directly over a table, e.g. `[r for r in Table]`, suggest looping over `Table.all` instead. Test Plan: Added Python unit tests. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3593
This commit is contained in:
		
							parent
							
								
									56e8e1f4b3
								
							
						
					
					
						commit
						eac1f26f3e
					
				@ -133,6 +133,20 @@ class UserTable(object):
 | 
			
		||||
    # the constructor.
 | 
			
		||||
    return []
 | 
			
		||||
 | 
			
		||||
  def __getattr__(self, item):
 | 
			
		||||
    if self.table.has_column(item):
 | 
			
		||||
      raise AttributeError(
 | 
			
		||||
        "To retrieve all values in a column, use `{table_id}.all.{item}`. "
 | 
			
		||||
        "Tables have no attribute '{item}'".format(table_id=self.table.table_id, item=item)
 | 
			
		||||
      )
 | 
			
		||||
    super(UserTable, self).__getattribute__(item)
 | 
			
		||||
 | 
			
		||||
  def __iter__(self):
 | 
			
		||||
    raise TypeError(
 | 
			
		||||
      "To iterate (loop) over all records in a table, use `{table_id}.all`. "
 | 
			
		||||
      "Tables are not directly iterable.".format(table_id=self.table.table_id)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Table(object):
 | 
			
		||||
  """
 | 
			
		||||
 | 
			
		||||
@ -157,6 +157,81 @@ else:
 | 
			
		||||
    self.assertFormulaError(self.engine.get_formula_error('Math', 'custom_err', 3),
 | 
			
		||||
                            Exception, "hello")
 | 
			
		||||
 | 
			
		||||
  def test_missing_all_attribute(self):
 | 
			
		||||
    # Test that `Table.Col` raises a helpful AttributeError suggesting to use `Table.all.Col`.
 | 
			
		||||
    sample = testutil.parse_test_sample({
 | 
			
		||||
      "SCHEMA": [
 | 
			
		||||
        [1, "Table", [
 | 
			
		||||
          [11, "A", "Any", True, "Table.id", "", ""],
 | 
			
		||||
          [12, "B", "Any", True, "Table.id2", "", ""],
 | 
			
		||||
        ]]
 | 
			
		||||
      ],
 | 
			
		||||
      "DATA": {
 | 
			
		||||
        "Table": [
 | 
			
		||||
          ["id"],
 | 
			
		||||
          [1],
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    self.load_sample(sample)
 | 
			
		||||
 | 
			
		||||
    # `Table.id` gives a custom message because `id` is an existing column.
 | 
			
		||||
    self.assertFormulaError(
 | 
			
		||||
      self.engine.get_formula_error('Table', 'A', 1),
 | 
			
		||||
      AttributeError,
 | 
			
		||||
        'To retrieve all values in a column, use `Table.all.id`. '
 | 
			
		||||
        "Tables have no attribute 'id'"
 | 
			
		||||
        + six.PY3 * (
 | 
			
		||||
          "\n\nAn `AttributeError` occurs when the code contains something like\n"
 | 
			
		||||
          "    `object.x`\n"
 | 
			
		||||
          "and `x` is not a method or attribute (variable) belonging to `object`."
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # `Table.id2` gives a standard message because `id2` is not an existing column.
 | 
			
		||||
    error = self.engine.get_formula_error('Table', 'B', 1).error
 | 
			
		||||
    message = str(error)
 | 
			
		||||
    self.assertNotIn('Table.all', message)
 | 
			
		||||
    self.assertIn("'UserTable' object has no attribute 'id2'", message)
 | 
			
		||||
 | 
			
		||||
  def test_missing_all_iteration(self):
 | 
			
		||||
    sample = testutil.parse_test_sample({
 | 
			
		||||
      "SCHEMA": [
 | 
			
		||||
        [1, "MyTable", [
 | 
			
		||||
          [11, "A", "Any", True, "list(MyTable)", "", ""],
 | 
			
		||||
          [12, "B", "Any", True, "list(MyTable.all)", "", ""],
 | 
			
		||||
        ]]
 | 
			
		||||
      ],
 | 
			
		||||
      "DATA": {
 | 
			
		||||
        "MyTable": [
 | 
			
		||||
          ["id"],
 | 
			
		||||
          [1],
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    self.load_sample(sample)
 | 
			
		||||
 | 
			
		||||
    # `list(MyTable)` gives a custom message suggesting `.all`.
 | 
			
		||||
    self.assertFormulaError(
 | 
			
		||||
      self.engine.get_formula_error('MyTable', 'A', 1),
 | 
			
		||||
      TypeError,
 | 
			
		||||
        "To iterate (loop) over all records in a table, use `MyTable.all`. "
 | 
			
		||||
        "Tables are not directly iterable."
 | 
			
		||||
        + six.PY3 * (
 | 
			
		||||
          '\n\nA `TypeError` is usually caused by trying\n'
 | 
			
		||||
          'to combine two incompatible types of objects,\n'
 | 
			
		||||
          'by calling a function with the wrong type of object,\n'
 | 
			
		||||
          'or by trying to do an operation not allowed on a given type of object.'
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # `list(MyTable.all)` works correctly.
 | 
			
		||||
    self.assertTableData('MyTable', data=[
 | 
			
		||||
      ['id', 'A', 'B'],
 | 
			
		||||
      [ 1,   objtypes.RaisedException(TypeError()), [objtypes.RecordStub('MyTable', 1)]],
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
  def test_lookup_state(self):
 | 
			
		||||
    # Bug https://phab.getgrist.com/T297 was caused by lookup maps getting corrupted while
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user