From 3f75b67c90720a2602edc1fc272b04ff7486b5fd Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Wed, 16 May 2018 21:14:23 -0700 Subject: [PATCH 1/4] Create new, simpler packaging tutorial Move the existing tutorial into guides/distributing-packages-using-setuptools to be cleaned up later. --- nox.py | 15 +- requirements.txt | 1 + ...istributing-packages-using-setuptools.rst} | 2 +- source/guides/index.rst | 1 + source/index.rst | 2 +- source/key_projects.rst | 2 + source/tutorials/index.rst | 2 +- source/tutorials/packaging-libraries.rst | 344 ++++++++++++++++++ 8 files changed, 361 insertions(+), 8 deletions(-) rename source/{tutorials/distributing-packages.rst => guides/distributing-packages-using-setuptools.rst} (99%) create mode 100644 source/tutorials/packaging-libraries.rst diff --git a/nox.py b/nox.py index 40eda5004..7d3e19dc1 100644 --- a/nox.py +++ b/nox.py @@ -8,18 +8,23 @@ @nox.session -def build(session): +def build(session, autobuild=False): session.interpreter = 'python3.6' session.install('-r', 'requirements.txt') # Treat warnings as errors. session.env['SPHINXOPTS'] = '-W' session.run(shutil.rmtree, 'build', ignore_errors=True) - session.run('sphinx-build', '-W', '-b', 'html', 'source', 'build') + + if autobuild: + command = 'sphinx-autobuild' + else: + command = 'sphinx-build' + + session.run(command, '-W', '-b', 'html', 'source', 'build') @nox.session def preview(session): session.reuse_existing_virtualenv = True - build(session) - session.chdir('build') - session.run('python', '-m', 'http.server') + session.install("sphinx-autobuild") + build(session, autobuild=True) diff --git a/requirements.txt b/requirements.txt index db31424d3..8d2ca996f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ sphinx==1.6.2 +sphinx-autobuild==0.7.1 git+https://github.com/python/python-docs-theme.git#egg=python-docs-theme git+https://github.com/pypa/pypa-docs-theme.git#egg=pypa-docs-theme diff --git a/source/tutorials/distributing-packages.rst b/source/guides/distributing-packages-using-setuptools.rst similarity index 99% rename from source/tutorials/distributing-packages.rst rename to source/guides/distributing-packages-using-setuptools.rst index c52e012f8..0f98bec11 100644 --- a/source/tutorials/distributing-packages.rst +++ b/source/guides/distributing-packages-using-setuptools.rst @@ -6,7 +6,7 @@ Packaging and distributing projects This section covers the basics of how to configure, package and distribute your own Python projects. It assumes that you are already familiar with the contents -of the :doc:`installing-packages` page. +of the :doc:`/tutorials/installing-packages` page. The section does *not* aim to cover best practices for Python project development as a whole. For example, it does not provide guidance or tool diff --git a/source/guides/index.rst b/source/guides/index.rst index 07a477630..15b9af93c 100644 --- a/source/guides/index.rst +++ b/source/guides/index.rst @@ -13,6 +13,7 @@ introduction to packaging, see :doc:`/tutorials/index`. installing-using-linux-tools installing-scientific-packages multi-version-installs + distributing-packages-using-setuptools single-sourcing-package-version supporting-multiple-python-versions dropping-older-python-versions diff --git a/source/index.rst b/source/index.rst index fe445590e..3123ba1b7 100644 --- a/source/index.rst +++ b/source/index.rst @@ -40,7 +40,7 @@ covered in our :doc:`tutorials/index` section: * to learn how to manage dependencies in a version controlled project, see the :doc:`tutorial on managing application dependencies `. * to learn how to package and distribute your projects, see the - :doc:`tutorial on packaging and distributing ` + :doc:`tutorial on packaging and distributing ` Learn more ========== diff --git a/source/key_projects.rst b/source/key_projects.rst index 38cc4a3ea..10c1bd4d3 100644 --- a/source/key_projects.rst +++ b/source/key_projects.rst @@ -280,6 +280,8 @@ a complimentary command line tool to drive packaging, testing and release activities with Python. +.. _flit: + flit ==== diff --git a/source/tutorials/index.rst b/source/tutorials/index.rst index a4178567c..af72ba96a 100644 --- a/source/tutorials/index.rst +++ b/source/tutorials/index.rst @@ -10,4 +10,4 @@ topics, see :doc:`/guides/index`. installing-packages managing-dependencies - distributing-packages + packaging-libraries diff --git a/source/tutorials/packaging-libraries.rst b/source/tutorials/packaging-libraries.rst new file mode 100644 index 000000000..4fd7d045a --- /dev/null +++ b/source/tutorials/packaging-libraries.rst @@ -0,0 +1,344 @@ +Packaging Python Libraries +========================== + +This tutorial walks you through how to package a simple Python library. It will +show you how to add the necessary files and structure to create the package, how +to build the package, and how to upload it to the Python Package Index. + + +A simple library +---------------- + +This tutorial uses a simple library named `example_pkg`. If you are unfamiliar +with Python's modules and :term:`import packages `, take a few +minutes to read over the `Python documentation for packages and modules`_. + +To create this library locally, create the following file structure: + +.. code-block:: text + + /example_pkg + /example_pkg + __init__.py + + +Once you create this structure, you'll want to run all of the commands in this +tutorial within the top-level folder - so be sure to ``cd example-pkg``. + +You should also edit :file:`example_pkg/__init__.py` and put the following +code in there: + +.. code-block:: python + + name = "example_pkg" + +This is just so that you can verify that it installed correctly later in this +tutorial. + +.. _Python documentation for packages and modules: + https://docs.python.org/3/tutorial/modules.html#packages + + +Creating the package files +-------------------------- + +You will now create a handful of files to package up this library and prepare it +for distribution. Create the new files listed below - you will add content to +them in the following steps. + +.. code-block:: text + + /example_pkg + /example_pkg + __init__.py + setup.py + LICENSE + README.md + + +Creating setup.py +----------------- + +:file:`setup.py` is the build script for :ref:`setuptools`. It tells setuptools +about your package (such as the name and version) as well as which code files +to include. + +Open :file:`setup.py` and enter the following content, you can personalize +the values if you want: + +.. code-block:: python + + import setuptools + + with open("README.rst", "r") as fh: + long_description = fh.read() + + setuptools.setup( + name="example_pkg", + version="0.0.1", + author="Example Author", + author_email="author@example.com", + description="A small example package", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/pypa/example-project", + packages=setuptools.find_packages(), + classifiers=( + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ), + ) + + +:func:`setup` takes several arguments. This example package uses a relatively +minimal set: + +- ``name`` is the name of your package. This can be any name as long as only + contains letters, numbers, ``_`` , and ``-``. It also must not already + taken on pypi.org. +- ``version`` is the package version see PEPTODO for more details on + versions. +- ``author`` and ``author_email`` are used to identify the author of the + package. +- ``description`` is a short, one-sentence summary of the package. +- ``long_description`` is a detailed description of the package. This is + shown on the package detail package on the Python Package Index. In + this case, the long description is loaded from :file:`README.md` which is + a common pattern. +- ``long_description_content_type`` tells the index what type of markup is + used for the long description. In this case, it's Markdown. +- ``url`` is the URL for the homepage of the project. For many projects, this + will just be a link to GitHub, GitLab, Bitbucket, or similar code hosting + service. +- ``packages`` is a list of all Python :term:`import packages ` that should be included in the :term:`distribution package`. + Instead of listing each package manually, we can use :func:`find_packages` + to automatically discover all packages and subpackages. In this case, the + list of packages will be `example_pkg` as that's the only package present. +- ``classifiers`` tell the index and :ref:`pip` some additional metadata + about your pckage. In this case, the package is only compatible with Python + 3, is licensed under the MIT license, and is OS-independent. You should + always include at least which version(s) of Python your package works on, + which license your package is available under, and which operating systems + your package will work on. For a complete list of classifiers, see + https://pypi.org/classifiers/. + +There are many more than the ones mentioned here. See +:doc:`/guides/distributing-packages-using-setuptools` for more details. + + +Creating README.md +------------------ + +Open :file:`README.md` and enter the following content. You can customize this +if you'd like. + +.. code-block:: md + + # Example Package + + This is a simple example package. You can use + [Github-flavored Markdown](http://TODO) to write your content. + + +Creating a LICENSE +------------------ + +It's important for every package uploaded to the Python Package Index to include +a license. This tells users who install your package the terms under which they +can use your package. For help picking a license, see +https://choosealicense.com/. For now, open :file:`LICENSE` and enter the +following content - which is the MIT license. + +.. code-block:: text + + Copyright (c) 2018 The Python Packaging Authority + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + +.. _generating archives: + +Generating distribution archives +-------------------------------- + +The next step is to generate :term:`distribution packages ` for the package. These are archives that are uploaded the the Package +Index and can be installed by :ref:`pip`. + +Make sure you have the latest versions of ``setuptools`` and ``wheel`` +installed: + +.. code-block:: bash + + python3 -m pip install --user --upgrade setuptools wheel + +.. tip:: IF you have trouble installing these, see the + :doc:`installing-packages` tutorial. + +Now run this command from the same directory where :file:`setup.py` is located: + +.. code-block:: bash + + python3 setup.py sdist bdist_wheel + +This command should output a lot of text and once completed should generate two +files in the :file:`dist` directory: + +.. code-block:: text + + dist/ + example_pkg-0.0.1-py3-none-any.whl + example_pkg-0.0.1.tar.gz + +.. note:: If you run into trouble here, please copy the output and file an issue + over on `packaging problems`_ and we'll do our best to help you! + +.. _packaging problems: + https://github.com/pypa/packaging-problems/issues/new?title=Trouble+following+packaging+libraries+tutorial + + +The ``tar.gz`` file is a :term:`source archive` - it is intended mostly for +downstream packagers (such as Linux distros) or users who have disabled +installing built distributions. The ``.whl`` file is a :term:`built +distribution`. Newer :ref:`pip` versions preferentially installs built +distributions, but will fall back to source distributions if needed. For +pure-Python packages, like our example package, the broadest compatibility is +achieved by creating and uploading both source and built distributions. + + +Uploading the distribution archives +----------------------------------- + +Finally, it's time to upload your package to the Python Package Index! + +The first thing you'll need to do is register and account on `Test PyPI`. Test +PyPI is a separate instance of the package index intended for testing and +experimentation. It's great for things like this tutorial where we don't +necessarily want to upload to the real index. To register an account, go to +https://test.pypi.org/account/register/ and complete the steps on that page. +You will also need to verify your email address before you're able to upload +any packages. For more details on Test PyPI, see +:doc:`/guides/using-testpypi`. + +Now that you are registered, you can use :ref:`twine` to upload the +distribution packages. You'll need to install twine: + +.. code-block:: bash + + python3 -m pip install --user --upgrade twine + +Once install, run twine to upload all of the archives under :file:`dist`: + +.. code-block:: bash + + twine upload --repository-url https://test.pypi.org/legacy/ dist/* + +You will be prompted for the username and password you registered with Test +PyPI. After the command completes, you should see output similar to this: + +.. code-block:: bash + + Uploading distributions to https://test.pypi.org/legacy/ + Enter your username: [your username] + Enter your password: + Uploading example_pkg-0.0.1-py3-none-any.whl + 100%|█████████████████████| 4.65k/4.65k [00:01<00:00, 2.88kB/s] + Uploading example_pkg-0.0.1.tar.gz + 100%|█████████████████████| 4.25k/4.25k [00:01<00:00, 3.05kB/s] + +.. note:: If you get an error that says ``The user '[your username]' isn't + allowed to upload to project 'example-pkg'``, you'll need to go and pick + a unique name for your package. A good choice is + ``example_pkg_your_username``. Update the ``name`` argument in + :file:`setup.py`, remove the :file:`dist` folder, and + :ref:`regenerate the archives `. + + +Once uploaded your package should be viewable on TestPyPI, for example, +https://test.pypi.org/project/example-pkg + + +Installing your newly uploaded package +-------------------------------------- + +You can use :ref:`pip` to install your package and verify that it works. +Create a new :ref:`virtualenv` (see :doc:`/tutorials/installing-packages` for +detailed instructions) and install your package from TestPyPI: + +.. code-block:: bash + + python3 -m pip install --index-url https://test.pypi.org/simple/ example_pkg + +.. note:: If you used a different package name in the preview step, replace + ``example_pkg`` in the command above with your package name. + +pip should install the package from Test PyPI and the output should look +something like this: + +.. code-block:: text + + Collecting example_pkg + Downloading https://test-files.pythonhosted.org/packages/.../example_pkg-0.0.1-py3-none-any.whl + Installing collected packages: example-pkg + Successfully installed example-pkg-0.0.1 + +You can test that it was installed correctly by importing the module and +referencing the ``name`` property you put in :file:`__init__.py` earlier. + +Run python (make sure you're still in your virtualenv): + +.. code-block:: bash + + python + +And then import the module and print out the ``name`` property. This should be +the same regardless of what you name you gave your :term:`distribution package` +in :file:`setup.py` because your :term:`import package` is ``example_pkg``. + + +.. code-block:: python + + >>> import example_pkg + >>> example_pkg.name + 'example_pkg' + +Next steps +---------- + +**Congratulations, you've packaged and distributed a Python library!** +✨ 🍰 ✨ + +If you want to upload your package to the real Python Package Index you can do +it by registering an account on https://pypi.org and following the same +instructions above but omitting the ``--repository-url`` argument to ``twine`` +and the ``--index-url`` argument to ``pip``. + +At this point if you want to read more on packaging Python libraries here are +some things you can do: + +* Read more about using :ref:`setuptools` to package libraries in + :doc:`/guides/distributing-packages-using-setuptools`. +* Read about :doc:`/guides/packaging-binary-extensions`. +* Consider alternatives to :ref:`setuptools` such as :ref:`flit`, `hatch`_, + and `poetry`_. + +.. _hatch: https://github.com/ofek/hatch +.. _poetry: https://github.com/sdispater/poetry \ No newline at end of file From 5c22e0c7ae8b50077d0ebe326da8c72292e135e9 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Thu, 17 May 2018 10:04:23 -0700 Subject: [PATCH 2/4] Address review comments --- source/index.rst | 2 +- source/tutorials/index.rst | 2 +- ...g-libraries.rst => packaging-projects.rst} | 49 ++++++++++--------- 3 files changed, 27 insertions(+), 26 deletions(-) rename source/tutorials/{packaging-libraries.rst => packaging-projects.rst} (88%) diff --git a/source/index.rst b/source/index.rst index 3123ba1b7..67925f71f 100644 --- a/source/index.rst +++ b/source/index.rst @@ -40,7 +40,7 @@ covered in our :doc:`tutorials/index` section: * to learn how to manage dependencies in a version controlled project, see the :doc:`tutorial on managing application dependencies `. * to learn how to package and distribute your projects, see the - :doc:`tutorial on packaging and distributing ` + :doc:`tutorial on packaging and distributing ` Learn more ========== diff --git a/source/tutorials/index.rst b/source/tutorials/index.rst index af72ba96a..33ab4f98a 100644 --- a/source/tutorials/index.rst +++ b/source/tutorials/index.rst @@ -10,4 +10,4 @@ topics, see :doc:`/guides/index`. installing-packages managing-dependencies - packaging-libraries + packaging-projects diff --git a/source/tutorials/packaging-libraries.rst b/source/tutorials/packaging-projects.rst similarity index 88% rename from source/tutorials/packaging-libraries.rst rename to source/tutorials/packaging-projects.rst index 4fd7d045a..0057f0f2c 100644 --- a/source/tutorials/packaging-libraries.rst +++ b/source/tutorials/packaging-projects.rst @@ -1,19 +1,19 @@ -Packaging Python Libraries -========================== +Packaging Python Projects +========================= -This tutorial walks you through how to package a simple Python library. It will +This tutorial walks you through how to package a simple Python project. It will show you how to add the necessary files and structure to create the package, how to build the package, and how to upload it to the Python Package Index. -A simple library +A simple project ---------------- -This tutorial uses a simple library named `example_pkg`. If you are unfamiliar +This tutorial uses a simple project named `example_pkg`. If you are unfamiliar with Python's modules and :term:`import packages `, take a few minutes to read over the `Python documentation for packages and modules`_. -To create this library locally, create the following file structure: +To create this project locally, create the following file structure: .. code-block:: text @@ -42,7 +42,7 @@ tutorial. Creating the package files -------------------------- -You will now create a handful of files to package up this library and prepare it +You will now create a handful of files to package up this project and prepare it for distribution. Create the new files listed below - you will add content to them in the following steps. @@ -97,7 +97,7 @@ minimal set: - ``name`` is the name of your package. This can be any name as long as only contains letters, numbers, ``_`` , and ``-``. It also must not already taken on pypi.org. -- ``version`` is the package version see PEPTODO for more details on +- ``version`` is the package version see :pep:`440` for more details on versions. - ``author`` and ``author_email`` are used to identify the author of the package. @@ -139,7 +139,8 @@ if you'd like. # Example Package This is a simple example package. You can use - [Github-flavored Markdown](http://TODO) to write your content. + [Github-flavored Markdown](http://https://guides.github.com/features/mastering-markdown/) + to write your content. Creating a LICENSE @@ -148,8 +149,9 @@ Creating a LICENSE It's important for every package uploaded to the Python Package Index to include a license. This tells users who install your package the terms under which they can use your package. For help picking a license, see -https://choosealicense.com/. For now, open :file:`LICENSE` and enter the -following content - which is the MIT license. +https://choosealicense.com/. Once you have chosen a license, open +:file:`LICENSE` and enter the license text. For example, if you had chosen the +MIT license: .. code-block:: text @@ -215,14 +217,12 @@ files in the :file:`dist` directory: https://github.com/pypa/packaging-problems/issues/new?title=Trouble+following+packaging+libraries+tutorial -The ``tar.gz`` file is a :term:`source archive` - it is intended mostly for -downstream packagers (such as Linux distros) or users who have disabled -installing built distributions. The ``.whl`` file is a :term:`built -distribution`. Newer :ref:`pip` versions preferentially installs built -distributions, but will fall back to source distributions if needed. For -pure-Python packages, like our example package, the broadest compatibility is -achieved by creating and uploading both source and built distributions. - +The ``tar.gz`` file is a :term:`source archive` whereas the ``.whl`` file is a +:term:`built distribution`. Newer :ref:`pip` versions preferentially install +built distributions, but will fall back to source archives if needed. You +should always upload a source archive and provide built archives for the +platforms your project is compatible with. In this case, our example package is +compatible with Python on any platform so only one built distribution is needed. Uploading the distribution archives ----------------------------------- @@ -239,13 +239,13 @@ any packages. For more details on Test PyPI, see :doc:`/guides/using-testpypi`. Now that you are registered, you can use :ref:`twine` to upload the -distribution packages. You'll need to install twine: +distribution packages. You'll need to install Twine: .. code-block:: bash python3 -m pip install --user --upgrade twine -Once install, run twine to upload all of the archives under :file:`dist`: +Once installed, run Twine to upload all of the archives under :file:`dist`: .. code-block:: bash @@ -313,23 +313,24 @@ And then import the module and print out the ``name`` property. This should be the same regardless of what you name you gave your :term:`distribution package` in :file:`setup.py` because your :term:`import package` is ``example_pkg``. - .. code-block:: python >>> import example_pkg >>> example_pkg.name 'example_pkg' + Next steps ---------- -**Congratulations, you've packaged and distributed a Python library!** +**Congratulations, you've packaged and distributed a Python project!** ✨ 🍰 ✨ If you want to upload your package to the real Python Package Index you can do it by registering an account on https://pypi.org and following the same instructions above but omitting the ``--repository-url`` argument to ``twine`` -and the ``--index-url`` argument to ``pip``. +and the ``--index-url`` argument to ``pip``. Note that Test PyPI is emphermal +and it's not unusual for packages and accounts to be deleted occasionally. At this point if you want to read more on packaging Python libraries here are some things you can do: From 6f7a00a09bc055cc11ac3575379dc5cf11809651 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Thu, 17 May 2018 12:23:04 -0700 Subject: [PATCH 3/4] Address review comments --- source/tutorials/packaging-projects.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/tutorials/packaging-projects.rst b/source/tutorials/packaging-projects.rst index 0057f0f2c..05541e6a3 100644 --- a/source/tutorials/packaging-projects.rst +++ b/source/tutorials/packaging-projects.rst @@ -9,7 +9,7 @@ to build the package, and how to upload it to the Python Package Index. A simple project ---------------- -This tutorial uses a simple project named `example_pkg`. If you are unfamiliar +This tutorial uses a simple project named ``example_pkg``. If you are unfamiliar with Python's modules and :term:`import packages `, take a few minutes to read over the `Python documentation for packages and modules`_. @@ -303,7 +303,7 @@ something like this: You can test that it was installed correctly by importing the module and referencing the ``name`` property you put in :file:`__init__.py` earlier. -Run python (make sure you're still in your virtualenv): +Run the Python interpreter (make sure you're still in your virtualenv): .. code-block:: bash From 475b43ed6a56d2e7276452c4de32c9522af2c56a Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Sat, 19 May 2018 23:08:25 -0700 Subject: [PATCH 4/4] Address review comments --- source/tutorials/packaging-projects.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/tutorials/packaging-projects.rst b/source/tutorials/packaging-projects.rst index 05541e6a3..0536386b8 100644 --- a/source/tutorials/packaging-projects.rst +++ b/source/tutorials/packaging-projects.rst @@ -326,11 +326,12 @@ Next steps **Congratulations, you've packaged and distributed a Python project!** ✨ 🍰 ✨ -If you want to upload your package to the real Python Package Index you can do -it by registering an account on https://pypi.org and following the same -instructions above but omitting the ``--repository-url`` argument to ``twine`` -and the ``--index-url`` argument to ``pip``. Note that Test PyPI is emphermal -and it's not unusual for packages and accounts to be deleted occasionally. +Keep in mind that this tutorial showed you how to upload your package to Test +PyPI and Test PyPI is ephemeral. It's not unusual for packages and accounts to +be deleted occasionally. If you want to upload your package to the real Python +Package Index you can do it by registering an account on https://pypi.org and +following the same instructions above but omitting the ``--repository-url`` +argument to ``twine`` and the ``--index-url`` argument to ``pip``. At this point if you want to read more on packaging Python libraries here are some things you can do: