Closed ale-rt closed 5 years ago
My personal opinion: using sixer sound a really good idea 👍 👍 👍 👍 run it automatically on all our packages a really bad one 👎 👎 👎
Although I guess, I have barely tried six before, that six is mostly safe, our test matrix is quite complex enough to give it one commit on each package and then have fun trying to figure out which one is responsible of the breakage.
I love this talk from this year's pycon: https://www.youtube.com/watch?v=66XoCk79kjM what relates this talk to this issue is: "one commit one specific change", i.e. don't fix 300 different little changes on a single commit, because even if 300 little changes one on their own are really small and innocent, 300 at the same time are an easy way to slip simple mistakes that go unseen, because, who reviews a boring pull request doing only 300 small cleanups that spans 90 files with a diff of +1000 lines? If that same diff is split into 300 chunks is far more easier to spot where the mistake has been made (if any).
I started to document the process. Feel free to update as needed.
Install modernize
(https://pypi.python.org/pypi/modernize) and sixer
(https://pypi.python.org/pypi/sixer) in a fresh python3 virtualenv. sixer does not run in python 2.7
$ python3 -m venv py3
$ cd py3
$ ./bin/pip install modernize
$ ./bin/pip install sixer
$ source bin/activate
Pick a package to work on. Add it to https://github.com/plone/Products.CMFPlone/issues/2368 with your name. If the package is not yet in checkouts.cfg
add it and run buildout. Note: Do not commit this change into the coredev! When a merge-request is merged mr.developer automatically adds the package to this file.
Run sixer for a complete package in coredev to get an idea how much will need to be changed:
$ sixer all src/plone.app.robotframework
Create a new branch for the package you are working on:
$ cd coredev/src/plone.app.robotframework
$ git checkout -b python3
Make all changes in-place:
$ sixer all -w src/plone.app.robotframework
Afterwards run isort with the Plone settings. If the settings are configured globally and isort is in the path, then sixer can be run like this instead::
$ sixer all --app=isort -w src/plone.app.robotframework
If that does not work (for some it does not) you have to check that sixer did not import six
in silly places.
Now you need to:
sixer
puts import six
at the bottom of all imports. We sort it along all other imports.sixer
made really make sense. Sort the imports according to the Plone coding guidelines: Do not leave import six
at the bottom of the imports unless it belongs there.Run the tests for this package (in python 2.7) e.g.
$ ./bin/test --all -s plone.app.robotframework
sixer
gives warnings when it is not sure what to do with a possible problem. You should check those. On http://python-future.org/compatible_idioms.html you may find a good way to write something that is Python 2 and 3 compatible.
sixer
is not picking up doctests automatically. It only changes files ending with .py
. Others need to be specified explicitly, e.g.:
$ sixer all -w src/plone.testing/src/plone/testing/*.rst
$ sixer all -w src/plone.app.content/plone/app/content/*.txt
$ sixer all -w src/Products/CMFPlone/skins/plone_login/*
Also sixer
for some reason does not always fix doctests, so you will have to do that by hand 😭 . See #2268 for more discussion about doctests.
Another thing sixer
cannot do is change deprecated imports or patterns (like using @implementer()
instead of implements()
. Most of these issues will probably only appear once the imports and syntax are valid enough so that a instance can start up. So don't put too much work into guessing the right code for py3 while you cannot test it yet in py3.
modernize
works very similar to sixer but adresses way more issues. Among others it can fix relative imports, exceptions and tuple parameters. For details just give it a try.
$ python-modernize -wn -x libmodernize.fixes.fix_import .
This runs all fixers except one that adds from __future__ import absolute_import
to each file (https://python-modernize.readthedocs.io/en/latest/fixers.html#2to3fixer-import).
Same as with sixer you need to check each change for its necessity and sanity!!!
If the tests pass in python 2.7 and you think you did your best to prepare the code to be able to at least start up in python 3 do the following:
six
to the dependencies in setup.py
CHANGES.txt
: Prepare for Python 2 / 3 compatibility [yourname]
Prepare for Python 2 / 3 compatibility
Plone does not yet run or even startup in Python 3 but trying it is a nice way to find and fix py3 issues without guessing.
The config py3.cfg
(https://github.com/plone/buildout.coredev/blob/5.2/py3.cfg) can
be used to build and run Plone on Python3. It removes the Arcetypes-dependecies that will not be
ported to python3. In your own projects you could add a similar effect by depending only on Products.CMFPlone
instead of Plone
.
The config depends on wsgi.cfg
for wsgi-support and adds checkouts and
branches with python3 compatability that is work-in progress.
It also removes a lot of parts that either break at the moment or are not necessary before startup works.
Clone coredev and use branch 5.2::
$ git clone git@github.com:plone/buildout.coredev.git coredev_py3
$ cd coredev_py3
$ git checkout 5.2
Create py3 virtualenv::
$ python3.6 -m venv .
Install buildout::
$ ./bin/pip install -r requirements.txt
Run buildout::
$ ./bin/buildout -c py3.cfg
Start Plone::
$ ./bin/wsgi.py
Startup will fail since a lot of code is not python3-compatible yet.
Frequent errors include:
@pbauer thanks for putting some documentation!! ❤️ , shouldn't we put that on buildout.coredev docs folder?
May I ask one question? According to wikipedia by 2020, only Python 3.6 will still be supported (ok, 3.5 as well, but only until September).
Does it make sense to support 3.4 at all? Given that our testing setup is complex enough, having to run all the tests against 2.7 and 3.6 will be already a lot of time to wait to merge pull requests, the more python versions we try to support the longer the whole process it will be...
@gforcada good question. I simply thought we would support the same version as Zope. But it makes sense to only support 3.5. and 3.6 since 3.4 reaches end of live in march next year. The ticket is WIP. I'll move the final text to buildout.coredev docs after the sprint in Insbruck this week.
Cool 👍 have fun there!! ⛷
sixer is not picking up doctests automatically. Files not ending with *.py need to be specified explicitly:
sixer all -w src/plone.testing/src/plone/testing/*.rst
Some things that sixer does not pick up:
cmp
function anymore. For sorting it is always better to use a key instead.zope.interface.implements
should become a decorator @implementer
. But Gil already fixed a lot of this last year.types.UnicodeType
, which can be replaced by six.text_type
next(self)
methods, which should be converted into an alias to a new __next__(self)
method, as well as <iterator>.next
calls which should be replaced by next(<iterator>)
ones. (see https://stackoverflow.com/questions/29578469/how-to-make-an-object-both-a-python2-and-python3-iterator or https://github.com/zopefoundation/Products.ExternalEditor/pull/8/files#diff-0522eecf4a53d28fa89b294910ac5b6fL58)As an addendum to @pbauer 's instructions to get coredev-py3 running:
I ran into problems running ./bin/buildout -c py3.cfg
when it tried to install zope.security
:
ValueError: not enough values to unpack (expected 1, got 0)
Installing it manually via ./bin/pip install zope.security
yields:
src/zope/proxy/_zope_proxy_proxy.c:27:10: fatal error: Python.h: File or directory not found
This means that I had to install the Python3 development package (which on my system is done with sudo apt install python3-dev
).
Maybe this helps someone.
I think that unicode could have a full blown article in your porting code guidelines.
I tried Plone Python 3 and searching for news, one of the first thing I tried, is crashing.
ERROR:portlets:Error while determining renderer availability of portlet ('context' '/Plone' 'news'): a bytes-like object is required, not 'str'
Traceback (most recent call last):
File "/home/gp/coredev_py3/eggs/plone.portlets-2.3-py3.6.egg/plone/portlets/manager.py", line 117, in _lazyLoadPortlets
isAvailable = renderer.available
File "/home/gp/coredev_py3/src/plone.app.portlets/plone/app/portlets/portlets/news.py", line 91, in available
return self.data.count > 0 and len(self._data())
File "/home/gp/coredev_py3/eggs/plone.memoize-1.2.2-py3.6.egg/plone/memoize/instance.py", line 53, in memogetter
val = func(*args, **kwargs)
File "/home/gp/coredev_py3/src/plone.app.portlets/plone/app/portlets/portlets/news.py", line 119, in _data
sort_limit=limit)[:limit]
File "/home/gp/coredev_py3/src/Products.CMFPlone/Products/CMFPlone/CatalogTool.py", line 458, in searchResults
if not show_inactive and not self.allow_inactive(kw):
File "/home/gp/coredev_py3/src/Products.CMFPlone/Products/CMFPlone/CatalogTool.py", line 419, in allow_inactive
parts = path[len(site_path) + 1:].split('/')
TypeError: a bytes-like object is required, not 'str'
The Python error message is difficult to understand, path[len(site_path) + 1:] is a bytes object, that's the whole problem, if it was a str it would work.
the code:
if isinstance(paths, six.string_types):
paths = [paths]
objs = []
site = getSite()
for path in list(paths):
path = path.encode('utf-8') # paths must not be unicode
try:
site_path = '/'.join(site.getPhysicalPath())
parts = path[len(site_path) + 1:].split('/')
parent = site.unrestrictedTraverse('/'.join(parts[:-1]))
objs.append(parent.restrictedTraverse(parts[-1]))
Removing the path = path.encode('utf-8') # paths must not be unicode line makes it work (not that it is a good fix of course)
I grepped the python code of the git-cloned coredev for encode('utf-8') and got about 250 hits. I have tried without success to search in Github for the plone and collective organisations, I am not sure there is a way.
Not that all these hundred of hits will be bad of course. But they could. And it may not even be especially obvious. So a guideline on Unicode and porting Plone to Python 3 could be good to have, also for add-on writers who could write not compatible code - even right now
Try:
path[len(site_path) + 1:].split(b'/')
Note the b. Prepending a b should work on both Py2 and Py3.
@ale-rt : yes it seems to work on both Plone for Python 3 and Python 5.1.2. My main point was that it's a specific task that could be documented in guidelines, with the patterns to search, how to fix...It can't be the one place in the code where unicode will be a problem.
The docs above mostly apply to porting Plone itself. For addons this is documented in https://github.com/plone/documentation/blob/5.2/manage/upgrading/version_specific_migration/upgrade_to_python3.rst
Yesterday we have been discussing (see #2181) if using six is a good option for us and there seems there is consensus about it.
We are investigating ways to automate the code porting and we found out the tool sixer.
We now want to write a script to automatically run sixer on our packages.
Anybody who has suggestions (or is aware of some reasons to not use this approach) is invited to discuss on this issue.