-
Notifications
You must be signed in to change notification settings - Fork 6
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
Python 3 support #3
Conversation
Footnote: pipelining is broken
The Python 3.6 documentation for HTTPConnection.get_response() has this note: Note that you must have read the whole response before you can send a new request to the server. As far as I understand, the problem is that HTTPResponse() wraps the raw socket object in a buffered reader that reads ahead, so the first response object gets all of the pipelined responses into the buffer of the socket wrapper that is discarded and not passed over to subsequent responses.
It's very hard to debug these because exceptions in the thread are swallowed, not printed.
No Python 3.4 because it doesn't support b'...' % (...).
I don’t use zope.server, but I’m happy (and honored) to review. It may be a day or so. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! This looks pretty good to me.
I left some comments, mostly stylistic or potential ways to "tighten" the port for closer equivalence between the versions.
setup.py
Outdated
'Programming Language :: Python :: Implementation :: CPython', | ||
'Natural Language :: English', | ||
'Operating System :: OS Independent', | ||
'Topic :: Internet :: WWW/HTTP', | ||
'Framework :: Zope3'], | ||
'Framework :: Zope3', | ||
], | ||
url='http://pypi.python.org/pypi/zope.server', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be changed to https://github.com/zopefoundation/zope.server/
?
src/zope/server/buffers.py
Outdated
except ImportError: | ||
from StringIO import StringIO | ||
from io import BytesIO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason not to use io.BytesIO
on both platforms (so that we're guaranteed consistent with the use of bytes/text)? (Related: zopefoundation/ZServer#7 (comment))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not! I'll try it and see how it goes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's how it goes: all tests pass. But since test coverage is 78%, ¯\_(ツ)_/¯
(That's partially why I preferred sticking with cStringIO on Python 2.7: it might not work on Python 3, but at least it wouldn't introduce regressions on Python 2.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're pretty close to 100% coverage. I can work on a followup PR to try to get us there.
src/zope/server/ftp/publisher.py
Outdated
try: | ||
from cStringIO import StringIO | ||
except ImportError: | ||
from io import StringIO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, can we use the io
class on both platforms?. (Although io.StringIO
looks odd to me; is this file really dealing in the text domain?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The annoying thing with io.StringIO on Python 2 is that it doesn't accept native ASCII-only strings. This makes it painful when you're, e.g., mocking out sys.stdout. As a result I got into the habit of staying with cStringIO on Python 2 when I'm porting something.
I don't remember where this particular StringIO instance is used; perhaps unicode would work after all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used just once: request = self.request_factory(StringIO(''), env)
. Probably the main reason that works is that it's an empty string and there's nothing to iterate or parse. I'm guessing other layers would have issues with unicode text there, and so this should really be ByteIO.
@@ -194,7 +194,7 @@ def cmd_dele(self, args): | |||
def cmd_help(self, args): | |||
'See IFTPCommandHandler' | |||
self.reply('HELP_START', flush=0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that LineServerChannel.reply
handles encoding, whereas write
does not (expects bytes). I found that slightly surprising and had to look it up to verify that a prefix wasn't missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mostly an accident of implementation: I was trying to get the tests to pass, instead of stopping and thinking about what makes sense in the API.
Thank you for paying attention to such details. I'll think about this some more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so LineServerChannel.reply()
doesn't take a reply string, it takes a reply code, which is used to look up the reply string in the status_messages
dict.
TBH I initially assumed those codes would be integers (because why would I familiarize myself with code I'm trying to modify? there are tests so I wouldn't have to think /s).
Now about the encoding -- perhaps it would be cleaner if the status_messages dict contained bytestrings as values, and if the args passed to reply()
also were bytes. I'll think about this some more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, given that I want Python 3.4 support after all, it'd be difficult to have reply implement %-formatting I require args to be bytes. I'm going to keep this as is.
Besides, I don't expect zope.server FTP support to have many users.
self.reply('ERR_NO_LIST', str(err)) | ||
return | ||
ok_reply = ('OPEN_DATA_CONN', self.type_map[self.transfer_mode]) | ||
cdc = RETRChannel(self, ok_reply) | ||
try: | ||
cdc.write(s) | ||
cdc.write(s.encode('utf-8')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked this up too 😄 . RFC2640 does extend the FTP character set from ASCII to UTF-8.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What a lucky guess! :)
start_new_thread(self.handlerThread, (thread_no,)) | ||
t = threading.Thread(target=self.handlerThread, | ||
args=(thread_no,), | ||
name='zope.server-%d' % thread_no) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, it looks like this is the thread being left behind by testServerAsProxy
.
try: | ||
from cStringIO import StringIO | ||
except ImportError: | ||
from io import StringIO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be used as the underlying buffer of a logging.StreamHandler. Similar comments as in test_wsgiserver.py (when it's used to replace sys.stdXXX) apply.
tox.ini
Outdated
coverage | ||
commands = | ||
coverage run -m zope.testrunner --test-path=src {posargs:-pvc} | ||
coverage report -m |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A --fail-under=XX
might be helpful. Even if XX is not 100% yet it at least captures the current state and prevents regressions.
.travis.yml
Outdated
- 2.7 | ||
- 3.5 | ||
- 3.6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we have a coverage tox environment, can we get coveralls added here?
Can we enable pip caching?
(And maybe ditch tox-travis/tox)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's a good package to cargo-cult the current .travis-ci.yml best practices from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I often copy zope.viewlet:
language: python
sudo: false
python:
- 2.7
- 3.4
- 3.5
- 3.6
- pypy
- pypy3
install:
- pip install -U pip setuptools
- pip install -U coverage coveralls
- pip install -U -e .[test]
script:
- coverage run -m zope.testrunner --test-path=src
after_success:
- coveralls
notifications:
email: false
cache: pip
(Of course that leaves off --auto-color
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--auto-...
is overkill for Travis, IMHO, we know they give us a pty. I like zope-testrunner ... -pvc
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know if the before_cache: rm -f $HOME/.cache/pip/log/debug.log
I snarfed from zope.vocabularyregistry is no longer necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pip now seems to touch a bunch of files in its cache directory when it runs (probably something to do with the up-to-date check?) so removing that file no longer prevents a new cache archive from being created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I say that, but when I re-ran this build, it didn't write a new archive:
$ rm -f $HOME/.cache/pip/log/debug.log
store build cache
nothing changed, not updating cache
Looking at some other projects, it's hard to tell exactly when pip will touch files. Here's the output for a different projects where only six
was downloaded and updated.
0.01s$ rm -f $HOME/.cache/pip/log/debug.log
cache.2
store build cache
0.01s
4.53schange detected (content changed, file is created, or file is deleted):
/home/travis/.cache/pip/http/0/5/1/d/d/051ddbcfada687fb27df4b43966167a67f6706da37bcbb89d2838836
/home/travis/.cache/pip/http/0/a/8/f/a/0a8faabd212d81beff3ad0e11f3e4746188c0ad05c9190218de2e48a
/home/travis/.cache/pip/http/0/e/7/b/9/0e7b99547a0247a24df2175f35e166cf96f0194417e99b4851dbf306
/home/travis/.cache/pip/http/1/1/4/a/4/114a41049557a84c55958c18d02d44eb147fadc5d401e269e3d7ea8e
/home/travis/.cache/pip/http/1/c/f/2/e/1cf2ec08b116d0e8a4bd0c1c6441595628f3d6f6275c7d9cb54fb0c2
/home/travis/.cache/pip/http/2/6/6/b/d/266bdf220752466a969e134263f71564e77e2b9b41ad250878d8f5d8
/home/travis/.cache/pip/http/2/b/9/3/c/2b93c2d17b7b3f0ea9392df02b2eaa5b41f0eefacc87dc903a847b70
/home/travis/.cache/pip/http/2/f/1/0/6/2f10635579eda75bec25d08978dec5575ccd2fa532b1f347d21ec0a1
/home/travis/.cache/pip/http/3/7/2/f/2/372f280a7b4ee088e9327ee15a8c8095522419e73ce895ede6016b18
/home/travis/.cache/pip/http/3/a/f/3/a/3af3addf06e983a6c02f46e7bea70c221d3ff95bf1418fa6da354e14
/home/travis/.cache/pip/http/4/a/1/1/8/4a
...
tox.ini
Outdated
py26,py27 | ||
py27, | ||
py35, | ||
py36, | ||
|
||
[testenv] | ||
deps = | ||
zope.testrunner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to list zope.testrunner in the [test]
extra. It simplifies both tox.ini and .travis.yml, and (to me at least) it helps make it more clear that we're unlikely to run with 'python setup.py test'.
For consistency with PMDBHTTPServer, and so you can interrupt infinite loops and drop into a debugger with a ^C.
Even in test code, where we're not expecting anything other than plain ASCII. (I've been convinced that following good style is a good idea even in test code.)
It was not as difficult as I thought it would be.
Any further comments before I hit the big green Merge button? |
LGTM! Thanks for your work on this. |
The next item on my list is zopefoundation/zope.app.server#1, which is waiting for a zope.server PyPI release. I think it makes sense to wait for your promised 100% test coverage PR and outdated logging infrastructure removal PRs before I make one. (Actually it doesn't have to be me -- I gave you the PyPI bit just now.) |
Coverage in the 100_coverage branch is at 86.4% and rising. There are only ~20 files that have less than 100% right now. |
BTW I am curious -- do you have permissions to edit the Coveralls.io settings at https://coveralls.io/github/zopefoundation/zope.server/settings? I've set the COVERAGE THRESHOLD FOR FAILURE (why is coveralls shouting at me?) to 78%, and it'll likely need to be updated once we get to 100%. (I've also set COVERAGE DECREASE THRESHOLD FOR FAILURE to 1%, so any backslides would have to be gradual.) |
I do, yes. I was able to change it to 77% just now. (I don't see coveralls shouting here? It shows green checkmarks.) |
(I was referring to the pervasive ALL UPPERCASE TEXT in coveralls's UI.) |
Why am I doing this? I want zodbbrowser to run on Python 3, just to fill in a grid cell in the matrix at http://projects.gedmin.as/#python-versions.
Status: all tests pass. (Including zodbbrowser's functional tests that run an actual HTTP server and talk to it over TCP sockets.)
Remaining work: