Skip to content

parse_wpc_surface_bulletin does not handle StringIO object, results in AttributeError: 'str' object has no attribute 'decode' #3923

Open

Description

@akrherz

What went wrong?

The docstring for parse_wpc_surface_bulletin says

bulletin : str or file-like object
If str, the name of the file to be opened. If bulletin is a file-like object, this will be read from directly.

but passing it a StringIO instance yields an AttributeError

Operating System

Linux

Version

1.1.0.post367+g05e1d20f6

Python Version

3.13

Code to Reproduce

from io import StringIO
from metpy.io import parse_wpc_surface_bulletin

sio = StringIO("""VALID 062818Z
HIGHS 1022 3961069 1020 3851069 1026 3750773 1022 4430845 1019 5520728
LOWS 1016 4510934 1002 3441145 1003 4271229 1002 4471230 1009 4631181
TROF 2971023 2831018 2691008 I2531003
TROF 2911100 2681082 2511055 2431024
 """)
parse_wpc_surface_bulletin(sio, year=2000)

Errors, Traceback, and Logs

Traceback (most recent call last):
  File "metpy_example.py", line 11, in <module>
    parse_wpc_surface_bulletin(sio, year=2000)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/home/akrherz/projects/MetPy/src/metpy/io/text.py", line 102, in parse_wpc_surface_bulletin
    text = file.read().decode('utf-8')
           ^^^^^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'decode'. Did you mean: 'encode'?

Activity

  1. dopplershift commented on Sep 26, 2025

    @dopplershift
    Member

    @akrherz This should work fine if you replace StringIO with BytesIO. I'm not sure how we can detect whether the decode() call is needed without some ugly type-checking for what read() returns.

  2. DWesl commented on May 9, 2026

    @DWesl
    Contributor

    Checking whether decode is possible/succeeds would be simpler:

    text = file.read()
    try:
        text = text.decode('utf-8')
    except AttributeError:
        pass
  3. dopplershift commented on May 11, 2026

    @dopplershift
    Member

    Given that text comes from a call to read(), this isn't so bad. At the same time, I hesitate because this is essentially undoing/revisiting what was an important Python 2->3 change; I just wonder if we're opening a door to a bunch of complicated problems.

  4. DWesl commented on May 12, 2026

    @DWesl
    Contributor

    In that case, you'll want to document that parse_wpc_surface_bulletin only accepts ASCII- or UTF-8-encoded binary-mode file-like objects, not text-mode file-like objects nor binary-mode file-like objects with different encodings, so they don't try

    with open(name, mode="r", encoding="utf-16") as in_file:
        synoptic_setup = parse_wpc_surface_bulletin(in_file)

    but instead

    with open(name, mode="r", encoding="utf-16") as in_file:
        synoptic_setup = parse_wpc_surface_bulletin(io.BytesIO(in_file.read().endode("utf-8")))

    or run the file through iconv before attempting to open it

    (or even

    with open(name, mode="r", encoding="us-ascii") as in_file:
        synoptic_setup = parse_wpc_surface_bulletin(in_file)

    )

    You may also want to make a general statement on the metpy.io page that all files are expected to be passed in binary mode, and therefore in-memory examples should use io.BytesIO rather than io.StringIO, to explain why a file with requirements for its text encoding is expected as a binary-mode file rather than a text-mode file

    (Alternately, add an encoding argument to parse_wpc_surface_bulletin and open_if_needed, defaulting to UTF-8 in the former and None in the latter, since you expect the file to contain text)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type: BugSomething is not working like it should

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions