# Copyright 2001 by Tarjei Mikkelsen.  All rights reserved.
# Copyright 2007 by Michiel de Hoon.  All rights reserved.
#
# This file is part of the Biopython distribution and governed by your
# choice of the "Biopython License Agreement" or the "BSD 3-Clause License".
# Please see the LICENSE file that should have been included as part of this
# package.

"""Code to work with the KEGG Ligand/Compound database.

Functions:
 - parse - Returns an iterator giving Record objects.

Classes:
 - Record - A representation of a KEGG Ligand/Compound.
"""


from Bio.KEGG import _default_wrap, _struct_wrap, _wrap_kegg, _write_kegg


# Set up line wrapping rules (see Bio.KEGG._wrap_kegg)
name_wrap = [0, "", (" ", "$", 1, 1), ("-", "$", 1, 1)]
id_wrap = _default_wrap
struct_wrap = _struct_wrap


class Record:
    """Holds info from a KEGG Ligand/Compound record.

    Attributes:
     - entry       The entry identifier.
     - name        A list of the compound names.
     - formula     The chemical formula for the compound
     - mass        The molecular weight for the compound
     - pathway     A list of 3-tuples: ('PATH', pathway id, pathway)
     - enzyme      A list of the EC numbers.
     - structures  A list of 2-tuples: (database, list of struct ids)
     - dblinks     A list of 2-tuples: (database, list of link ids)

    """

    def __init__(self):
        """Initialize as new record."""
        self.entry = ""
        self.name = []
        self.formula = ""
        self.mass = ""
        self.pathway = []
        self.enzyme = []
        self.structures = []
        self.dblinks = []

    def __str__(self):
        """Return a string representation of this Record."""
        return (
            self._entry()
            + self._name()
            + self._formula()
            + self._mass()
            + self._pathway()
            + self._enzyme()
            + self._structures()
            + self._dblinks()
            + "///"
        )

    def _entry(self):
        return _write_kegg("ENTRY", [self.entry])

    def _name(self):
        return _write_kegg(
            "NAME", [_wrap_kegg(l, wrap_rule=name_wrap) for l in self.name]
        )

    def _formula(self):
        return _write_kegg("FORMULA", [self.formula])

    def _mass(self):
        return _write_kegg("MASS", [self.mass])

    def _pathway(self):
        s = []
        for entry in self.pathway:
            s.append(entry[0] + "  " + entry[1])
        return _write_kegg("PATHWAY", [_wrap_kegg(l, wrap_rule=id_wrap(16)) for l in s])

    def _enzyme(self):
        return _write_kegg(
            "ENZYME", [_wrap_kegg(l, wrap_rule=name_wrap) for l in self.enzyme]
        )

    def _structures(self):
        s = []
        for entry in self.structures:
            s.append(entry[0] + ": " + "  ".join(entry[1]) + "  ")
        return _write_kegg(
            "STRUCTURES", [_wrap_kegg(l, wrap_rule=struct_wrap(5)) for l in s]
        )

    def _dblinks(self):
        s = []
        for entry in self.dblinks:
            s.append(entry[0] + ": " + " ".join(entry[1]))
        return _write_kegg("DBLINKS", [_wrap_kegg(l, wrap_rule=id_wrap(9)) for l in s])


def parse(handle):
    """Parse a KEGG Ligan/Compound file, returning Record objects.

    This is an iterator function, typically used in a for loop.  For
    example, using one of the example KEGG files in the Biopython
    test suite,

    >>> with open("KEGG/compound.sample") as handle:
    ...     for record in parse(handle):
    ...         print("%s %s" % (record.entry, record.name[0]))
    ...
    C00023 Iron
    C00017 Protein
    C00099 beta-Alanine
    C00294 Inosine
    C00298 Trypsin
    C00348 all-trans-Undecaprenyl phosphate
    C00349 2-Methyl-3-oxopropanoate
    C01386 NH2Mec

    """
    record = Record()
    for line in handle:
        if line[:3] == "///":
            yield record
            record = Record()
            continue
        if line[:12] != "            ":
            keyword = line[:12]
        data = line[12:].strip()
        if keyword == "ENTRY       ":
            words = data.split()
            record.entry = words[0]
        elif keyword == "NAME        ":
            data = data.strip(";")
            record.name.append(data)
        elif keyword == "ENZYME      ":
            while data:
                column = data[:16]
                data = data[16:]
                enzyme = column.strip()
                record.enzyme.append(enzyme)
        elif keyword == "PATHWAY     ":
            map, name = data.split("  ")
            pathway = ("PATH", map, name)
            record.pathway.append(pathway)
        elif keyword == "FORMULA     ":
            record.formula = data
        elif keyword == "MASS        ":
            record.mass = data
        elif keyword == "DBLINKS     ":
            if ":" in data:
                key, values = data.split(":")
                values = values.split()
                row = (key, values)
                record.dblinks.append(row)
            else:
                row = record.dblinks[-1]
                key, values = row
                values.extend(data.split())
                row = key, values
                record.dblinks[-1] = row


if __name__ == "__main__":
    from Bio._utils import run_doctest

    run_doctest()

Bio/KEGG/Compound/__init__.py
# Copyright 2001 by Tarjei Mikkelsen.  All rights reserved.
# Copyright 2007 by Michiel de Hoon.  All rights reserved.
#
# This file is part of the Biopython distribution and governed by your
# choice of the "Biopython License Agreement" or the "BSD 3-Clause License".
# Please see the LICENSE file that should have been included as part of this
# package.

"""Code to work with the KEGG Ligand/Compound database.

Functions:
 - parse - Returns an iterator giving Record objects.

Classes:
 - Record - A representation of a KEGG Ligand/Compound.
"""


from Bio.KEGG import _default_wrap, _struct_wrap, _wrap_kegg, _write_kegg


# Set up line wrapping rules (see Bio.KEGG._wrap_kegg)
name_wrap = [0, "", (" ", "$", 1, 1), ("-", "$", 1, 1)]
id_wrap = _default_wrap
struct_wrap = _struct_wrap


class Record:
    """Holds info from a KEGG Ligand/Compound record.

    Attributes:
     - entry       The entry identifier.
     - name        A list of the compound names.
     - formula     The chemical formula for the compound
     - mass        The molecular weight for the compound
     - pathway     A list of 3-tuples: ('PATH', pathway id, pathway)
     - enzyme      A list of the EC numbers.
     - structures  A list of 2-tuples: (database, list of struct ids)
     - dblinks     A list of 2-tuples: (database, list of link ids)

    """

    def __init__(self):
        """Initialize as new record."""
        self.entry = ""
        self.name = []
        self.formula = ""
        self.mass = ""
        self.pathway = []
        self.enzyme = []
        self.structures = []
        self.dblinks = []

    def __str__(self):
        """Return a string representation of this Record."""
        return (
            self._entry()
            + self._name()
            + self._formula()
            + self._mass()
            + self._pathway()
            + self._enzyme()
            + self._structures()
            + self._dblinks()
            + "///"
        )

    def _entry(self):
        return _write_kegg("ENTRY", [self.entry])

    def _name(self):
        return _write_kegg(
            "NAME", [_wrap_kegg(l, wrap_rule=name_wrap) for l in self.name]
        )

    def _formula(self):
        return _write_kegg("FORMULA", [self.formula])

    def _mass(self):
        return _write_kegg("MASS", [self.mass])

    def _pathway(self):
        s = []
        for entry in self.pathway:
            s.append(entry[0] + "  " + entry[1])
        return _write_kegg("PATHWAY", [_wrap_kegg(l, wrap_rule=id_wrap(16)) for l in s])

    def _enzyme(self):
        return _write_kegg(
            "ENZYME", [_wrap_kegg(l, wrap_rule=name_wrap) for l in self.enzyme]
        )

    def _structures(self):
        s = []
        for entry in self.structures:
            s.append(entry[0] + ": " + "  ".join(entry[1]) + "  ")
        return _write_kegg(
            "STRUCTURES", [_wrap_kegg(l, wrap_rule=struct_wrap(5)) for l in s]
        )

    def _dblinks(self):
        s = []
        for entry in self.dblinks:
            s.append(entry[0] + ": " + " ".join(entry[1]))
        return _write_kegg("DBLINKS", [_wrap_kegg(l, wrap_rule=id_wrap(9)) for l in s])


def parse(handle):
    """Parse a KEGG Ligan/Compound file, returning Record objects.

    This is an iterator function, typically used in a for loop.  For
    example, using one of the example KEGG files in the Biopython
    test suite,

    >>> with open("KEGG/compound.sample") as handle:
    ...     for record in parse(handle):
    ...         print("%s %s" % (record.entry, record.name[0]))
    ...
    C00023 Iron
    C00017 Protein
    C00099 beta-Alanine
    C00294 Inosine
    C00298 Trypsin
    C00348 all-trans-Undecaprenyl phosphate
    C00349 2-Methyl-3-oxopropanoate
    C01386 NH2Mec

    """
    record = Record()
    for line in handle:
        if line[:3] == "///":
            yield record
            record = Record()
            continue
        if line[:12] != "            ":
            keyword = line[:12]
        data = line[12:].strip()
        if keyword == "ENTRY       ":
            words = data.split()
            record.entry = words[0]
        elif keyword == "NAME        ":
            data = data.strip(";")
            record.name.append(data)
        elif keyword == "ENZYME      ":
            while data:
                column = data[:16]
                data = data[16:]
                enzyme = column.strip()
                record.enzyme.append(enzyme)
        elif keyword == "PATHWAY     ":
            map, name = data.split("  ")
            pathway = ("PATH", map, name)
            record.pathway.append(pathway)
        elif keyword == "FORMULA     ":
            record.formula = data
        elif keyword in ("MASS        ", "EXACT_MASS  "):
            record.mass = data
        elif keyword == "DBLINKS     ":
            if ":" in data:
                key, values = data.split(":")
                values = values.split()
                row = (key, values)
                record.dblinks.append(row)
            else:
                row = record.dblinks[-1]
                key, values = row
                values.extend(data.split())
                row = key, values
                record.dblinks[-1] = row


