Skip to content

non-writeable property 'name' in StringIO #598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sharmaeklavya2 opened this issue Oct 11, 2016 · 5 comments
Closed

non-writeable property 'name' in StringIO #598

sharmaeklavya2 opened this issue Oct 11, 2016 · 5 comments

Comments

@sharmaeklavya2
Copy link
Contributor

sharmaeklavya2 commented Oct 11, 2016

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
@JukkaL
Copy link
Contributor

JukkaL commented Oct 11, 2016

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?

@gvanrossum
Copy link
Member

gvanrossum commented Oct 11, 2016 via email

@sharmaeklavya2
Copy link
Contributor Author

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

from six.moves import StringIO as _StringIO
class StringIO(_StringIO):
    name = ''

@gvanrossum
Copy link
Member

gvanrossum commented Oct 11, 2016 via email

sharmaeklavya2 added a commit to sharmaeklavya2/typeshed that referenced this issue Oct 12, 2016
Add an attribute 'name' of type str to StringIO.StringIO in python2
and io.StringIO in python2 and python3.

Fixes python#598.
matthiaskramm pushed a commit that referenced this issue Oct 12, 2016
Add an attribute 'name' of type str to StringIO.StringIO in python2
and io.StringIO in python2 and python3.

Fixes #598.
@hvbtup
Copy link

hvbtup commented Dec 21, 2017

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
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants