Skip to content

Commit 5881c44

Browse files
authored
Merge pull request #55 from Carreau/practicalities
Start working on the practicalities section.
2 parents 9a3c3b4 + f9ed6e8 commit 5881c44

File tree

7 files changed

+420
-5
lines changed

7 files changed

+420
-5
lines changed

Diff for: .gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
_site/
2-
.DS_Store
2+
.DS_Store
3+
*.swo

Diff for: _config.yml

+1
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ colors:
3535

3636
collections:
3737
- sections
38+
- practicalities
3839

Diff for: _includes/css/base.css

+10-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,16 @@
274274
width:90%;
275275
}
276276

277-
.highlight pre, .highlight code { display:block; margin:0; padding:0; background: none; overflow:auto; word-wrap: normal; }
277+
.highlight pre, .highlight code {
278+
display:block;
279+
margin:0;
280+
padding:0;
281+
background:
282+
none;
283+
overflow:auto;
284+
word-wrap: normal;
285+
white-space: pre;
286+
}
278287

279288
.highlight, .linenodiv {
280289
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWPQ1dU1BgABzQC7XXMTYQAAAABJRU5ErkJggg==);

Diff for: _includes/css/main.css

+10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ html { box-sizing: border-box; }
2525
#{{id}} .sectiondivider { color: {{ bg }}; }
2626
{% endfor %}
2727

28+
{% for node in site.practicalities %}
29+
{% capture id %}{{ node.id | remove:'/' | downcase }}{% endcapture %}
30+
{% capture bg %}{% if site.colors[node.bg] %}{{ site.colors[node.bg] }}{% else %}{{ node.bg }}{% endif %}{% endcapture %}
31+
{% capture fg %}{% if site.colors[node.color] %}{{ site.colors[node.color] }}{% else %}{{ node.color }}{% endif %}{% endcapture %}
32+
nav .p-{{id}} { border-color: {{ bg }}; }
33+
#{{id}} { background-color: {{ bg }} !important; color: {{ fg }}; }
34+
#{{id}} a { color: {{ fg }}; }
35+
#{{id}} .sectiondivider { color: {{ bg }}; }
36+
{% endfor %}
37+
2838

2939
/* ----- code, syntax highlighting, etc ----- */
3040

Diff for: _practicalities/intro.md

