Skip to content

non-writeable property 'name' in StringIO #598

Closed

Description

@sharmaeklavya2

StringIO (I'm talking about io.StringIO in python3 and python2 and StringIO.stringIO in python2) doesn't have an attribute called name.

>>> h.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: StringIO instance has no attribute 'name'

However, we can set that attribute and assign it any value

>>> h.name = 4
>>> h.name
4 

But according to typeshed, StringIO.StringIO is a subclass of IO[str] and io.StringIO is a subclass of IO[Text]. In typeshed, IO defines name using @property. So there's a getter for it but no setter. So according to typeshed, StringIO.name is readable (which is false until it is set it), but not writeable. This doesn't correspond to what happens at run-time.

Maybe we can write a setter for it in StringIO. I don't know whether mypy currently supports setters.

In some of zulip's tests, we're passing StringIO objects to functions which accept file-like objects. Since those functions (which are probably part of django) read name of the file-like objects, we have to set a suitable value of name for those file-like objects. But when we do that, mypy complains:

error: Property "name" defined in "StringIO" is read-only

Activity

  1. JukkaL commented on Oct 11, 2016

    @JukkaL
    Contributor

    File objects don't have a writable name attribute, so the IO stub seems reasonable. What seems to be going on is that StringIO does not fully implement the IO interface, but we declare it as a subclass of IO for convenience, since in most respects it acts like an IO object.

    A somewhat correct fix would be to 'undefine' name in StringIO, but there's no standard way to do that. Defining it as a regular attribute seems like a lie, if it actually isn't defined. You can assign to an arbitrary attribute such as asdfasdf and Python will not complain. We want to be able to catch assignments to undefined attributes.

    My suggestion is to use setattr to set the attribute, or to cast the StringIO object to Any before performing the manipulations, as this feels like something a type checker can't easily support without allowing genuinely bad code to pass type checking. But perhaps this is special and I'm missing something?

  2. gvanrossum commented on Oct 11, 2016

    @gvanrossum
    Member
  3. sharmaeklavya2 commented on Oct 11, 2016

    @sharmaeklavya2
    ContributorAuthor

    For now I'm using this workaround in zulip's code:

    from six.moves import StringIO as _StringIO
    class StringIO(_StringIO):
        name = ''
  4. gvanrossum commented on Oct 11, 2016

    @gvanrossum
    Member
  5. added a commit that references this issue on Oct 12, 2016
    8b70ed1
  6. added a commit that references this issue on Oct 12, 2016
    7f7a578
  7. hvbtup commented on Dec 21, 2017

    @hvbtup

    I have a similar issuee. What I'm trying to do is treat a cx_Oracle.CLOB value like a stream (which seems quite natural, only the LOB API is a bit different).

    My attempt is like this:

    class UnbufferedClobReader(io.TextIOBase):
        """
        A file-like wrapper for a read-only cx_Oracle CLOB object.
        """
        def __init__(self, clobLocator, encoding):
            self.clobLocator = clobLocator
            self.offset = 0
            self.encoding = encoding
            self.size = clobLocator.size()
            log.debug("CLOB reader created, size=%s", self.size)
    
        def seekable(self):
            return True
        
        def seek(self, offset, whence):
            log.debug("CLOB seek: offset=%s, whence=%r", offset, whence)
            if whence == 0:
                self.offset = offset
            elif whence == 1:
                self.offset += offset
                if self.offset < 0:
                    self.offset = 0
            elif whence == 2:
                if offset <= 0 and -offset <= self.size:
                    self.offset = self.size + offset
                else:
                    raise IOError(96, "Invalid offset for CBlobReader")
            else:
                self._unsupported("seek")
            return self.offset
    
        def readable(self):
            return True
            
        def read(self, size):
            size = min(self.size - self.offset, size)
            log.debug("CLOB read %s chars at offset %s", size, self.offset)
            if size == 0:
                return ""
            chunk = self.clobLocator.read(self.offset + 1, size)
            assert len(chunk) == size
            self.offset += size
            return chunk
    

    Note: Actually I only need the read method in my code.

    The constructor fails with the exception
    AttributeError: attribute 'encoding' of '_io._TextIOBase' objects is not writable

    Having non-writable attributes seems somewhat un-pythonic to me.
    Any ideas how specifying the encoding (for when the stream has to be written to a byte stream) can be accomplished?

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions