Repository: skypyproject/skypy

Issue:
pipeline passes Column objects to functions
**Describe the bug**
The `skypy.pipeline.Pipeline` class passes data as `Column` type to functions. This is problematic when said functions expect a `Quantity`, since `Column` is not entirely `Quantity`-like.

**To Reproduce**
```py
import numpy as np
from astropy import units
from skypy.pipeline import Pipeline


def lengths_in_cm(q):
    return q.to_value(units.cm)


def random_lengths(size):
    return np.random.rand(size)*units.m


config = {
    'tables': {
        'mytable': {
            'lengths': (random_lengths, [10]),
            'lengths_in_cm': (lengths_in_cm, '$mytable.lengths')}}}

pipeline = Pipeline(config)
pipeline.execute()

print(pipeline.state)
```

```shell
$ python test.py
python test.py
Traceback (most recent call last):
  File "test.py", line 21, in <module>
    pipeline.execute()
  File "skypy/pipeline/_pipeline.py", line 193, in execute
    self.state[table][column] = self.get_value(settings)
  File "skypy/pipeline/_pipeline.py", line 245, in get_value
    result = function(parsed_args)
  File "test.py", line 7, in lengths_in_cm
    return q.to_value(units.cm)
AttributeError: 'Column' object has no attribute 'to_value'
```

**Expected behavior**
Table columns with units should be passed in a `Quantity`-like manner.

**Additional context**
This should ideally be fixed inside Astropy, but either way, we need to work around this issue for compatibility with Astropy 4.0 one way or another -- where the "another" way is to only use the subset of `Quantity` methods that `Column` supports, e.g. no `to_value`.


Discussion:

Patch:
diff --git a/skypy/pipeline/_pipeline.py b/skypy/pipeline/_pipeline.py
index ccd6a88..5a42291 100644
--- a/skypy/pipeline/_pipeline.py
+++ b/skypy/pipeline/_pipeline.py
@@ -5,7 +5,7 @@ and handle their results.
 """
 
 from astropy.cosmology import default_cosmology
-from astropy.table import Table
+from astropy.table import Table, Column
 from copy import copy, deepcopy
 from ._config import load_skypy_yaml
 import networkx
@@ -274,4 +274,7 @@ class Pipeline:
                 item = item[key]
             except KeyError as e:
                 raise KeyError('unknown label: ' + name) from e
-        return item
+        if isinstance(item, Column):
+            return item.data if item.unit is None else item.quantity
+        else:
+            return item


Test patch:
diff --git a/skypy/pipeline/tests/test_pipeline.py b/skypy/pipeline/tests/test_pipeline.py
index 54a3045..2e40144 100644
--- a/skypy/pipeline/tests/test_pipeline.py
+++ b/skypy/pipeline/tests/test_pipeline.py
@@ -3,6 +3,7 @@ from astropy.cosmology.core import Cosmology
 from astropy.io import fits
 from astropy.table import Table
 from astropy.table.column import Column
+from astropy.units import Quantity
 from astropy.utils.data import get_pkg_data_filename
 import networkx
 import numpy as np
@@ -219,6 +220,36 @@ def test_pipeline_read():
     assert isinstance(pipeline['test_table_1']['test_column_3'], Column)
 
 
+def test_column_quantity():
+
+    # Regression test for pull request #356
+    # Previously Pipeline.__getitem__ would return column data from tables as
+    # an astropy.table.Column object. However, most functions take either
+    # numpy.ndarray or astropy.units.Quantity objects as arguments. As of
+    # astropy version 4.1.0 Column does not support all of the same class
+    # methods as Quantity e.g. to_value. This test ensures that column data in
+    # a Pipeline is accessed as either an ndarray or Quantity (depending on
+    # units). It also checks that functions using methods not supported by
+    # Column can be called on column data inside a Pipeline.
+
+    def value_in_cm(q):
+        return q.to_value(unit='cm')
+
+    config = {
+        'tables': {
+            'test_table': {
+                'lengths': Quantity(np.random.uniform(size=50), unit='m'),
+                'lengths_in_cm': (value_in_cm, '$test_table.lengths')}}}
+
+    pipeline = Pipeline(config)
+    pipeline.execute()
+
+    assert isinstance(pipeline['test_table.lengths'], Quantity)
+    assert isinstance(pipeline['test_table.lengths_in_cm'], np.ndarray)
+    np.testing.assert_array_less(0, pipeline['test_table.lengths_in_cm'])
+    np.testing.assert_array_less(pipeline['test_table.lengths_in_cm'], 100)
+
+
 def teardown_module(module):
 
     # Remove fits file generated in test_pipeline