+352
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
---
2+
bg: '#4da45e'
3+
color: white
4+
title: practicalities
5+
fa-icon: pencil
6+
id: bar
7+
---
8+
9+
We do not discourage authors to release software on Python 2. While this guide
10+
is mostly written with the assumption that software are going to stop Python 2
11+
support, it does perfectly apply to a package that wish to not support Python 3,
12+
or is stopping support for any minor version.
13+
14+
15+
This page gather information and links to resources allowing to release a
16+
library that stop supporting an older version of Python without causing too
17+
much disruption for users who haven't upgraded to this new version.
18+
19+
Whether you are a user, or a developer, being aware of the issue listed here, at
20+
least the main points should ease lots of the pain.
21+
22+
# Too long, did not read:
23+
24+
- Help and encourage users to install **pip 9.0+**
25+
- Help and encourage users to install **setuptools 24.3+**
26+
- As maintainer use `setup(..., python_requires='>=3.4')` new option.
27+
- do use `pip install [-e] .` and do **not** invoke `setup.py` directly.
28+
- **Fail** early at **install time** if on Python 2.
29+
- We are giving a talk at PyCon 2017 (likely recorded), add link here.
30+
31+
## The problem
32+
33+
Up until December 2016 it was hard to publish a new major version of library
34+
that changed requirements in Python version and mark it as such so that user
35+
system will not try to upgrade said library.
36+
37+
With the recent changes in Python packaging this is now possible.
38+
39+
As an example let's look at the example of the `fictitious` library.
40+
41+
- `fictitious` 1.1, 1.2, 1.3, 1.4 are compatible Python 2.7 and 3.3+
42+
- `fictitious` 2.0 has been released and is python 3.4+ only.
43+
44+
As a Python 2.7 user, if I don't pay attention, or if the library is not
45+
correctly tagged, if I issue the following:
46+
47+
$ python -c 'import fictitious; print(fictitious.__version__)'
48+
1.3.2
49+
$ pip install fiction --upgrade
50+
51+
Either my system will install 2.0, which will not work, on the worst case
52+
scenario, or fail to install, in which case I will not get the critical 1.4
53+
upgrade.
54+
55+
## As a user
56+
57+
### Install Pip 9.0
58+
59+
If you are already a Python 3 user, you should not encounter a lot of
60+
disruption. Please still check that the libraries you use follow best practices
61+
not to break for Python 2 users. Python is a community regardless of which
62+
python version you have to (or decided to) run, making sure that everything
63+
works make the community strong.
64+
65+
Make sure you have Pip >= 9.0, this is especially important if you have Python
66+
2 installations. Having pip 9.0+ is not a guaranty to flawless upgrade. But pip
67+
9.0+ does have a number of safety check not available on previous versions.
68+
69+
Having a version of pip < 9.0 can lead your system to try to upgrade to
70+
non-compatible versions of Python packages even if these are marked as
71+
non-compatible.
72+
73+
Help as many other _users_ as possible to install pip >=9.0, for the
74+
transition, it is the slowest part of the ecosystem to update, and is the only
75+
piece that requires action of all Python users.
76+
77+
The simplest way to make sure all is up to date is to run the following for
78+
each installation of Python:
79+
80+
$ pip install --upgrade setuptools pip
81+
82+
This will install the latest version of pip and setuptools.
83+
84+
You can issue the following to see the version of pip:
85+
86+
$ pip --version
87+
9.0.0
88+
89+
All good.
90+
91+
92+
93+
## Setuptools
94+
95+
If you are on a system for which no wheel is available, pip will try to
96+
install a source distribution (aka `sdist`).
97+
98+
Installing an `sdist` will require setuptools make sure you have setuptools
99+
`>=24.2.0` or building Python 3 only libraries is likely to fail. In particular
100+
if library authors have taken time to mark their library as Python 3 only, the
101+
`python_requires` argument to `setup()` may not be recognized and installation
102+
will fail.
103+
104+
Use the following to check setuptools version :
105+
106+
$ python -c 'import setuptools; print(setuptools.__version__)
107+
24.2.0
108+
109+
Again make sure to upgrade pip and setuptools to make sure you have an up to
110+
date system:
111+
112+
$ pip install --upgrade setuptools pip
113+
114+
## Local package index
115+
116+
If you are using a custom local package index, for example if you are working
117+
at a company with private packages, make sure it implement correctly
118+
[pep-503](https://www.python.org/dev/peps/pep-0503/) and let pip knows about
119+
the `python_requires` field. This _mostly_ mean that the html you are exposing
120+
should get a `data-python-requires` data attribute with the (html escaped)
121+
version specifier.
122+
123+
## The state of PyPI
124+
125+
Note that at the time of this writing the patches to `pypi.python.org` are not
126+
deployed yet but should hopefully be deployed soon.
127+
128+
[Warehouse](https://github.com/pypi/warehouse) and [Legacy
129+
PyPI](https://github.com/pypa/legacy-pypi) have received various patches to
130+
insure they support this new functionality.
131+
132+
# Preparing your library
133+
134+
135+
As a library author one of the most important factor in a smooth transition is
136+
planning and communication, letting your user base know in advance that the
137+
transition is happening and what step to take is critical for a transition.
138+
139+
For your library code here the steps you need to take to ensure that
140+
installation will fail in the least number of case:
141+
142+
You need to release your new packages version with
143+
[setuptools](https://pypi.python.org/pypi/setuptools) version 24.2.0 or above.
144+
You can also use one of the alternate package manager that can set the
145+
[Requires-Python](https://www.python.org/dev/peps/pep-0345/#requires-python)
146+
metadata field. Without this, pip 9.0 **will try** to install non-compatible
147+
version of your software on Python 2. This version of setuptools is recent
148+
(July 20, 2016) and this possible thank to the [work of Xavier
149+
Fernandez](https://github.com/pypa/setuptools/pull/631)
150+
151+
Add the following to your `setup.py`
152+
153+
```
154+
setup(
155+
...
156+
python_requires='>=3.3'
157+
...
158+
)
159+
```
160+
161+
Change `>=3.3` accordingly depending on what version your library decides to
162+
support. In particular you can use `>=2.6` or `>=3.5` ! Note that this also
163+
support the _compable with_ syntax: `~=2.5` (meaning, `>=2.5` and `<3`).
164+
165+
This will make [PyPI aware](https://github.com/pypa/warehouse/pull/1448) that
166+
your package is Python 3.3+ only, and [allow
167+
pip](https://github.com/pypa/pip/pull/3877) to be [made aware of
168+
this](https://github.com/pypa/pypi-legacy/pull/506).
169+
170+
Thus as long as your user have recent enough versions of pip and setuptools
171+
they will get the right version of your library.
172+
173+
# Unit Testing and documentation
174+
175+
It is recommended **not** to invoke `setup.py` directly either with `install` or
176+
`develop` subcommands. These may not correctly resolve dependencies, and can
177+
install incompatible versions of dependencies. Please recommend and use `pip
178+
install . ` and `pip install -e .` for regular and developer install.
179+
180+
Check in scripts, and documentation that the correct installation command is
181+
used.
182+
183+
# Recommended Mitigations
184+
185+
These are not mandatory but should make the transition seamless by warning your
186+
user early enough _and_ providing useful error messages.
187+
188+
## Runtime warning on master
189+
190+
Add a warning at _runtime_ early on master (before switching to Python 3
191+
only)
192+
193+
```
194+
import warnings
195+
import sys
196+
if sys.version_info < (3,):
197+
warnings.warn('You are using master of `Frobulator` with Python 2. '
198+
'Frobulator will soon be Python 3 only. '
199+
'See this issue to know more.',
200+
UserWarning)
201+
```
202+
203+
Your Python 2 user have a chance to upgrade, or get off master, (for example on
204+
the LTS branch).
205+
206+
## Fail early at import time
207+
208+
Add an error early at import at runtime with a clear error message, leave the
209+
early import compatible Python 2 for users to not be welcomed with a useless
210+
`SyntaxError`. Don't hesitate to use multi-line strings in error messages.
211+
212+
Error at import time _will_ happen on system with old version of pip and
213+
setuptools. Keep in mind that saying the package is Python 3 only is not a lot
214+
more helpful than a Syntax error. The most reasonable reason would be out of
215+
data pip and setuptools:
216+
217+
218+
```
219+
import sys
220+
221+
if sys.version_info < (3,):
222+
raise ImportError(
223+
"""You are running Frobulator 6.0 on Python 2
224+
225+
Unfortunately Frobulator 6.0 and above are not compatible with Python 2
226+
anymore, and you still ended up with this version installed on your system.
227+
That's a bummer. Sorry about that. It should not have happened. Make sure you
228+
have pip >= 9.0 to avoid this kind of issues, as well as setuptools >= 24.2:
229+
230+
$ pip install pip setuptools --upgrade
231+
232+
You have various other choices
233+
234+
- install an older version of Frobulator:
235+
236+
$ pip install 'frobulator<6.0'
237+
238+
- Upgrade your system to use Python 3.
239+
240+
It would be great if you can figure out how this version ended up being
241+
installed, and try to check how to prevent that for future users.
242+
243+
See the following url for more up to date informations:
244+
245+
https://i.am.an/url
246+
247+
""")
248+
249+
```
250+
251+
## Watch out for beta releases
252+
253+
254+
Make sure your version number match pep 440 or you will get surprises during
255+
beta in particular as the `sdist` and `wheel` will appear as being different
256+
versions, in particular sdist (during beta/rc/post) can appear with a greater
257+
version number than wheels. Pip thus try to install the sdist instead of the
258+
wheel, which have more chance of failing, in particular with pre 24.2 versions
259+
of setuptools.
260+
261+
The regular expression to check for validity of pep440 can be find below:
262+
263+
`^([1-9]\\d*!)?(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))*((a|b|rc)(0|[1-9]\\d*))?(\\.post(0|[1-9]\\d*))?(\\.dev(0|[1-9]\\d*))?`
264+
265+
266+
## fail early in setup.py
267+
268+
Leave `setup.py` python 2 compatible and fail early. If you detect Python 2
269+
raise a clear error message and ask user to make sure they have pip >9.0 (or
270+
migrate to Python 3). You can (try to) conditionally import pip and check for
271+
its version but this might not be the same pip. Failing early is important to
272+
make sure the Python installation does not install an incompatible version.
273+
Otherwise user code can fail at runtime arbitrary later in the future, which can
274+
be a difficult to debug and fix. Get inspiration from the message of failure at
275+
runtime, and adapt for installation time.
276+
277+
## Fix dependant libraries
278+
279+
If you control dependant packages, Make sure to include conditional dependencies
280+
depending on the version of Python.
281+
282+
# Non recommended mitigations
283+
284+
This is a collection of "mitigation" or "solutions" you will find on the web
285+
and that you will hear about. This is an attempt to acknowledge them, and
286+
explain why they can't work and what are their drawbacks before you attempt to
287+
implement them.
288+
289+
### Use a meta-package.
290+
291+
It is possible to release a meta-package that has _virtually_ no code and rely
292+
on conditional dependency to install its actual core code on the user system.
293+
For example, Frob-6.0 could be a meta-package which depends on
294+
Frob-real-py2 on Python <3.0, and Frob-real-py3 on Python >= 3.4. While
295+
this approach is _doable_ this can make imports confusing.
296+
297+
## Depend on setuptools
298+
299+
You can mark your library as dependent on setuptools greater than 24.3 this
300+
will insure that during the next upgrade (when the packages drop python 2
301+
support) will have the right version of setuptools.
302+
303+
Of course regardless of all the care you will take for your library to no break
304+
and to install only on python 2, you will likely have cases where it still end
305+
up being installed on incompatible versions of Python. Simply because users
306+
upgrades rarely and only an old version of pip or setuptools is enough to make
307+
the all update process broken.
308+
309+
Plus setuptools is rarely an actual dependency of your project but a
310+
requirement to build wheels.
311+
312+
313+
### Multiple Sdist.
314+
315+
Pip (used to) support a "feature" where a sdist ending in `-pyX.Y.tar.gz` would
316+
only be seen as compatible on Python X.Y, thus it used to be possible to
317+
publish multiple sdist of a package targeting various python version.
318+
319+
Though it is not possible anymore to upload multiple sdist on PyPI. This
320+
solution is thus not possible.
321+
322+
### Wheel only ?
323+
324+
Releasing a package only using wheel for a given python version is doable, but
325+
this will break downstream packages that may require the original source to
326+
reproduce the build.
327+
328+
# Why all that ?
329+
330+
You might wonder why all this, it's 2016 already, so how come all these
331+
issues ? Python 3 has been out for 8+ years now !
332+
333+
Well there are many reasons to this, first of all, this issue mostly affect
334+
libraries that are currently python 2 and Python 3 compatible at the same time.
335+
Many libraries have transitioned from Python 2-only to Python 2 + 3. And the
336+
issue of transitioning to Python 3 only is relatively recent. Technically it
337+
can also apply to libraries that are only stopping support for 2.6, or even are
338+
already Python 3 only, but are starting to stop support for earlier versions of
339+
Python. For example a library releasing a Python 3.4+ only version.
340+
341+
Python 3.3 was release at the end of 2012, and was the first version to
342+
support (again) `u` as a prefix for Unicode string. It was one of the first
343+
minor version of Python 3 that saw a majority of single-source project working
344+
both on Python 2 and Python 3. These are the Project that will likely be
345+
affected by this issue.
346+
347+
The introduction of Python 3 was chaotic, there are still strong argument both
348+
in Python 2 and Python 3 camps. In the one suffering the most from this are
349+
users. Starting with the fact that inevitably some libraries will stop support
350+
for Python 2 and release Python 3 only library. And that inevitably some system
351+
will will not be upgraded to Python 3 how can we _ensure_ that users get the
352+
_least_ breakage as possible ? And what are the best practices to follow.

0 commit comments

Comments
 (0)