Finishing implementation and doc strings
Tags: gsoc
Published: 2024-06-17 04:44:45

The main implementation and documentation strings phase has been finished.

One of the functions that have been deleted was `parse_unit`, it almost wasn't triggered by any event of HEK events, so I decided to delete it, and we will see in the future if it has any major effects on the code.

Also `parse_columns_to_table` has been refactored by adding more variables to make the code more readable.

This is the new implementation:
```python
def parse_columns_to_table(table, attributes, is_coord_prop = False):
    """
    Parses the columns in an Astropy table and convert the values into Astropy objects.

    Parameters
    ----------
    table: astropy.table
        Astropy table.
    attributes: list
        A list of HEK unit attributes or coordinate attributes.
    is_coord_prop: bool
        To specify if `attributes` is a list of unit attributes or coordinate attributes.

    Returns
    -------
    `astropy.table`

    Raises
    ------
    TypeError
        If `table` is not an Astropy table.
    KeyError
        If any of the attribute dictionaries are missing required keys (i.e. "name", "unit_prop").
    """
    for attribute in attributes:
        if attribute["name"] in table.colnames and ("unit_prop" in attribute or attribute.get("is_chaincode", False)) and attribute.get("is_unit_prop", True):
            unit_attr = ""
            if is_coord_prop:
                unit_attr = "event_coordunit"
            else:
                unit_attr = attribute["unit_prop"]

            new_column = []
            for idx, value in enumerate(table[attribute["name"]]):
                new_value = ""
                if value in ["", None]:
                    new_value = value
                elif attribute.get("is_chaincode", False):
                    new_value = parse_chaincode(value, attribute, table[attribute["unit_prop"]][idx])
                else:
                    unit = get_unit(table[unit_attr][idx])
                    new_value = value * unit
                new_column.append(new_value)

            table[attribute["name"]] = new_column

    for attribute in attributes:
        if attribute.get("is_unit_prop", False) and attribute["name"] in table.colnames:
            del table[attribute["name"]]
    return table

```

Also a public interface `__all__` has been added to `util.py`, until now, the public "api" for `util.py` is:
```python
__all__ = [
    'freeze',
    'parse_times',
    'parse_values_to_quantities',
    'UNIT_FILE_PATH',
    'COORD_FILE_PATH'
]
```
`UNIT_FILE_PATH` and `COORD_FILE_PATH` are used in `test_hek.py`

There was a problem in `get_unit` because of some units that wasn't parsed correctly, one of them is `ergs per cubic centimeter`, because of the first implementation of `get_unit`:
```python
with u.add_enabled_units([cm2, m2, m3]), u.set_enabled_aliases(aliases):
        # Units for coordinate frames have more than one unit, otherwise it will be just one unit.
        # There is an assumption that coord1_unit, coord2_unit and coord3_unit are the same.
        units = re.split(r'[, ]', unit)
        return u.Unit(units[0].lower())
```
The function was taking the first "word" of the input and returns the unit, obviously this will be wrong with `ergs per cubic centimeter` because it will take only `ergs` and returns `u.Unit('erg')`.
But this is the only case with HEK units, the other units works just fine.
This is the new implementation to correct this behavior:
```python
def get_unit(unit):
    """
    Converts string into astropy unit.

    Parameters
    ----------
    unit: str
        The targeted unit

    Returns
    -------
    unit
        Astropy unit object (e.g. <class 'astropy.units.core.Unit'> or <class 'astropy.units.core.CompositeUnit'>)

    Raises
    ------
    ValueError
        Because `unit` did not parse as unit.

    Notes
    ----
    For the complete list of HEK parameters: https://www.lmsal.com/hek/VOEvent_Spec.html

    """
    cm2 = u.def_unit("cm2", u.cm**3)
    m2 = u.def_unit("m2", u.m**2)
    m3 = u.def_unit("m3", u.m**3)
    erg_per_cm3 = u.def_unit("ergs/cm^3", u.erg/u.ml)

    aliases = {
        "steradian": u.sr,
        "arcseconds": u.arcsec,
        "degrees": u.deg,
        "sec": u.s,
        "emx": u.Mx,
        "amperes": u.A,
        "ergs": u.erg,
        "cubic centimeter": u.ml,
        "square centimeter": cm2,
        "cubic meter": m3,
        "square meter": m2,
        "ergs per cubic centimeter": erg_per_cm3,
    }
    with u.add_enabled_units([cm2, m2, m3]), u.set_enabled_aliases(aliases), warnings.catch_warnings():
        # Units for coordinate frames have more than one unit, otherwise it will be just one unit.
        # There is an assumption that coord1_unit, coord2_unit and coord3_unit are the same.
        warnings.filterwarnings("ignore", category=u.UnitsWarning)
        if unit in aliases:
            unit = u.Unit(aliases[unit])
        else:
            unit = u.Unit(re.split(r'[, ]', unit)[0].lower())
        return unit
```
Also, the units warnings were ignored because it's irrelevant to the user of HEK.

So, the next phase is testing, we will see if our assumptions about the unit parsing and the other refactoring were right or not.