if __name__ == "__main__":
    from Bio._utils import run_doctest

    run_doctest()

Bio/KEGG/Compound/__init__.py
diff --git a/Bio/KEGG/Compound/__init__.py b/Bio/KEGG/Compound/__init__.py
index e270757951b..5f371f9d1ff 100644
--- a/Bio/KEGG/Compound/__init__.py
+++ b/Bio/KEGG/Compound/__init__.py
@@ -153,7 +153,7 @@ def parse(handle):
             record.pathway.append(pathway)
         elif keyword == "FORMULA     ":
             record.formula = data
-        elif keyword == "MASS        ":
+        elif keyword in ("MASS        ", "EXACT_MASS  "):
             record.mass = data
         elif keyword == "DBLINKS     ":
             if ":" in data:
diff --git a/CONTRIB.rst b/CONTRIB.rst
index 24a70495c5c..3f5c6640a4d 100644
--- a/CONTRIB.rst
+++ b/CONTRIB.rst
@@ -131,6 +131,7 @@ please open an issue on GitHub or mention it on the mailing list.
 - Hector Martinez <https://github.com/hmarlo>
 - Hielke Walinga <https://github.com/hwalinga>
 - Hongbo Zhu <https://github.com/hongbo-zhu-cn>
+- Hussein Faara <https://github.com/hfaara18>
 - Hye-Shik Chang <perky at domain fallin.lv>
 - Iddo Friedberg <https://github.com/idoerg>
 - Ilya Flyamer <https://github.com/Phlya>
diff --git a/NEWS.rst b/NEWS.rst
index a161d2e2b35..e974dc17cc2 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -61,6 +61,7 @@ possible, especially the following contributors:
 - Damien Goutte-Gattat
 - Erik  Whiting
 - Fabian Egli
+- Hussein Faara (first contribution)
 - Manuel Lera Ramirez
 - João Rodrigues
 - Markus Piotrowski
diff --git a/Tests/test_KEGG.py b/Tests/test_KEGG.py
index c21fbd6c03c..6855e4d7fae 100644
--- a/Tests/test_KEGG.py
+++ b/Tests/test_KEGG.py
@@ -134,7 +134,7 @@ def test_sample(self):
             records = list(Compound.parse(handle))
         self.assertEqual(len(records), 8)
         self.assertEqual(records[1].entry, "C00017")
-        self.assertEqual(records[1].mass, "")  # Why?
+        self.assertEqual(records[1].mass, "")
         self.assertEqual(records[1].formula, "C2H4NO2R(C2H2NOR)n")
         self.assertEqual(records[1].name, ["Protein"])
         self.assertEqual(
@@ -145,12 +145,13 @@ def test_sample(self):
         self.assertEqual(records[1].structures, [])
         self.assertEqual(records[1].dblinks[0], ("PubChem", ["3319"]))
         self.assertEqual(
-            str(records[-1]).replace(" ", "").split("\n")[:10],
+            str(records[-1]).replace(" ", "").split("\n")[:11],
             [
                 "ENTRYC01386",
                 "NAMENH2Mec",
                 "7-Amino-4-methylcoumarin",
                 "FORMULAC10H9NO2",
+                "MASS175.0633",
                 "DBLINKSCAS:26093-31-2",
                 "PubChem:4580",
                 "ChEBI:51771",
@@ -167,6 +168,17 @@ def test_irregular(self):
         self.assertEqual(len(records), 2)
         self.assertEqual(records[0].entry, "C01454")
 
+    def test_mass(self):
+        """record.mass tests."""
+        with open("KEGG/compound.sample") as handle:
+            records = list(Compound.parse(handle))
+        self.assertEqual(records[0].entry, "C00023")
+        self.assertEqual(records[0].mass, "55.9349")
+        self.assertEqual(records[2].entry, "C00099")
+        self.assertEqual(records[2].mass, "89.0477")
+        self.assertEqual(records[3].entry, "C00294")
+        self.assertEqual(records[3].mass, "268.0808")
+
 
 class MapTests(unittest.TestCase):
     """Bio.KEGG.Map tests."""

KEGG.Compound.parse not returning mass 
### Setup

I am reporting a problem with Biopython version, Python version, and operating
system as follows:

1.78
3.9.12
Windows 10 Pro

### Expected behaviour

Calling KEGG.Compound.parse on a KEGG record should return a KEGG record object containing the mass. For example, compound C00120 should have a mass attribute containing 244.0882.

### Actual behaviour

However, no mass attribute is returned. 

### Steps to reproduce

```
from Bio.KEGG.Compound import parse
from Bio.KEGG.REST import kegg_get
c00120 = next(parse(kegg_get('C00120')))
print(c00120.mass)
```
### Fix
This is because the KEGG record now uses separate EXACT_MASS and MOL_WEIGHT fields (can be seen by running kegg_get('C00120').read()). Fixed by replacing line 156 in KEGG.Compound.__init__.py with:
`elif keyword == "EXACT_MASS  ":`


