Repository: internetarchive/openlibrary

Issue:
StringIO wants str, not bytes
<!-- What does this PR achieve? [feature|hotfix|fix|refactor] -->
Error when editing this edition on Docker on Python 3 only:
`http://localhost:8080/books/OL24173003M/Travels_into_several_remote_nations_of_the_world/edit?debug=true`
```
web_1        | Traceback (most recent call last):
web_1        |   File "/home/openlibrary/.pyenv/versions/3.8.5/lib/python3.8/site-packages/web/application.py", line 280, in process
web_1        |     return self.handle()
web_1        |   File "/home/openlibrary/.pyenv/versions/3.8.5/lib/python3.8/site-packages/web/application.py", line 271, in handle
web_1        |     return self._delegate(fn, self.fvars, args)
web_1        |   File "/home/openlibrary/.pyenv/versions/3.8.5/lib/python3.8/site-packages/web/application.py", line 514, in _delegate
web_1        |     return handle_class(cls)
web_1        |   File "/home/openlibrary/.pyenv/versions/3.8.5/lib/python3.8/site-packages/web/application.py", line 492, in handle_class
web_1        |     return tocall(*args)
web_1        |   File "/openlibrary/infogami/utils/app.py", line 187, in <lambda>
web_1        |     HEAD = GET = POST = PUT = DELETE = lambda self: delegate()
web_1        |   File "/openlibrary/infogami/utils/app.py", line 206, in delegate
web_1        |     return getattr(cls(), method)(*args)
web_1        |   File "/openlibrary/openlibrary/plugins/upstream/addbook.py", line 768, in POST
web_1        |     helper.save(web.input())
web_1        |   File "/openlibrary/openlibrary/plugins/upstream/addbook.py", line 485, in save
web_1        |     work_data, edition_data = self.process_input(formdata)
web_1        |   File "/openlibrary/openlibrary/plugins/upstream/addbook.py", line 609, in process_input
web_1        |     work = self.process_work(i.work)
web_1        |   File "/openlibrary/openlibrary/plugins/upstream/addbook.py", line 653, in process_work
web_1        |     work.subjects = list(read_subject(work.get('subjects', '')))
web_1        |   File "/openlibrary/openlibrary/plugins/upstream/addbook.py", line 645, in read_subject
web_1        |     f = StringIO(subjects.encode('utf-8')) # no unicode in csv module
web_1        | TypeError: initial_value must be str or None, not bytes
```

https://github.com/internetarchive/openlibrary/blame/master/openlibrary/plugins/upstream/addbook.py#L645 was written 11 years ago (2 years before Py3) and it is doing the opposite of what is needed.
1. `b"bytes".decode("utf-8")` is a `str` and `"str".encode("utf-8")` is a `bytes`.
2. `StringIO("wants_a_str")`.
So, the current code is converting a str --> bytes and then calling `StringIO(b"this_is_bytes")` which will always be a problem on Python 3.
This fix closes a dangling StringIO instance, removes a double decode, fixes a new style exception, and fixes the same ol_infobase.py bytes vs. str mixup that is also fixed in @aasifkhan7 's #3733.

### Technical
<!-- What should be noted about the implementation? -->

### Testing
<!-- Steps for reviewer to reproduce/verify what this PR does/fixes. -->

### Screenshot
<!-- If this PR touches UI, please post evidence (screenshots) of it behaving correctly. -->

### Stakeholders
<!-- @ tag stakeholders of this bug -->



Discussion:

Resolving patch:
diff --git a/openlibrary/plugins/upstream/addbook.py b/openlibrary/plugins/upstream/addbook.py
--- a/openlibrary/plugins/upstream/addbook.py
+++ b/openlibrary/plugins/upstream/addbook.py
@@ -2,8 +2,6 @@
 
 import web
 import simplejson
-from collections import defaultdict
-from six import StringIO
 import csv
 import datetime
 
@@ -639,13 +637,25 @@ def process_work(self, work):
         :rtype: web.storage
         """
         def read_subject(subjects):
+            """
+            >>> list(read_subject("A,B,C,B")) == [u'A', u'B', u'C']   # str
+            True
+            >>> list(read_subject(r"A,B,C,B")) == [u'A', u'B', u'C']  # raw
+            True
+            >>> list(read_subject(u"A,B,C,B")) == [u'A', u'B', u'C']  # Unicode
+            True
+            >>> list(read_subject(""))
+            []
+            """
             if not subjects:
                 return
-
-            f = StringIO(subjects.encode('utf-8'))  # no unicode in csv module
+            if six.PY2:
+                subjects = subjects.encode('utf-8')  # no unicode in csv module
+            f = six.StringIO(subjects)
             dedup = set()
             for s in next(csv.reader(f, dialect='excel', skipinitialspace=True)):
-                s = s.decode('utf-8')
+                if six.PY2:
+                    s = s.decode('utf-8')
                 if s.lower() not in dedup:
                     yield s
                     dedup.add(s.lower())


Test patch: