Repository: biopython/biopython

Issue:
`GenBank.read()`/`Genbank.parse()` throw `AttributeError` on `.gb` file fetched from `Entrez`
### Setup

I am reporting a problem with Biopython version, Python version, and operating
system as follows:

```python
import sys; print(sys.version)
import platform; print(platform.python_implementation()); print(platform.platform())
import Bio; print(Bio.__version__)
```

```bash
3.11.5 (main, Aug 24 2023, 15:23:14) [Clang 13.0.0 (clang-1300.0.29.30)]
CPython
macOS-11.6-x86_64-i386-64bit
1.83
```

### Expected behaviour

When called on a handle of a well-formatted `.gb` file, `GenBank.read()`/`GenBank.parse()` are expected to return a `GenBank.Record` instance that contains all the information parsed from the `.gb` file.

### Actual behaviour

For [a particular entry](https://www.ncbi.nlm.nih.gov/nuccore/NC_028230.1), these functions did not parse the file successfully and raised an `AttributeError`.

The generated trace is the following:

```bash
Traceback (most recent call last):

    record = GenBank.read(handle)
             ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/Bio/GenBank/__init__.py", line 1195, in read
    record = next(iterator)
             ^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/Bio/GenBank/__init__.py", line 103, in __next__
    return self._parser.parse(self.handle)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/Bio/GenBank/__init__.py", line 184, in parse
    self._scanner.feed(handle, _consumer)
  File "/usr/local/lib/python3.11/site-packages/Bio/GenBank/Scanner.py", line 465, in feed
    self._feed_header_lines(consumer, self.parse_header())
  File "/usr/local/lib/python3.11/site-packages/Bio/GenBank/Scanner.py", line 1802, in _feed_header_lines
    % consumer.data.name,
      ^^^^^^^^^^^^^^^^^^
AttributeError: 'Record' object has no attribute 'name'
```

### Steps to reproduce

Execute the code snippet below to reproduce the issue (ignore the `Entrez` warning, as we haven't specified an email):

```python
from Bio import Entrez, GenBank

handle = Entrez.efetch(
    db="nucleotide",
    rettype="gb",
    retmode="text",
    id="946699478"
)

try:
    record = GenBank.read(handle)

except AttributeError as e:
    print(
        f"There has been an error:\n\nAttributeError: {e}\n\nThis is the GenBank record:\n\n{handle.read()}"
    )
```


Discussion:
This happened while downloading and processing a massive amount of data. I isolated the entry that caused this issue.
Funny thing is that, if I comment out

```python
warnings.warn(
    "Structured comment not parsed for %s. Is it malformed?"
    % consumer.data.name,
    BiopythonParserWarning,
)
```

from [Bio/GenBank/Scanner.py#L1800-L1804](https://github.com/biopython/biopython/blob/master/Bio/GenBank/Scanner.py#L1800-L1804), it parses the file successfully.
Looks like an error in the warning (accessing an undefined variable).
Cross reference #2851 which added the warning 4 years ago.

Can you work out which record it breaks on? With a single accession to try this would be much easier to fix and test.
The particular record (also used in the example above):

https://www.ncbi.nlm.nih.gov/nuccore/NC_028230.1
It looks like the issue is that data from GenBank are stored in different record classes depending on which abstraction calls the GenBank parser. If you use `Bio.SeqIO.read`, then `Bio.SeqRecord` is used, but if you use `Bio.GenBank.read()`, then `Bio.GenBank.Record` is used.

But, `consumer.data` always points to the record being used by the parser regardless of the record class. `.name` exists on `Bio.SeqRecord`, so this code works fine as long as you use `Bio.SeqIO` to parse the GenBank handle. You can circumvent this whole issue by just using that abstraction instead:
```python
from Bio import Entrez, SeqIO

handle = Entrez.efetch(
    db="nucleotide",
    rettype="gb",
    retmode="text",
    id="946699478"
)

record = SeqIO.read(handle, 'gb')
print(record)
```

But there's no `.name` attribute on `Bio.GenBank.Record` for some reason (there is, however, a `.locus` attribute which does the same job), so you get an attribute error. I guess whoever introduced that warning only tested with SeqIO. I opened PR #4587 which should fix this specific issue by aliasing `.name` to `.locus`.
As per discussion on #4587, the warning currently wrongly assume the SeqRecord based consumer is being used.

The warning should use a local variable, perhaps ``line`` in the warning if that is informative.

Resolving patch:
diff --git a/Bio/GenBank/Scanner.py b/Bio/GenBank/Scanner.py
--- a/Bio/GenBank/Scanner.py
+++ b/Bio/GenBank/Scanner.py
@@ -1798,8 +1798,7 @@ def _feed_header_lines(self, consumer, lines):
                                     not in structured_comment_dict
                                 ):
                                     warnings.warn(
-                                        "Structured comment not parsed for %s. Is it malformed?"
-                                        % consumer.data.name,
+                                        f"Structured comment not parsed on malformed header line: {line}",
                                         BiopythonParserWarning,
                                     )
                                     continue


Test patch:
diff --git a/Tests/test_GenBank.py b/Tests/test_GenBank.py
--- a/Tests/test_GenBank.py
+++ b/Tests/test_GenBank.py
@@ -7759,7 +7759,8 @@ def test_malformed_structured_comment_parsing(self):
             record = SeqIO.read(path, "genbank")
             self.assertNotIn("structured_comment", record.annotations)
             self.assertIn(
-                "Structured comment not parsed for AYW00820.", str(caught[0].message)
+                "Structured comment not parsed on malformed header line",
+                str(caught[0].message),
             )
 
     def test_locus_line_topogoly(self):