Repository: skypyproject/skypy

Issue:
simple specification of unitful quantities in config files
We need an easy way to specify numbers with units in the config files. Currently those need to be constructed manually:

```yaml
survey_area: !astropy.units.Quantity '1000 deg2'
```

This can be simplified in the first instance by registering a custom `!quantity` tag that does the construction:

```yaml
survey_area: !quantity '1000 deg2'
```

After which we can register a custom resolver that matches values that look like `number + string` to the `!quantity` tag:

```yaml
survey_area: 1000 deg2
```

Discussion:

Patch:
diff --git a/skypy/pipeline/_config.py b/skypy/pipeline/_config.py
index f0c4d5a..f2aa6a0 100644
--- a/skypy/pipeline/_config.py
+++ b/skypy/pipeline/_config.py
@@ -1,6 +1,8 @@
 import builtins
 from importlib import import_module
 import yaml
+import re
+from astropy.units import Quantity
 
 __all__ = [
     'load_skypy_yaml',
@@ -52,10 +54,22 @@ class SkyPyLoader(yaml.SafeLoader):
 
         return (function,) if args == '' else (function, args)
 
+    def construct_quantity(self, node):
+        value = self.construct_scalar(node)
+        return Quantity(value)
+
 
 # constructor for generic functions
 SkyPyLoader.add_multi_constructor('!', SkyPyLoader.construct_function)
 
+# constructor for quantities
+SkyPyLoader.add_constructor('!quantity', SkyPyLoader.construct_quantity)
+# Implicitly resolve quantities using the regex from astropy
+SkyPyLoader.add_implicit_resolver('!quantity', re.compile(r'''
+    \s*[+-]?((\d+\.?\d*)|(\.\d+)|([nN][aA][nN])|
+    ([iI][nN][fF]([iI][nN][iI][tT][yY]){0,1}))([eE][+-]?\d+)?[.+-]? \w* \W+
+''', re.VERBOSE), list('-+0123456789.'))
+
 
 def load_skypy_yaml(filename):
     '''Read a SkyPy pipeline configuration from a YAML file.


Test patch:
diff --git a/skypy/pipeline/tests/data/quantities.yml b/skypy/pipeline/tests/data/quantities.yml
new file mode 100644
index 0000000..3745e8f
--- /dev/null
+++ b/skypy/pipeline/tests/data/quantities.yml
@@ -0,0 +1,2 @@
+42_km: !quantity 42.0 km
+1_deg2: 1 deg2
diff --git a/skypy/pipeline/tests/test_config.py b/skypy/pipeline/tests/test_config.py
index a3d8164..525c616 100644
--- a/skypy/pipeline/tests/test_config.py
+++ b/skypy/pipeline/tests/test_config.py
@@ -2,6 +2,7 @@ from astropy.utils.data import get_pkg_data_filename
 from collections.abc import Callable
 import pytest
 from skypy.pipeline import load_skypy_yaml
+from astropy import units
 
 
 def test_load_skypy_yaml():
@@ -31,3 +32,12 @@ def test_load_skypy_yaml():
     filename = get_pkg_data_filename('data/bad_module.yml')
     with pytest.raises(ImportError):
         load_skypy_yaml(filename)
+
+
+def test_yaml_quantities():
+    # config with quantities
+    filename = get_pkg_data_filename('data/quantities.yml')
+    config = load_skypy_yaml(filename)
+
+    assert config['42_km'] == units.Quantity('42 km')
+    assert config['1_deg2'] == units.Quantity('1 deg2')