|
| 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